TorbatYar/frontend/modules/beauty/pages/reception.tsx
Mortezakoohjani d579d0b142 feat(loyalty): add Loyalty Platform Frontend module
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>
2026-07-27 10:50:55 +03:30

213 lines
7.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import Link from "next/link";
import { useQuery } from "@tanstack/react-query";
import { PageHeader, EmptyState, Badge, Button } from "@/components/ds";
import { beautyBusinessApi } from "@/modules/beauty/services/beauty-business-api";
import { useTenantId } from "@/hooks/useTenantId";
import { useBeautyLookups } from "@/modules/beauty/hooks/useBeautyLookups";
import { AppointmentCard, QueueRow } from "@/modules/beauty/design-system";
import { ReceptionAppointmentRow } from "@/modules/beauty/components/ReceptionAppointmentRow";
import {
AggregateStatsRow,
AnalyticsPage,
BeautyPageError,
BeautyPageLoader,
} from "./shared";
export function ReceptionDashboard() {
const { tenantId } = useTenantId();
const apptsQ = useQuery({
queryKey: ["beauty", tenantId, "reception-appts"],
queryFn: () => beautyBusinessApi.appointments.list(tenantId!),
enabled: !!tenantId,
});
const waitQ = useQuery({
queryKey: ["beauty", tenantId, "reception-waiting"],
queryFn: () => beautyBusinessApi.waitingList.list(tenantId!),
enabled: !!tenantId,
});
if (!tenantId || apptsQ.isLoading) return <BeautyPageLoader />;
if (apptsQ.error) return <BeautyPageError error={apptsQ.error} onRetry={() => apptsQ.refetch()} />;
return (
<div>
<PageHeader title="داشبورد پذیرش" description="صف، نوبت‌ها و لیست انتظار." />
<AggregateStatsRow
stats={[
{ label: "نوبت‌ها", value: apptsQ.data?.length ?? 0 },
{ label: "لیست انتظار", value: waitQ.data?.length ?? 0 },
{
label: "پذیرش‌شده",
value: (apptsQ.data ?? []).filter((a) => a.status === "checked_in").length,
},
]}
/>
</div>
);
}
export function ReceptionCalendar() {
const { tenantId } = useTenantId();
const { staffName, serviceName } = useBeautyLookups();
const apptsQ = useQuery({
queryKey: ["beauty", tenantId, "reception-calendar"],
queryFn: () => beautyBusinessApi.appointments.list(tenantId!),
enabled: !!tenantId,
});
if (!tenantId || apptsQ.isLoading) return <BeautyPageLoader />;
if (apptsQ.error) return <BeautyPageError error={apptsQ.error} onRetry={() => apptsQ.refetch()} />;
return (
<div>
<PageHeader title="تقویم پذیرش" description="نوبت‌های روز." />
<div className="mt-6 space-y-3">
{(apptsQ.data ?? []).map((a) => (
<AppointmentCard
key={a.id}
appointment={a}
staffName={staffName(a.staff_ref)}
serviceName={serviceName(a.service_ref)}
/>
))}
</div>
{(apptsQ.data ?? []).length === 0 ? <EmptyState title="نوبتی نیست" /> : null}
</div>
);
}
export function ReceptionWalkIn() {
const { tenantId } = useTenantId();
const { customers, services } = useBeautyLookups();
return (
<div>
<PageHeader title="مراجعه حضوری" description="ثبت walk-in در waiting-list." />
<p className="text-sm text-[var(--muted)]">
مشتریان: {customers.length} · خدمات: {services.length}
</p>
<Link href="/beauty/booking/waiting-list" className="mt-4 inline-block">
<Button>لیست انتظار</Button>
</Link>
</div>
);
}
export function ReceptionCheckIn() {
const { tenantId } = useTenantId();
const { staffName } = useBeautyLookups();
const apptsQ = useQuery({
queryKey: ["beauty", tenantId, "reception-checkin"],
queryFn: () => beautyBusinessApi.appointments.list(tenantId!),
enabled: !!tenantId,
select: (rows) => rows.filter((a) => a.status === "confirmed" || a.status === "requested"),
});
if (!tenantId || apptsQ.isLoading) return <BeautyPageLoader />;
if (apptsQ.error) return <BeautyPageError error={apptsQ.error} onRetry={() => apptsQ.refetch()} />;
return (
<div>
<PageHeader title="پذیرش" description="نوبت‌های آماده check-in." />
<div className="mt-6 space-y-3">
{(apptsQ.data ?? []).map((a) => (
<ReceptionAppointmentRow
key={a.id}
appointment={a}
staffName={staffName(a.staff_ref)}
actions={a.status === "requested" ? ["confirm", "cancel"] : ["check-in", "cancel", "no-show"]}
queryKey={["beauty", tenantId, "reception-checkin"]}
/>
))}
</div>
{(apptsQ.data ?? []).length === 0 ? <EmptyState title="نوبتی برای پذیرش نیست" /> : null}
</div>
);
}
export function ReceptionCheckOut() {
const { tenantId } = useTenantId();
const { staffName, serviceName } = useBeautyLookups();
const apptsQ = useQuery({
queryKey: ["beauty", tenantId, "reception-checkout"],
queryFn: () => beautyBusinessApi.appointments.list(tenantId!),
enabled: !!tenantId,
select: (rows) => rows.filter((a) => a.status === "in_progress" || a.status === "checked_in"),
});
if (!tenantId || apptsQ.isLoading) return <BeautyPageLoader />;
if (apptsQ.error) return <BeautyPageError error={apptsQ.error} onRetry={() => apptsQ.refetch()} />;
return (
<div>
<PageHeader title="ترخیص" description="نوبت‌های در حال انجام." />
<div className="mt-6 space-y-3">
{(apptsQ.data ?? []).map((a) => (
<ReceptionAppointmentRow
key={a.id}
appointment={a}
staffName={staffName(a.staff_ref)}
serviceName={serviceName(a.service_ref)}
actions={["complete", "cancel"]}
queryKey={["beauty", tenantId, "reception-checkout"]}
/>
))}
</div>
{(apptsQ.data ?? []).length === 0 ? <EmptyState title="موردی برای ترخیص نیست" /> : null}
</div>
);
}
export function ReceptionQueue() {
const { tenantId } = useTenantId();
const { customerName, serviceName } = useBeautyLookups();
const waitQ = useQuery({
queryKey: ["beauty", tenantId, "reception-queue"],
queryFn: () => beautyBusinessApi.waitingList.list(tenantId!),
enabled: !!tenantId,
});
if (!tenantId || waitQ.isLoading) return <BeautyPageLoader />;
if (waitQ.error) return <BeautyPageError error={waitQ.error} onRetry={() => waitQ.refetch()} />;
return (
<div>
<PageHeader title="صف انتظار" description="waiting-list API." />
<div className="mt-6 space-y-3">
{(waitQ.data ?? []).map((w, i) => (
<QueueRow
key={w.id}
position={i + 1}
title={customerName(w.customer_ref)}
subtitle={serviceName(w.service_ref)}
status="pending"
/>
))}
</div>
{(waitQ.data ?? []).length === 0 ? <EmptyState title="صف خالی است" /> : null}
</div>
);
}
export function ReceptionPayments() {
return (
<div>
<PageHeader title="پرداخت" description="ثبت پرداخت از طریق یکپارچگی حسابداری." />
<EmptyState
title="پرداخت از ماژول حسابداری"
action={
<Link href="/accounting">
<Button variant="outline">رفتن به حسابداری</Button>
</Link>
}
/>
</div>
);
}
export function ReceptionReports() {
return <AnalyticsPage title="گزارش پذیرش" />;
}