Add frontend/modules/loyalty with types, API client, design system, feature pages and thin App Router routes under app/loyalty/. BFF proxy at app/api/loyalty/. Include loyalty frontend docs. Co-authored-by: Cursor <cursoragent@cursor.com>
83 lines
2.5 KiB
TypeScript
83 lines
2.5 KiB
TypeScript
"use client";
|
|
|
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import { toast } from "sonner";
|
|
import { beautyBusinessApi, type Appointment } from "@/modules/beauty/services/beauty-business-api";
|
|
import { useTenantId } from "@/hooks/useTenantId";
|
|
import { Button } from "@/components/ds";
|
|
import { AppointmentCard } from "@/modules/beauty/design-system";
|
|
|
|
type ActionKind = "confirm" | "check-in" | "complete" | "cancel" | "no-show";
|
|
|
|
export function ReceptionAppointmentRow({
|
|
appointment,
|
|
staffName,
|
|
serviceName,
|
|
actions,
|
|
queryKey,
|
|
}: {
|
|
appointment: Appointment;
|
|
staffName?: string;
|
|
serviceName?: string;
|
|
actions: ActionKind[];
|
|
queryKey: string[];
|
|
}) {
|
|
const { tenantId } = useTenantId();
|
|
const qc = useQueryClient();
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: (kind: ActionKind) => {
|
|
const body = { version: appointment.version };
|
|
switch (kind) {
|
|
case "confirm":
|
|
return beautyBusinessApi.appointments.confirm(tenantId!, appointment.id, body);
|
|
case "check-in":
|
|
return beautyBusinessApi.appointments.checkIn(tenantId!, appointment.id, body);
|
|
case "complete":
|
|
return beautyBusinessApi.appointments.complete(tenantId!, appointment.id, body);
|
|
case "cancel":
|
|
return beautyBusinessApi.appointments.cancel(tenantId!, appointment.id, body);
|
|
case "no-show":
|
|
return beautyBusinessApi.appointments.noShow(tenantId!, appointment.id, body);
|
|
}
|
|
},
|
|
onSuccess: async () => {
|
|
toast.success("انجام شد");
|
|
await qc.invalidateQueries({ queryKey });
|
|
},
|
|
onError: (e: Error) => toast.error(e.message),
|
|
});
|
|
|
|
const labels: Record<ActionKind, string> = {
|
|
confirm: "تأیید",
|
|
"check-in": "پذیرش",
|
|
complete: "تکمیل",
|
|
cancel: "لغو",
|
|
"no-show": "عدم حضور",
|
|
};
|
|
|
|
return (
|
|
<AppointmentCard
|
|
appointment={appointment}
|
|
staffName={staffName}
|
|
serviceName={serviceName}
|
|
actions={
|
|
<div className="flex flex-wrap gap-1">
|
|
{actions.map((a) => (
|
|
<Button
|
|
key={a}
|
|
type="button"
|
|
size="sm"
|
|
variant={a === "cancel" ? "outline" : "secondary"}
|
|
disabled={mutation.isPending}
|
|
onClick={() => mutation.mutate(a)}
|
|
>
|
|
{labels[a]}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
}
|
|
/>
|
|
);
|
|
}
|