Add Reorder shortcut to finished orders on /my-orders
## Original task (Task #66) Let users quickly re-order a service from their order history. On every order card whose status is `completed` or `cancelled`, show a "Reorder" button (Arabic: "اطلب مجددًا") that opens the existing OrderServiceModal pre-filled with the original notes. Submitting creates a brand-new order via the existing endpoint; the original order stays unchanged. ## Implementation - `artifacts/tx-os/src/components/order-service-modal.tsx`: added an optional `initialNotes` prop and seeded the notes textarea with it (truncated to NOTES_MAX) whenever the modal opens. Existing services page callers are unaffected (prop defaults to undefined => empty notes). - `artifacts/tx-os/src/pages/my-orders.tsx`: - Imported the `RotateCcw` icon and `OrderServiceModal`. - Added a "Reorder" button (with icon) on `OrderCard` that shows only for finished orders, alongside the existing cancel/delete actions. - Hoisted reorder modal state to `MyOrdersPage` via a new `reorderTarget` state and an `onReorder` callback wired through `OrderCard`. - Render `OrderServiceModal` at the page level using the original order's service info and notes; closing/submitting clears state. - `artifacts/tx-os/src/locales/{en,ar}.json`: added `myOrders.reorder` ("Reorder" / "اطلب مجددًا"). ## Notes / non-deviations - Task description references `artifacts/teaboy-os/...`; the actual artifact in this repo is `artifacts/tx-os/`. Same files, same intent. - Submitting reuses `useCreateServiceOrder` so the original order is untouched and standard error/success toasts fire from the modal. ## Verification - `pnpm exec tsc --noEmit` shows no new errors in the touched files. - E2E test: created a fresh user with one completed and one cancelled order, logged in, opened /my-orders, confirmed the Reorder button appears with notes prefilled in the modal, edited the notes, submitted, and verified a new pending order with the edited notes appeared while the original order remained unchanged. Replit-Task-Id: 2140b360-9d2f-45d9-9ddc-267ea29f7d7c
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 24 KiB |
@@ -30,6 +30,7 @@ type Props = {
|
||||
imageUrl?: string | null;
|
||||
} | null;
|
||||
resolveImage: (url: string | null | undefined) => string | null;
|
||||
initialNotes?: string | null;
|
||||
};
|
||||
|
||||
export function OrderServiceModal({
|
||||
@@ -37,6 +38,7 @@ export function OrderServiceModal({
|
||||
onOpenChange,
|
||||
service,
|
||||
resolveImage,
|
||||
initialNotes,
|
||||
}: Props) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const lang = i18n.language;
|
||||
@@ -49,10 +51,10 @@ export function OrderServiceModal({
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setNotes("");
|
||||
setNotes((initialNotes ?? "").slice(0, NOTES_MAX));
|
||||
setImgErrored(false);
|
||||
}
|
||||
}, [open, service?.id]);
|
||||
}, [open, service?.id, initialNotes]);
|
||||
|
||||
if (!service) return null;
|
||||
const name = lang === "ar" ? service.nameAr : service.nameEn;
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
"cancelFailed": "تعذّر إلغاء الطلب",
|
||||
"restoreFailed": "تعذّر استعادة الطلب",
|
||||
"delete": "حذف",
|
||||
"reorder": "اطلب مجددًا",
|
||||
"deleteConfirmTitle": "حذف هذا الطلب؟",
|
||||
"deleteConfirmBody": "سيتم حذف الطلب نهائياً من سجلّك.",
|
||||
"deleteConfirm": "نعم، احذف",
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
"cancelFailed": "Could not cancel the order",
|
||||
"restoreFailed": "Could not restore the order",
|
||||
"delete": "Delete",
|
||||
"reorder": "Reorder",
|
||||
"deleteConfirmTitle": "Delete this order?",
|
||||
"deleteConfirmBody": "This will permanently remove the order from your history.",
|
||||
"deleteConfirm": "Yes, delete",
|
||||
|
||||
@@ -21,8 +21,10 @@ import {
|
||||
PackageCheck,
|
||||
XCircle,
|
||||
Trash2,
|
||||
RotateCcw,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { OrderServiceModal } from "@/components/order-service-modal";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -156,9 +158,11 @@ function OrderImage({
|
||||
function OrderCard({
|
||||
order,
|
||||
onDelete,
|
||||
onReorder,
|
||||
}: {
|
||||
order: ServiceOrder;
|
||||
onDelete: (id: number) => void;
|
||||
onReorder: (order: ServiceOrder) => void;
|
||||
}) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -172,7 +176,9 @@ function OrderCard({
|
||||
const img = resolveServiceImageUrl(order.service.imageUrl);
|
||||
const cancellable =
|
||||
order.status === "pending" || order.status === "received";
|
||||
const deletable = isFinished(order.status);
|
||||
const finished = isFinished(order.status);
|
||||
const deletable = finished;
|
||||
const reorderable = finished;
|
||||
|
||||
const handleCancel = () => {
|
||||
const previousStatus = order.status;
|
||||
@@ -258,8 +264,19 @@ function OrderCard({
|
||||
|
||||
<StatusTimeline status={order.status} />
|
||||
|
||||
{(cancellable || deletable) && (
|
||||
{(cancellable || finished) && (
|
||||
<div className="flex justify-end gap-1">
|
||||
{reorderable && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-primary hover:text-primary hover:bg-primary/10 gap-1.5"
|
||||
onClick={() => onReorder(order)}
|
||||
>
|
||||
<RotateCcw size={14} />
|
||||
{t("myOrders.reorder")}
|
||||
</Button>
|
||||
)}
|
||||
{cancellable && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -357,6 +374,15 @@ export default function MyOrdersPage() {
|
||||
const [pendingDeleteIds, setPendingDeleteIds] = useState<Set<number>>(
|
||||
() => new Set(),
|
||||
);
|
||||
const [reorderTarget, setReorderTarget] = useState<{
|
||||
service: {
|
||||
id: number;
|
||||
nameAr: string;
|
||||
nameEn: string;
|
||||
imageUrl?: string | null;
|
||||
};
|
||||
notes: string | null;
|
||||
} | null>(null);
|
||||
|
||||
// Track scheduled timers and queued IDs so we can flush on unmount.
|
||||
const timersRef = useRef<Map<number, ReturnType<typeof setTimeout>>>(
|
||||
@@ -553,6 +579,17 @@ export default function MyOrdersPage() {
|
||||
key={o.id}
|
||||
order={o}
|
||||
onDelete={(id) => scheduleDelete([id])}
|
||||
onReorder={(order) =>
|
||||
setReorderTarget({
|
||||
service: {
|
||||
id: order.service.id,
|
||||
nameAr: order.service.nameAr,
|
||||
nameEn: order.service.nameEn,
|
||||
imageUrl: order.service.imageUrl,
|
||||
},
|
||||
notes: order.notes ?? null,
|
||||
})
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -585,6 +622,16 @@ export default function MyOrdersPage() {
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<OrderServiceModal
|
||||
open={reorderTarget !== null}
|
||||
onOpenChange={(o) => {
|
||||
if (!o) setReorderTarget(null);
|
||||
}}
|
||||
service={reorderTarget?.service ?? null}
|
||||
resolveImage={resolveServiceImageUrl}
|
||||
initialNotes={reorderTarget?.notes ?? null}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user