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:
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import OrderForm from '@/routes/Orders/partials/OrderForm';
|
||||
@@ -17,6 +17,7 @@ export default function OrdersEdit() {
|
||||
const nav = useNavigate();
|
||||
const qc = useQueryClient();
|
||||
const { setPageHeader, clearPageHeader } = usePageHeader();
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
|
||||
// Hide FAB on edit page
|
||||
useFABConfig('none');
|
||||
@@ -62,19 +63,28 @@ export default function OrdersEdit() {
|
||||
|
||||
const order = orderQ.data || {};
|
||||
|
||||
// Set page header with back button
|
||||
// Set page header with back button and save button
|
||||
useEffect(() => {
|
||||
const backButton = (
|
||||
<Button size="sm" variant="ghost" onClick={() => nav(`/orders/${orderId}`)}>
|
||||
{__('Back')}
|
||||
</Button>
|
||||
const actions = (
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" variant="ghost" onClick={() => nav(`/orders/${orderId}`)}>
|
||||
{__('Back')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => formRef.current?.requestSubmit()}
|
||||
disabled={upd.isPending}
|
||||
>
|
||||
{upd.isPending ? __('Saving...') : __('Save')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
const title = order.number
|
||||
? sprintf(__('Edit Order #%s'), order.number)
|
||||
: __('Edit Order');
|
||||
setPageHeader(title, backButton);
|
||||
setPageHeader(title, actions);
|
||||
return () => clearPageHeader();
|
||||
}, [order.number, orderId, setPageHeader, clearPageHeader, nav]);
|
||||
}, [order.number, orderId, upd.isPending, setPageHeader, clearPageHeader, nav]);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@@ -91,6 +101,8 @@ export default function OrdersEdit() {
|
||||
shippings={(shippingsQ.data || [])}
|
||||
itemsEditable={['pending', 'on-hold', 'failed', 'draft'].includes(order.status)}
|
||||
showCoupons
|
||||
formRef={formRef}
|
||||
hideSubmitButton={true}
|
||||
onSubmit={(form) => {
|
||||
const payload = { ...form } as any;
|
||||
upd.mutate(payload);
|
||||
|
||||
Reference in New Issue
Block a user