feat: Move action buttons to contextual headers for CRUD pages

Implemented proper contextual header pattern for all Order CRUD pages.

Problem:
- New/Edit pages had action buttons at bottom of form
- Detail page showed duplicate headers (contextual + inline)
- Not following mobile-first best practices

Solution: [Back] Page Title [Action]

1. Edit Order Page
   Header: [Back] Edit Order #337 [Save]

   Implementation:
   - Added formRef to trigger form submit from header
   - Save button in contextual header
   - Removed submit button from form bottom
   - Button shows loading state during save

   Changes:
   - Edit.tsx: Added formRef, updated header with Save button
   - OrderForm.tsx: Added formRef and hideSubmitButton props
   - Form submit triggered via formRef.current.requestSubmit()

2. New Order Page
   Header: [Back] New Order [Create]

   Implementation:
   - Added formRef to trigger form submit from header
   - Create button in contextual header
   - Removed submit button from form bottom
   - Button shows loading state during creation

   Changes:
   - New.tsx: Added formRef, updated header with Create button
   - Same OrderForm props as Edit page

3. Order Detail Page
   Header: (hidden)

   Implementation:
   - Cleared contextual header completely
   - Detail page has its own inline header with actions
   - Inline header: [Back] Order #337 [Print] [Invoice] [Label] [Edit]

   Changes:
   - Detail.tsx: clearPageHeader() in useEffect
   - No duplicate headers

OrderForm Component Updates:
- Added formRef prop (React.RefObject<HTMLFormElement>)
- Added hideSubmitButton prop (boolean)
- Form element accepts ref: <form ref={formRef}>
- Submit button conditionally rendered: {!hideSubmitButton && <Button...>}
- Backward compatible (both props optional)

Benefits:
 Consistent header pattern across all CRUD pages
 Action buttons always visible (sticky header)
 Better mobile UX (no scrolling to find buttons)
 Loading states in header buttons
 Clean, modern interface
 Follows industry standards (Gmail, Notion, Linear)

Files Modified:
- routes/Orders/New.tsx
- routes/Orders/Edit.tsx
- routes/Orders/Detail.tsx
- routes/Orders/partials/OrderForm.tsx

Result:
 New/Edit: Action buttons in contextual header
 Detail: No contextual header (has inline header)
 Professional, mobile-first UX! 🎯
This commit is contained in:
dwindown
2025-11-08 15:38:38 +07:00
parent 4e764f9368
commit 58d508eb4e
4 changed files with 53 additions and 47 deletions

View File

@@ -92,6 +92,8 @@ type Props = {
rightTop?: React.ReactNode;
itemsEditable?: boolean;
showCoupons?: boolean;
formRef?: React.RefObject<HTMLFormElement>;
hideSubmitButton?: boolean;
};
const STATUS_LIST = ['pending','processing','on-hold','completed','cancelled','refunded','failed'];
@@ -113,6 +115,8 @@ export default function OrderForm({
showCoupons = true,
currency,
currencySymbol,
formRef,
hideSubmitButton = false,
}: Props) {
const oneCountryOnly = countries.length === 1;
const firstCountry = countries[0]?.code || 'US';
@@ -346,7 +350,7 @@ export default function OrderForm({
}
return (
<form onSubmit={handleSubmit} className={cn('grid grid-cols-1 lg:grid-cols-3 gap-6', className)}>
<form ref={formRef} onSubmit={handleSubmit} className={cn('grid grid-cols-1 lg:grid-cols-3 gap-6', className)}>
{/* Left: Order details */}
<div className="lg:col-span-2 space-y-6">
{/* Items and Coupons */}
@@ -933,9 +937,11 @@ export default function OrderForm({
</div>
)}
<Button type="submit" disabled={submitting} className="w-full">
{submitting ? (mode === 'edit' ? __('Saving…') : __('Creating…')) : (mode === 'edit' ? __('Save changes') : __('Create order'))}
</Button>
{!hideSubmitButton && (
<Button type="submit" disabled={submitting} className="w-full">
{submitting ? (mode === 'edit' ? __('Saving…') : __('Creating…')) : (mode === 'edit' ? __('Save changes') : __('Create order'))}
</Button>
)}
</div>
</aside>
</form>