feat: Modern mobile-first Orders UI redesign

Implemented complete mobile-first redesign of Orders page with app-like UX.

Problem:
- Desktop table layout on mobile (cramped, not touch-friendly)
- "New order" button redundant with FAB
- Desktop-style filters not mobile-optimized
- Checkbox selection too small for touch
- Old-school pagination

Solution: Full Modern Mobile-First Redesign

New Components Created:

1. OrderCard.tsx
   - Card-based layout for mobile
   - Touch-friendly tap targets
   - Order number + status badge
   - Customer name
   - Items brief
   - Date + total amount
   - Chevron indicator
   - Checkbox for selection
   - Tap card → navigate to detail

2. FilterBottomSheet.tsx
   - Modern bottom sheet UI
   - Drag handle
   - Status filter
   - Date range picker
   - Sort order
   - Active filter count badge
   - Reset + Apply buttons
   - Smooth slide-in animation

3. SearchBar.tsx
   - Search input with icon
   - Filter button with badge
   - Clean, modern design
   - Touch-optimized

Orders Page Redesign:

Mobile Layout:
┌─────────────────────────────────┐
│ [🔍 Search orders...]      [⚙] │ ← Search + Filter
├─────────────────────────────────┤
│ ┌─────────────────────────────┐ │
│ │ 📦 #337              💰      │ │ ← Order card
│ │ Processing                   │ │
│ │ Dwindi Ramadhana             │ │
│ │ 2 items · Product A, ...     │ │
│ │ 2 hours ago      Rp64.500    │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ 📦 #336              ✓       │ │
│ │ Completed                    │ │
│ │ John Doe                     │ │
│ │ 1 item · Product B           │ │
│ │ Yesterday        Rp125.000   │ │
│ └─────────────────────────────┘ │
├─────────────────────────────────┤
│ [Previous]  Page 1  [Next]      │
├─────────────────────────────────┤
│ Dashboard Orders Products ...   │
└─────────────────────────────────┘
                            ( + ) ← FAB

Desktop Layout:
- Keeps table view (familiar for desktop users)
- Inline filters at top
- All existing functionality preserved

Features Implemented:

 Card-based mobile layout
 Search orders (by number, customer, status)
 Bottom sheet filters
 Active filter count badge
 Pull-to-refresh indicator
 Bulk selection with sticky action bar
 Touch-optimized tap targets
 Smooth animations
 Empty states with helpful messages
 Responsive: cards on mobile, table on desktop
 FAB for new order (removed redundant button)
 Clean, modern, app-like UX

Mobile-Specific Improvements:

1. No "New order" button at top (use FAB)
2. Search bar replaces desktop filters
3. Filter icon opens bottom sheet
4. Cards instead of cramped table
5. Larger touch targets
6. Sticky bulk action bar
7. Pull-to-refresh support
8. Better empty states

Desktop Unchanged:
- Table layout preserved
- Inline filters
- All existing features work

Result:
 Modern, app-like mobile UI
 Touch-friendly interactions
 Clean, uncluttered design
 Fast, responsive
 Desktop functionality preserved
 Consistent with mobile-first vision

Files Created:
- routes/Orders/components/OrderCard.tsx
- routes/Orders/components/FilterBottomSheet.tsx
- routes/Orders/components/SearchBar.tsx

Files Modified:
- routes/Orders/index.tsx (complete redesign)

The Orders page is now a modern, mobile-first experience! 🎯
This commit is contained in:
dwindown
2025-11-08 13:16:19 +07:00
parent b93a873765
commit e0a236fc64
8 changed files with 1059 additions and 220 deletions

View File

@@ -0,0 +1,102 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { ChevronRight, Package } from 'lucide-react';
import { __ } from '@/lib/i18n';
import { formatMoney } from '@/lib/currency';
import { formatRelativeOrDate } from '@/lib/dates';
import { Checkbox } from '@/components/ui/checkbox';
const statusStyle: Record<string, string> = {
pending: 'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300',
processing: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300',
completed: 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-300',
'on-hold': 'bg-slate-200 text-slate-800 dark:bg-slate-800 dark:text-slate-300',
cancelled: 'bg-zinc-200 text-zinc-800 dark:bg-zinc-800 dark:text-zinc-300',
refunded: 'bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300',
failed: 'bg-rose-100 text-rose-800 dark:bg-rose-900/30 dark:text-rose-300',
};
interface OrderCardProps {
order: any;
selected?: boolean;
onSelect?: (id: number) => void;
currencyConfig: any;
}
export function OrderCard({ order, selected, onSelect, currencyConfig }: OrderCardProps) {
const statusClass = statusStyle[order.status?.toLowerCase()] || 'bg-slate-100 text-slate-800';
return (
<Link
to={`/orders/${order.id}`}
className="block bg-card border border-border rounded-lg p-4 hover:bg-accent/50 transition-colors active:scale-[0.98] active:transition-transform"
>
<div className="flex items-start gap-3">
{/* Checkbox */}
{onSelect && (
<div
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onSelect(order.id);
}}
className="pt-1"
>
<Checkbox
checked={selected}
aria-label={__('Select order')}
/>
</div>
)}
{/* Icon */}
<div className="flex-shrink-0 w-10 h-10 rounded-lg bg-primary/10 text-primary flex items-center justify-center">
<Package className="w-5 h-5" />
</div>
{/* Content */}
<div className="flex-1 min-w-0">
{/* Order Number & Status */}
<div className="flex items-center justify-between gap-2 mb-1">
<h3 className="font-semibold text-base">#{order.number}</h3>
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium capitalize ${statusClass}`}>
{order.status || 'unknown'}
</span>
</div>
{/* Customer */}
<div className="text-sm text-muted-foreground mb-2">
{order.customer || __('Guest')}
</div>
{/* Items Brief */}
{order.items_brief && (
<div className="text-sm text-muted-foreground mb-2 truncate">
{order.items_count} {order.items_count === 1 ? __('item') : __('items')} · {order.items_brief}
</div>
)}
{/* Date & Total */}
<div className="flex items-center justify-between gap-2 mt-2">
<span className="text-xs text-muted-foreground">
{formatRelativeOrDate(order.date_ts)}
</span>
<span className="font-semibold text-base tabular-nums">
{formatMoney(order.total, {
currency: order.currency || currencyConfig.currency,
symbol: order.currency_symbol || currencyConfig.symbol,
thousandSep: currencyConfig.thousand_sep,
decimalSep: currencyConfig.decimal_sep,
position: currencyConfig.position,
decimals: currencyConfig.decimals,
})}
</span>
</div>
</div>
{/* Chevron */}
<ChevronRight className="w-5 h-5 text-muted-foreground flex-shrink-0 mt-2" />
</div>
</Link>
);
}