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:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user