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>
172 lines
5.1 KiB
TypeScript
172 lines
5.1 KiB
TypeScript
"use client";
|
||
|
||
import { Select } from "@/components/ds";
|
||
import { useBeautyLookups } from "@/modules/beauty/hooks/useBeautyLookups";
|
||
import { useQuery } from "@tanstack/react-query";
|
||
import { beautyBusinessApi } from "@/modules/beauty/services/beauty-business-api";
|
||
import { useTenantId } from "@/hooks/useTenantId";
|
||
|
||
type SelectProps = {
|
||
value: string;
|
||
onChange: (v: string) => void;
|
||
required?: boolean;
|
||
label?: string;
|
||
organizationId?: string;
|
||
};
|
||
|
||
function FieldShell({
|
||
label,
|
||
required,
|
||
children,
|
||
}: {
|
||
label?: string;
|
||
required?: boolean;
|
||
children: React.ReactNode;
|
||
}) {
|
||
return (
|
||
<label className="block text-sm">
|
||
{label ? (
|
||
<span className="mb-1 block text-[var(--muted)]">
|
||
{label}
|
||
{required ? " *" : ""}
|
||
</span>
|
||
) : null}
|
||
{children}
|
||
</label>
|
||
);
|
||
}
|
||
|
||
export function OrganizationSelect({ value, onChange, required, label = "سازمان" }: SelectProps) {
|
||
const { organizations } = useBeautyLookups();
|
||
return (
|
||
<FieldShell label={label} required={required}>
|
||
<Select value={value} onChange={(e) => onChange(e.target.value)} required={required}>
|
||
<option value="">انتخاب…</option>
|
||
{organizations.map((o) => (
|
||
<option key={o.id} value={o.id}>
|
||
{o.name}
|
||
</option>
|
||
))}
|
||
</Select>
|
||
</FieldShell>
|
||
);
|
||
}
|
||
|
||
export function BranchSelect({ value, onChange, required, label = "شعبه", organizationId }: SelectProps) {
|
||
const { branches } = useBeautyLookups();
|
||
const filtered = organizationId
|
||
? branches.filter((b) => b.organization_id === organizationId)
|
||
: branches;
|
||
return (
|
||
<FieldShell label={label} required={required}>
|
||
<Select value={value} onChange={(e) => onChange(e.target.value)} required={required}>
|
||
<option value="">انتخاب…</option>
|
||
{filtered.map((b) => (
|
||
<option key={b.id} value={b.id}>
|
||
{b.name}
|
||
</option>
|
||
))}
|
||
</Select>
|
||
</FieldShell>
|
||
);
|
||
}
|
||
|
||
export function StaffSelect({ value, onChange, required, label = "پرسنل" }: SelectProps) {
|
||
const { staff } = useBeautyLookups();
|
||
return (
|
||
<FieldShell label={label} required={required}>
|
||
<Select value={value} onChange={(e) => onChange(e.target.value)} required={required}>
|
||
<option value="">انتخاب…</option>
|
||
{staff.map((s) => (
|
||
<option key={s.id} value={s.id}>
|
||
{s.display_name}
|
||
</option>
|
||
))}
|
||
</Select>
|
||
</FieldShell>
|
||
);
|
||
}
|
||
|
||
export function CustomerSelect({ value, onChange, required, label = "مشتری" }: SelectProps) {
|
||
const { customers } = useBeautyLookups();
|
||
return (
|
||
<FieldShell label={label} required={required}>
|
||
<Select value={value} onChange={(e) => onChange(e.target.value)} required={required}>
|
||
<option value="">انتخاب…</option>
|
||
{customers.map((c) => (
|
||
<option key={c.id} value={c.id}>
|
||
{c.display_name}
|
||
</option>
|
||
))}
|
||
</Select>
|
||
</FieldShell>
|
||
);
|
||
}
|
||
|
||
export function ServiceSelect({ value, onChange, required, label = "خدمت" }: SelectProps) {
|
||
const { services } = useBeautyLookups();
|
||
return (
|
||
<FieldShell label={label} required={required}>
|
||
<Select value={value} onChange={(e) => onChange(e.target.value)} required={required}>
|
||
<option value="">انتخاب…</option>
|
||
{services.map((s) => (
|
||
<option key={s.id} value={s.id}>
|
||
{s.name}
|
||
</option>
|
||
))}
|
||
</Select>
|
||
</FieldShell>
|
||
);
|
||
}
|
||
|
||
export function CategorySelect({ value, onChange, required, label = "دستهبندی" }: SelectProps) {
|
||
const { tenantId } = useTenantId();
|
||
const q = useQuery({
|
||
queryKey: ["beauty", tenantId, "lookups", "categories"],
|
||
queryFn: () => beautyBusinessApi.serviceCategories.list(tenantId!),
|
||
enabled: !!tenantId,
|
||
});
|
||
return (
|
||
<FieldShell label={label} required={required}>
|
||
<Select value={value} onChange={(e) => onChange(e.target.value)} required={required}>
|
||
<option value="">انتخاب…</option>
|
||
{(q.data ?? []).map((c) => (
|
||
<option key={c.id} value={c.id}>
|
||
{c.name}
|
||
</option>
|
||
))}
|
||
</Select>
|
||
</FieldShell>
|
||
);
|
||
}
|
||
|
||
export function RoomSelect({
|
||
value,
|
||
onChange,
|
||
required,
|
||
label = "اتاق",
|
||
organizationId,
|
||
}: SelectProps) {
|
||
const { tenantId } = useTenantId();
|
||
const q = useQuery({
|
||
queryKey: ["beauty", tenantId, "lookups", "rooms"],
|
||
queryFn: () => beautyBusinessApi.treatmentRooms.list(tenantId!),
|
||
enabled: !!tenantId,
|
||
});
|
||
const rooms = (q.data ?? []).filter(
|
||
(r) => !organizationId || r.organization_id === organizationId
|
||
);
|
||
return (
|
||
<FieldShell label={label} required={required}>
|
||
<Select value={value} onChange={(e) => onChange(e.target.value)} required={required}>
|
||
<option value="">انتخاب…</option>
|
||
{rooms.map((r) => (
|
||
<option key={r.id} value={r.id}>
|
||
{r.name}
|
||
</option>
|
||
))}
|
||
</Select>
|
||
</FieldShell>
|
||
);
|
||
}
|