diff --git a/admin-spa/src/App.tsx b/admin-spa/src/App.tsx
index efac767..700a0b4 100644
--- a/admin-spa/src/App.tsx
+++ b/admin-spa/src/App.tsx
@@ -24,6 +24,7 @@ import CouponEdit from '@/routes/Coupons/Edit';
import CustomersIndex from '@/routes/Customers';
import CustomerNew from '@/routes/Customers/New';
import CustomerEdit from '@/routes/Customers/Edit';
+import CustomerDetail from '@/routes/Customers/Detail';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { LayoutDashboard, ReceiptText, Package, Tag, Users, Settings as SettingsIcon, Maximize2, Minimize2, Loader2 } from 'lucide-react';
import { Toaster } from 'sonner';
@@ -486,6 +487,7 @@ function AppRoutes() {
} />
} />
} />
+ } />
{/* More */}
} />
diff --git a/admin-spa/src/routes/Customers/Detail.tsx b/admin-spa/src/routes/Customers/Detail.tsx
new file mode 100644
index 0000000..9864288
--- /dev/null
+++ b/admin-spa/src/routes/Customers/Detail.tsx
@@ -0,0 +1,252 @@
+import React from 'react';
+import { useParams, useNavigate, Link } from 'react-router-dom';
+import { useQuery } from '@tanstack/react-query';
+import { __ } from '@/lib/i18n';
+import { CustomersApi } from '@/lib/api/customers';
+import { OrdersApi } from '@/lib/api';
+import { showErrorToast, getPageLoadErrorMessage } from '@/lib/errorHandling';
+import { usePageHeader } from '@/contexts/PageHeaderContext';
+import { ErrorCard } from '@/components/ErrorCard';
+import { Skeleton } from '@/components/ui/skeleton';
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { ArrowLeft, Edit, Mail, Calendar, ShoppingBag, DollarSign, User, MapPin } from 'lucide-react';
+import { formatMoney } from '@/lib/currency';
+
+export default function CustomerDetail() {
+ const { id } = useParams<{ id: string }>();
+ const navigate = useNavigate();
+ const customerId = parseInt(id || '0', 10);
+
+ // Fetch customer data
+ const customerQuery = useQuery({
+ queryKey: ['customer', customerId],
+ queryFn: () => CustomersApi.get(customerId),
+ enabled: !!customerId,
+ });
+
+ // Fetch customer orders
+ const ordersQuery = useQuery({
+ queryKey: ['customer-orders', customerId],
+ queryFn: () => OrdersApi.list({ customer_id: customerId, per_page: 100 }),
+ enabled: !!customerId,
+ });
+
+ const customer = customerQuery.data;
+ const orders = ordersQuery.data?.orders || [];
+ const { setPageHeader, clearPageHeader } = usePageHeader();
+
+ // Page header
+ React.useEffect(() => {
+ if (!customer) {
+ clearPageHeader();
+ return;
+ }
+
+ const actions = (
+
+
+
+
+ );
+
+ setPageHeader(
+ customer.display_name || `${customer.first_name} ${customer.last_name}`,
+ actions
+ );
+
+ return () => clearPageHeader();
+ }, [customer, customerId, navigate, setPageHeader, clearPageHeader]);
+
+ // Loading state
+ if (customerQuery.isLoading) {
+ return (
+
+
+
+
+ );
+ }
+
+ // Error state
+ if (customerQuery.isError || !customer) {
+ return (
+ customerQuery.refetch()}
+ />
+ );
+ }
+
+ // Calculate stats from orders
+ const completedOrders = orders.filter((o: any) => o.status === 'completed' || o.status === 'processing');
+ const totalSpent = completedOrders.reduce((sum: number, order: any) => sum + parseFloat(order.total || '0'), 0);
+
+ return (
+
+ {/* Customer Info Card */}
+
+
+
+
+
+
+
+
+ {customer.display_name || `${customer.first_name} ${customer.last_name}`}
+
+
{customer.email}
+
+
+
+ {customer.role === 'customer' ? __('Member') : __('Guest')}
+
+
+
+ {/* Stats Grid */}
+
+
+
+
+ {__('Total Orders')}
+
+
{customer.stats?.total_orders || 0}
+
+
+
+
+
+ {__('Total Spent')}
+
+
+ {customer.stats?.total_spent ? formatMoney(customer.stats.total_spent) : formatMoney(0)}
+
+
+
+
+
+
+ {__('Registered')}
+
+
+ {new Date(customer.registered).toLocaleDateString('id-ID', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
+ })}
+
+
+
+
+
+ {/* Contact & Address Info */}
+
+ {/* Contact Info */}
+
+
+
+ {__('Contact Information')}
+
+
+
+
{__('Email')}
+
{customer.email}
+
+ {customer.billing?.phone && (
+
+
{__('Phone')}
+
{customer.billing.phone}
+
+ )}
+
+
+
+ {/* Billing Address */}
+ {customer.billing && (customer.billing.address_1 || customer.billing.city) && (
+
+
+
+ {__('Billing Address')}
+
+
+ {customer.billing.address_1 &&
{customer.billing.address_1}
}
+ {customer.billing.address_2 &&
{customer.billing.address_2}
}
+
+ {[customer.billing.city, customer.billing.state, customer.billing.postcode]
+ .filter(Boolean)
+ .join(', ')}
+
+ {customer.billing.country &&
{customer.billing.country}
}
+
+
+ )}
+
+
+ {/* Recent Orders */}
+
+
+
{__('Recent Orders')}
+ {orders.length > 0 && (
+
+ {__('View all orders')}
+
+ )}
+
+
+ {ordersQuery.isLoading ? (
+
+
+
+
+ ) : orders.length === 0 ? (
+
+
+
{__('No orders yet')}
+
+ ) : (
+
+ {orders.slice(0, 10).map((order: any) => (
+
+
+
+
+ #{order.number}
+
+ {order.status}
+
+
+
+ {new Date(order.date_created).toLocaleDateString('id-ID')}
+
+
+
+
{formatMoney(parseFloat(order.total || '0'))}
+
+ {order.line_items?.length || 0} {__('items')}
+
+
+
+
+ ))}
+
+ )}
+
+
+ );
+}