import { useCallback, useEffect, useMemo, useState } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Spinner } from "@/components/ui/spinner"; import { useAuth } from "@/context/AuthContext"; import { useAuthStore } from "@/store/authStore"; import { apiGet, apiPost } from "@/lib/api"; import type { HotelLedgerRow, HotelPointAction, HotelPointRule } from "@/lib/hotel-staff-types"; type RowEdit = Record; export function LoyaltyPointsPage() { const { canManageLoyalty, canViewFinanceLedger } = useAuth(); const selectedPropertyId = useAuthStore((s) => s.selectedPropertyId); const [catalog, setCatalog] = useState([]); const [rules, setRules] = useState([]); const [loading, setLoading] = useState(true); const [savingId, setSavingId] = useState(null); const [edit, setEdit] = useState({}); const [ledger, setLedger] = useState([]); const [ledgerLoading, setLedgerLoading] = useState(false); const [ledgerError, setLedgerError] = useState(null); const ruleByActionId = useMemo(() => { const m = new Map(); for (const r of rules) m.set(r.actionId, r); return m; }, [rules]); const loadLedger = useCallback(async () => { if (!canViewFinanceLedger) return; setLedgerLoading(true); setLedgerError(null); try { const res = await apiGet<{ data: HotelLedgerRow[] }>("/loyalty/ledger"); setLedger(res.data ?? []); } catch (e) { setLedger([]); setLedgerError(e instanceof Error ? e.message : "Could not load ledger"); } finally { setLedgerLoading(false); } }, [canViewFinanceLedger, selectedPropertyId]); const load = useCallback(async () => { setLoading(true); try { const [c, r] = await Promise.all([ apiGet("/loyalty/catalog"), apiGet("/loyalty/rules"), ]); setCatalog(c.filter((a) => a.isActive)); setRules(r); const next: RowEdit = {}; for (const a of c) { const existing = r.find((x) => x.actionId === a.id); next[a.id] = { points: String(existing?.points ?? 0), isEnabled: existing?.isEnabled ?? true, }; } setEdit(next); } finally { setLoading(false); } }, [selectedPropertyId]); useEffect(() => { void load(); }, [load]); useEffect(() => { void loadLedger(); }, [loadLedger]); async function saveRow(actionId: string) { if (!canManageLoyalty) return; const row = edit[actionId]; if (!row) return; const points = Math.max(0, parseInt(row.points, 10) || 0); setSavingId(actionId); try { await apiPost("/loyalty/rules", { actionId, points, isEnabled: row.isEnabled, }); await load(); } finally { setSavingId(null); } } if (loading) { return (
); } return (

Point rules

Set how many points guests earn per action

Actions Code Label Points Enabled {catalog.map((a) => { const e = edit[a.id] ?? { points: "0", isEnabled: true }; const hasRule = ruleByActionId.has(a.id); return ( {a.code} {a.label} setEdit((prev) => ({ ...prev, [a.id]: { ...e, points: ev.target.value }, })) } /> {canManageLoyalty && ( )} ); })}
{!catalog.length && (

No point actions in catalog

)}
Finance: point ledger {canViewFinanceLedger ? ( ) : null} {!canViewFinanceLedger ? (

Full property ledger is visible to finance and admin roles only.

) : ledgerError ? (

{ledgerError}

) : ledgerLoading && !ledger.length ? (
) : ( When Guest Δ Reason {ledger.map((row) => ( {new Date(row.createdAt).toLocaleString()}
{row.user?.name ?? row.userId}
{row.user?.email ?? ""}
{row.delta} {row.reason}
))}
)} {canViewFinanceLedger && !ledgerLoading && !ledger.length && !ledgerError ? (

No ledger rows yet.

) : null}
); }