feat: Standardize table UI across Orders and Products modules
**Issue:** - Orders and Products had inconsistent table styling - Orders: px-3 py-2, no hover, no header bg - Products: p-3, hover effect, header bg **Solution:** 1. Added comprehensive Table/List UI Standards to PROJECT_SOP.md 2. Updated Orders table to match Products standard **Changes to PROJECT_SOP.md:** - Added "Table/List UI Standards" section - Defined required classes for all table elements - Specified padding: p-3 (NOT px-3 py-2) - Specified header: bg-muted/50 + font-medium - Specified rows: hover:bg-muted/30 - Added empty state and mobile card patterns **Changes to Orders/index.tsx:** - Container: border-border bg-card → border (match Products) - Header: border-b → bg-muted/50 + border-b - Header cells: px-3 py-2 → p-3 font-medium text-left - Body rows: Added hover:bg-muted/30 - Body cells: px-3 py-2 → p-3 - Empty state: px-3 py-12 → p-8 text-muted-foreground **Result:** ✅ Consistent padding across all modules (p-3) ✅ Consistent header styling (bg-muted/50 + font-medium) ✅ Consistent hover effects (hover:bg-muted/30) ✅ Consistent container styling (overflow-hidden) ✅ Documented standard for future modules
This commit is contained in:
@@ -260,6 +260,81 @@ When updating an existing module to follow this pattern:
|
||||
</Toolbar>
|
||||
```
|
||||
|
||||
#### Table/List UI Standards
|
||||
|
||||
All CRUD list pages MUST follow these consistent UI patterns:
|
||||
|
||||
**Table Structure:**
|
||||
```tsx
|
||||
<div className="hidden md:block rounded-lg border overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead className="bg-muted/50">
|
||||
<tr className="border-b">
|
||||
<th className="w-12 p-3">{/* Checkbox */}</th>
|
||||
<th className="text-left p-3 font-medium">{__('Column')}</th>
|
||||
{/* ... more columns */}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr className="border-b hover:bg-muted/30 last:border-0">
|
||||
<td className="p-3">{/* Cell content */}</td>
|
||||
{/* ... more cells */}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Required Classes:**
|
||||
|
||||
| Element | Classes | Purpose |
|
||||
|---------|---------|---------|
|
||||
| **Container** | `rounded-lg border overflow-hidden` | Rounded corners, border, hide overflow |
|
||||
| **Table** | `w-full` | Full width |
|
||||
| **Header Row** | `bg-muted/50` + `border-b` | Light background, bottom border |
|
||||
| **Header Cell** | `p-3 font-medium text-left` | Padding, bold, left-aligned |
|
||||
| **Body Row** | `border-b hover:bg-muted/30 last:border-0` | Border, hover effect, remove last border |
|
||||
| **Body Cell** | `p-3` | Consistent padding (NOT `px-3 py-2`) |
|
||||
| **Checkbox Column** | `w-12 p-3` | Fixed width for checkbox |
|
||||
| **Actions Column** | `text-right p-3` or `text-center p-3` | Right/center aligned |
|
||||
|
||||
**Empty State Pattern:**
|
||||
```tsx
|
||||
<tr>
|
||||
<td colSpan={columnCount} className="p-8 text-center text-muted-foreground">
|
||||
<IconComponent className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||
{primaryMessage}
|
||||
{helperText && <p className="text-sm mt-1">{helperText}</p>}
|
||||
</td>
|
||||
</tr>
|
||||
```
|
||||
|
||||
**Mobile Card Pattern:**
|
||||
```tsx
|
||||
<div className="md:hidden space-y-2">
|
||||
{items.map(item => (
|
||||
<Card key={item.id} className="p-4">
|
||||
{/* Card content */}
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
1. ✅ **Always use `p-3`** for table cells (NOT `px-3 py-2`)
|
||||
2. ✅ **Always add `hover:bg-muted/30`** to body rows
|
||||
3. ✅ **Always use `bg-muted/50`** for table headers
|
||||
4. ✅ **Always use `font-medium`** for header cells
|
||||
5. ✅ **Always use `last:border-0`** to remove last row border
|
||||
6. ✅ **Always use `overflow-hidden`** on table container
|
||||
7. ❌ **Never mix padding styles** between modules
|
||||
8. ❌ **Never omit hover effects** on interactive rows
|
||||
|
||||
**Responsive Behavior:**
|
||||
- Desktop: Show table with `hidden md:block`
|
||||
- Mobile: Show cards with `md:hidden`
|
||||
- Both views must support same actions (select, edit, delete)
|
||||
|
||||
#### Variable Product Handling in Order Forms
|
||||
|
||||
When adding products to orders, variable products MUST follow the Tokopedia/Shopee pattern:
|
||||
|
||||
@@ -392,11 +392,11 @@ export default function Orders() {
|
||||
</div>
|
||||
|
||||
{/* Desktop: Table */}
|
||||
<div className="hidden md:block rounded-lg border border-border bg-card overflow-auto">
|
||||
<div className="hidden md:block rounded-lg border overflow-hidden">
|
||||
<table className="min-w-[800px] w-full text-sm">
|
||||
<thead className="border-b">
|
||||
<tr className="text-left">
|
||||
<th className="px-3 py-2 w-12">
|
||||
<thead className="bg-muted/50">
|
||||
<tr className="border-b">
|
||||
<th className="w-12 p-3">
|
||||
<Checkbox
|
||||
checked={allSelected}
|
||||
onCheckedChange={toggleAll}
|
||||
@@ -404,39 +404,39 @@ export default function Orders() {
|
||||
className={someSelected ? 'data-[state=checked]:bg-gray-400' : ''}
|
||||
/>
|
||||
</th>
|
||||
<th className="px-3 py-2">{__('Order')}</th>
|
||||
<th className="px-3 py-2">{__('Date')}</th>
|
||||
<th className="px-3 py-2">{__('Customer')}</th>
|
||||
<th className="px-3 py-2">{__('Items')}</th>
|
||||
<th className="px-3 py-2">{__('Status')}</th>
|
||||
<th className="px-3 py-2 text-right">{__('Total')}</th>
|
||||
<th className="px-3 py-2 text-center">{__('Actions')}</th>
|
||||
<th className="text-left p-3 font-medium">{__('Order')}</th>
|
||||
<th className="text-left p-3 font-medium">{__('Date')}</th>
|
||||
<th className="text-left p-3 font-medium">{__('Customer')}</th>
|
||||
<th className="text-left p-3 font-medium">{__('Items')}</th>
|
||||
<th className="text-left p-3 font-medium">{__('Status')}</th>
|
||||
<th className="text-right p-3 font-medium">{__('Total')}</th>
|
||||
<th className="text-center p-3 font-medium">{__('Actions')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredOrders.map((row) => (
|
||||
<tr key={row.id} className="border-b last:border-0">
|
||||
<td className="px-3 py-2">
|
||||
<tr key={row.id} className="border-b hover:bg-muted/30 last:border-0">
|
||||
<td className="p-3">
|
||||
<Checkbox
|
||||
checked={selectedIds.includes(row.id)}
|
||||
onCheckedChange={() => toggleRow(row.id)}
|
||||
aria-label={__('Select order')}
|
||||
/>
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<td className="p-3">
|
||||
<Link className="underline underline-offset-2" to={`/orders/${row.id}`}>#{row.number}</Link>
|
||||
</td>
|
||||
<td className="px-3 py-2 min-w-32">
|
||||
<td className="p-3 min-w-32">
|
||||
<span title={row.date ?? ""}>
|
||||
{formatRelativeOrDate(row.date_ts)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-2">{row.customer || '—'}</td>
|
||||
<td className="px-3 py-2">
|
||||
<td className="p-3">{row.customer || '—'}</td>
|
||||
<td className="p-3">
|
||||
<ItemsCell row={row} />
|
||||
</td>
|
||||
<td className="px-3 py-2"><StatusBadge value={row.status} /></td>
|
||||
<td className="px-3 py-2 text-right tabular-nums font-mono">
|
||||
<td className="p-3"><StatusBadge value={row.status} /></td>
|
||||
<td className="p-3 text-right tabular-nums font-mono">
|
||||
{formatMoney(row.total, {
|
||||
currency: row.currency || store.currency,
|
||||
symbol: row.currency_symbol || store.symbol,
|
||||
@@ -446,7 +446,7 @@ export default function Orders() {
|
||||
decimals: store.decimals,
|
||||
})}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-center space-x-2">
|
||||
<td className="p-3 text-center space-x-2">
|
||||
<Link className="btn text-sm underline underline-offset-2" to={`/orders/${row.id}`}>{__('Open')}</Link>
|
||||
<Link className="btn text-sm underline underline-offset-2" to={`/orders/${row.id}/edit`}>{__('Edit')}</Link>
|
||||
</td>
|
||||
@@ -455,7 +455,7 @@ export default function Orders() {
|
||||
|
||||
{filteredOrders.length === 0 && (
|
||||
<tr>
|
||||
<td className="px-3 py-12 text-center" colSpan={8}>
|
||||
<td className="p-8 text-center text-muted-foreground" colSpan={8}>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<PackageOpen className="w-8 h-8 opacity-40" />
|
||||
<div className="font-medium">{__('No orders found')}</div>
|
||||
|
||||
Reference in New Issue
Block a user