feat: OrderCard redesign and CRUD header improvements
Implemented three major improvements based on user feedback. 1. OrderCard Redesign - Order ID Badge with Status Colors Problem: Icon wasted space, status badge redundant Solution: Replace icon with Order ID badge using status colors New Design: ┌─────────────────────────────────┐ │ ☐ [#337] Nov 04, 2025, 11:44 PM│ ← Order ID with status color │ Dwindi Ramadhana →│ ← Customer (bold) │ 1 item · Test Digital │ ← Items │ Rp64.500 │ ← Total (large, primary) └─────────────────────────────────┘ Status Colors: - Completed: Green background - Processing: Blue background - Pending: Amber background - Failed: Red background - Cancelled: Gray background - Refunded: Purple background - On-hold: Slate background Changes: - Removed Package icon - Order ID badge: w-16 h-16, rounded-xl, status color bg - Order ID: font-bold, centered in badge - Removed status badge from bottom - Customer name promoted to h3 (more prominent) - Total: text-lg, text-primary (stands out) - Cleaner, more modern look Inspiration: Uber, DoorDash, Airbnb order cards Result: More efficient use of space, status visible at a glance! 2. CRUD Header Improvements - Back Button in Contextual Header Problem: Inline headers on New/Edit pages, no back button in header Solution: Add back button to contextual header, remove inline headers New Order: ┌─────────────────────────────────┐ │ [Back] New Order │ ← Contextual header ├─────────────────────────────────┤ │ Order form... │ └─────────────────────────────────┘ Edit Order: ┌─────────────────────────────────┐ │ [Back] Edit Order #337 │ ← Contextual header ├─────────────────────────────────┤ │ Order form... │ └─────────────────────────────────┘ Changes: - Added Back button to contextual header (ghost variant) - Removed inline page headers - Cleaner, more consistent UI - Back button always visible (no scroll needed) Result: Better UX, consistent with mobile patterns! 3. FAB Visibility Fix - Hide on New/Edit Pages Problem: FAB visible on Edit page, causing confusion Solution: Hide FAB on New/Edit pages using useFABConfig("none") Changes: - New.tsx: Added useFABConfig("none") - Edit.tsx: Added useFABConfig("none") - FAB only visible on Orders list page Result: No confusion, FAB only where it makes sense! Files Modified: - routes/Orders/components/OrderCard.tsx - routes/Orders/New.tsx - routes/Orders/Edit.tsx Summary: ✅ OrderCard: Order ID badge with status colors ✅ CRUD Headers: Back button in contextual header ✅ FAB: Hidden on New/Edit pages ✅ Cleaner, more modern, more intuitive! 🎯
This commit is contained in:
@@ -50,7 +50,7 @@ export default function DateRange({ value, onChange }: Props) {
|
|||||||
}, [preset, start, end]);
|
}, [preset, start, end]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 w-full">
|
<div className="flex flex-col lg:flex-row gap-2 w-full">
|
||||||
<Select value={preset} onValueChange={(v) => setPreset(v)}>
|
<Select value={preset} onValueChange={(v) => setPreset(v)}>
|
||||||
<SelectTrigger className="w-full">
|
<SelectTrigger className="w-full">
|
||||||
<SelectValue placeholder={__("Last 7 days")} />
|
<SelectValue placeholder={__("Last 7 days")} />
|
||||||
@@ -66,10 +66,10 @@ export default function DateRange({ value, onChange }: Props) {
|
|||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
{preset === "custom" && (
|
{preset === "custom" && (
|
||||||
<div className="flex flex-col gap-2 w-full">
|
<div className="flex flex-col lg:flex-row gap-2 w-full">
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
className="w-full border border-input rounded-md px-3 py-2 text-sm bg-background ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&::-webkit-calendar-picker-indicator]:cursor-pointer"
|
className="w-full border !bg-transparent !border-input !rounded-md !shadow-sm px-3 py-2 text-sm bg-background ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&::-webkit-calendar-picker-indicator]:cursor-pointer"
|
||||||
style={{ WebkitAppearance: 'none', MozAppearance: 'textfield' } as any}
|
style={{ WebkitAppearance: 'none', MozAppearance: 'textfield' } as any}
|
||||||
value={start || ""}
|
value={start || ""}
|
||||||
onChange={(e) => setStart(e.target.value || undefined)}
|
onChange={(e) => setStart(e.target.value || undefined)}
|
||||||
@@ -77,7 +77,7 @@ export default function DateRange({ value, onChange }: Props) {
|
|||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
className="w-full border border-input rounded-md px-3 py-2 text-sm bg-background ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&::-webkit-calendar-picker-indicator]:cursor-pointer"
|
className="w-full border !bg-transparent !border-input !rounded-md !shadow-sm px-3 py-2 text-sm bg-background ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&::-webkit-calendar-picker-indicator]:cursor-pointer"
|
||||||
style={{ WebkitAppearance: 'none', MozAppearance: 'textfield' } as any}
|
style={{ WebkitAppearance: 'none', MozAppearance: 'textfield' } as any}
|
||||||
value={end || ""}
|
value={end || ""}
|
||||||
onChange={(e) => setEnd(e.target.value || undefined)}
|
onChange={(e) => setEnd(e.target.value || undefined)}
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import { OrdersApi } from '@/lib/api';
|
|||||||
import { showErrorToast, showSuccessToast, getPageLoadErrorMessage } from '@/lib/errorHandling';
|
import { showErrorToast, showSuccessToast, getPageLoadErrorMessage } from '@/lib/errorHandling';
|
||||||
import { ErrorCard } from '@/components/ErrorCard';
|
import { ErrorCard } from '@/components/ErrorCard';
|
||||||
import { LoadingState } from '@/components/LoadingState';
|
import { LoadingState } from '@/components/LoadingState';
|
||||||
import { ArrowLeft } from 'lucide-react';
|
|
||||||
import { __, sprintf } from '@/lib/i18n';
|
import { __, sprintf } from '@/lib/i18n';
|
||||||
import { usePageHeader } from '@/contexts/PageHeaderContext';
|
import { usePageHeader } from '@/contexts/PageHeaderContext';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useFABConfig } from '@/hooks/useFABConfig';
|
||||||
|
|
||||||
export default function OrdersEdit() {
|
export default function OrdersEdit() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@@ -17,6 +18,9 @@ export default function OrdersEdit() {
|
|||||||
const qc = useQueryClient();
|
const qc = useQueryClient();
|
||||||
const { setPageHeader, clearPageHeader } = usePageHeader();
|
const { setPageHeader, clearPageHeader } = usePageHeader();
|
||||||
|
|
||||||
|
// Hide FAB on edit page
|
||||||
|
useFABConfig('none');
|
||||||
|
|
||||||
const countriesQ = useQuery({ queryKey: ['countries'], queryFn: OrdersApi.countries });
|
const countriesQ = useQuery({ queryKey: ['countries'], queryFn: OrdersApi.countries });
|
||||||
const paymentsQ = useQuery({ queryKey: ['payments'], queryFn: OrdersApi.payments });
|
const paymentsQ = useQuery({ queryKey: ['payments'], queryFn: OrdersApi.payments });
|
||||||
const shippingsQ = useQuery({ queryKey: ['shippings'], queryFn: OrdersApi.shippings });
|
const shippingsQ = useQuery({ queryKey: ['shippings'], queryFn: OrdersApi.shippings });
|
||||||
@@ -58,29 +62,22 @@ export default function OrdersEdit() {
|
|||||||
|
|
||||||
const order = orderQ.data || {};
|
const order = orderQ.data || {};
|
||||||
|
|
||||||
// Set page header
|
// Set page header with back button
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (order.number) {
|
const backButton = (
|
||||||
setPageHeader(sprintf(__('Edit Order #%s'), order.number));
|
<Button size="sm" variant="ghost" onClick={() => nav(`/orders/${orderId}`)}>
|
||||||
} else {
|
{__('Back')}
|
||||||
setPageHeader(__('Edit Order'));
|
</Button>
|
||||||
}
|
);
|
||||||
|
const title = order.number
|
||||||
|
? sprintf(__('Edit Order #%s'), order.number)
|
||||||
|
: __('Edit Order');
|
||||||
|
setPageHeader(title, backButton);
|
||||||
return () => clearPageHeader();
|
return () => clearPageHeader();
|
||||||
}, [order.number, setPageHeader, clearPageHeader]);
|
}, [order.number, orderId, setPageHeader, clearPageHeader, nav]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
|
||||||
<button
|
|
||||||
className="border rounded-md px-3 py-2 text-sm flex items-center gap-2"
|
|
||||||
onClick={() => nav(`/orders/${orderId}`)}
|
|
||||||
>
|
|
||||||
<ArrowLeft className="w-4 h-4" /> {__('Back')}
|
|
||||||
</button>
|
|
||||||
<h2 className="text-lg font-semibold flex-1 min-w-[160px]">
|
|
||||||
{sprintf(__('Edit Order #%s'), orderId)}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<OrderForm
|
<OrderForm
|
||||||
mode="edit"
|
mode="edit"
|
||||||
|
|||||||
@@ -8,12 +8,16 @@ import { showErrorToast, showSuccessToast } from '@/lib/errorHandling';
|
|||||||
import { __, sprintf } from '@/lib/i18n';
|
import { __, sprintf } from '@/lib/i18n';
|
||||||
import { usePageHeader } from '@/contexts/PageHeaderContext';
|
import { usePageHeader } from '@/contexts/PageHeaderContext';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useFABConfig } from '@/hooks/useFABConfig';
|
||||||
|
|
||||||
export default function OrdersNew() {
|
export default function OrdersNew() {
|
||||||
const nav = useNavigate();
|
const nav = useNavigate();
|
||||||
const qc = useQueryClient();
|
const qc = useQueryClient();
|
||||||
const { setPageHeader, clearPageHeader } = usePageHeader();
|
const { setPageHeader, clearPageHeader } = usePageHeader();
|
||||||
|
|
||||||
|
// Hide FAB on new order page
|
||||||
|
useFABConfig('none');
|
||||||
|
|
||||||
// Countries from Woo (allowed + default + states)
|
// Countries from Woo (allowed + default + states)
|
||||||
const countriesQ = useQuery({ queryKey: ['countries'], queryFn: OrdersApi.countries });
|
const countriesQ = useQuery({ queryKey: ['countries'], queryFn: OrdersApi.countries });
|
||||||
const countriesData = React.useMemo(() => {
|
const countriesData = React.useMemo(() => {
|
||||||
@@ -40,20 +44,19 @@ export default function OrdersNew() {
|
|||||||
// Prefer global store currency injected by PHP
|
// Prefer global store currency injected by PHP
|
||||||
const { currency: storeCurrency, symbol: storeSymbol } = getStoreCurrency();
|
const { currency: storeCurrency, symbol: storeSymbol } = getStoreCurrency();
|
||||||
|
|
||||||
// Set page header
|
// Set page header with back button
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPageHeader(__('New Order'));
|
const backButton = (
|
||||||
|
<Button size="sm" variant="ghost" onClick={() => nav('/orders')}>
|
||||||
|
{__('Back')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
setPageHeader(__('New Order'), backButton);
|
||||||
return () => clearPageHeader();
|
return () => clearPageHeader();
|
||||||
}, [setPageHeader, clearPageHeader]);
|
}, [setPageHeader, clearPageHeader, nav]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h2 className="text-lg font-semibold">{__('New Order')}</h2>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<button className="border rounded-md px-3 py-2 text-sm" onClick={() => nav('/orders')}>{__('Cancel')}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<OrderForm
|
<OrderForm
|
||||||
mode="create"
|
mode="create"
|
||||||
|
|||||||
Reference in New Issue
Block a user