fix(customers): Improve index page UI and fix stats display

Fixed all 6 issues in Customer index:

1.  Search Input - Match Coupon Module:
- Mobile: Native input with proper styling
- Desktop: Native input with proper styling
- Consistent with Coupon module pattern
- Better focus states and padding

2.  Filter - Not Needed:
- Customer data is simple (name, email, stats)
- Search is sufficient for finding customers
- No complex filtering like products/coupons

3.  Stats Display - FIXED:
- Backend: Changed format_customer() to include stats (detailed=true)
- Now shows actual order count and total spent
- No more zero orders or dashed values

4.  Member/Guest Column - Added:
- New 'Type' column in table
- Shows badge: Member (blue) or Guest (gray)
- Based on customer.role field

5.  Actions Column - Added:
- New 'Actions' column with Edit button
- Edit icon + text link
- Navigates to /customers/:id/edit

6.  Navigation - Fixed:
- Name click → Detail page (/customers/:id)
- Edit button → Edit page (/customers/:id/edit)
- Mobile cards also link to detail page
- Separation of concerns: view vs edit

Changes Made:

Backend (CustomersController.php):
- Line 96: format_customer(, true) to include stats

Frontend (Customers/index.tsx):
- Search inputs: Match Coupon module styling
- Table: Added Type and Actions columns
- Type badge: Member (blue) / Guest (gray)
- Actions: Edit button with icon
- Navigation: Name → detail, Edit → edit
- Mobile cards: Link to detail page

Table Structure:
- Checkbox | Customer | Email | Type | Orders | Total Spent | Registered | Actions
- 8 columns total (was 6)

Next: Create customer detail page with related orders and stats
This commit is contained in:
dwindown
2025-11-21 00:25:22 +07:00
parent 2e993b2f96
commit 64e8de09c2
2 changed files with 29 additions and 13 deletions

View File

@@ -11,7 +11,7 @@ import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { ErrorCard } from '@/components/ErrorCard';
import { Skeleton } from '@/components/ui/skeleton';
import { RefreshCw, Trash2, Search, User, ChevronRight } from 'lucide-react';
import { RefreshCw, Trash2, Search, User, ChevronRight, Edit } from 'lucide-react';
import { formatMoney } from '@/lib/currency';
export default function CustomersIndex() {
@@ -103,15 +103,14 @@ export default function CustomersIndex() {
<div className="md:hidden">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
type="search"
placeholder={__('Search customers...')}
<input
value={search}
onChange={(e) => {
setSearch(e.target.value);
setPage(1);
}}
className="pl-9"
placeholder={__('Search customers...')}
className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-border bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
/>
</div>
</div>
@@ -146,15 +145,14 @@ export default function CustomersIndex() {
<div className="flex gap-3 items-center">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
type="search"
placeholder={__('Search customers...')}
<input
value={search}
onChange={(e) => {
setSearch(e.target.value);
setPage(1);
}}
className="pl-9 w-64"
placeholder={__('Search customers...')}
className="pl-10 pr-4 py-2 rounded-lg border border-border bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent w-64"
/>
</div>
</div>
@@ -175,15 +173,17 @@ export default function CustomersIndex() {
</th>
<th className="text-left p-3 font-medium">{__('Customer')}</th>
<th className="text-left p-3 font-medium">{__('Email')}</th>
<th className="text-left p-3 font-medium">{__('Type')}</th>
<th className="text-left p-3 font-medium">{__('Orders')}</th>
<th className="text-left p-3 font-medium">{__('Total Spent')}</th>
<th className="text-left p-3 font-medium">{__('Registered')}</th>
<th className="text-left p-3 font-medium">{__('Actions')}</th>
</tr>
</thead>
<tbody>
{customers.length === 0 ? (
<tr>
<td colSpan={6} className="p-8 text-center text-muted-foreground">
<td colSpan={8} className="p-8 text-center text-muted-foreground">
<User className="w-12 h-12 mx-auto mb-2 opacity-50" />
{search ? __('No customers found matching your search') : __('No customers yet')}
{!search && (
@@ -206,11 +206,18 @@ export default function CustomersIndex() {
/>
</td>
<td className="p-3">
<Link to={`/customers/${customer.id}/edit`} className="font-medium hover:underline">
<Link to={`/customers/${customer.id}`} className="font-medium hover:underline">
{customer.display_name || `${customer.first_name} ${customer.last_name}`}
</Link>
</td>
<td className="p-3 text-sm text-muted-foreground">{customer.email}</td>
<td className="p-3">
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
customer.role === 'customer' ? 'bg-blue-100 text-blue-800' : 'bg-gray-100 text-gray-800'
}`}>
{customer.role === 'customer' ? __('Member') : __('Guest')}
</span>
</td>
<td className="p-3 text-sm">{customer.stats?.total_orders || 0}</td>
<td className="p-3 text-sm font-medium">
{customer.stats?.total_spent ? formatMoney(customer.stats.total_spent) : '—'}
@@ -218,6 +225,15 @@ export default function CustomersIndex() {
<td className="p-3 text-sm text-muted-foreground">
{new Date(customer.registered).toLocaleDateString()}
</td>
<td className="p-3">
<button
onClick={() => navigate(`/customers/${customer.id}/edit`)}
className="inline-flex items-center gap-1 text-sm text-primary hover:underline"
>
<Edit className="w-4 h-4" />
{__('Edit')}
</button>
</td>
</tr>
))
)}
@@ -236,7 +252,7 @@ export default function CustomersIndex() {
customers.map((customer) => (
<Link
key={customer.id}
to={`/customers/${customer.id}/edit`}
to={`/customers/${customer.id}`}
className="block bg-card border border-border rounded-xl p-3 hover:bg-accent/50 transition-colors active:scale-[0.98] active:transition-transform shadow-sm"
>
<div className="flex items-center gap-3">

View File

@@ -93,7 +93,7 @@ class CustomersController {
$customers = [];
foreach ($users as $user) {
$customers[] = self::format_customer($user);
$customers[] = self::format_customer($user, true); // Include stats for list view
}
return new WP_REST_Response([