TorbatYar/frontend/modules/beauty/pages/reception.tsx
Mortezakoohjani 6f4a484051 Migrate Beauty, Healthcare, and Accounting frontend to modular src/modules architecture.
Move business logic out of App Router into module packages, add boundary validation scripts, and keep all routes as thin re-exports without changing URLs or API behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 22:28:27 +03:30

198 lines
6.9 KiB
TypeScript
Raw 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 {
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) => (
<AppointmentCard key={a.id} appointment={a} staffName={staffName(a.staff_ref)} href="/beauty/appointments" />
))}
</div>
{(apptsQ.data ?? []).length === 0 ? <EmptyState title="نوبتی برای پذیرش نیست" /> : null}
</div>
);
}
export function ReceptionCheckOut() {
const { tenantId } = useTenantId();
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) => (
<AppointmentCard key={a.id} appointment={a} href="/beauty/appointments" />
))}
</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="گزارش پذیرش" />;
}