"use client"; import Link from "next/link"; import { FormEvent, useEffect, useRef, useState } from "react"; import { Branch, Service, Tenant, Ticket, createTicket, getBranchServices, getBranches, getTenant, getTicketsForDevice, } from "@/lib/api"; import { getDeviceToken } from "@/lib/device"; type Step = "tenant" | "branch" | "service" | "ticket"; type Props = { initialTenantCode?: string; initialBranchId?: number; }; /** Prevents spam of "Dapatkan tiket lain" right after getting a number. */ const ANOTHER_TICKET_COOLDOWN_MS = 10_000; function pickLatestTicket( tickets: Ticket[], branchId?: number ): Ticket | null { const scoped = branchId != null ? tickets.filter((item) => item.branch.id === branchId) : tickets; if (scoped.length === 0) { return null; } return [...scoped].sort( (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() )[0]; } export default function CustomerTicketFlow({ initialTenantCode, initialBranchId, }: Props) { const fromQr = initialTenantCode != null && initialTenantCode !== "" && initialBranchId != null && Number.isFinite(initialBranchId); const [step, setStep] = useState(fromQr ? "service" : "tenant"); const [tenantCode, setTenantCode] = useState( initialTenantCode?.trim().toUpperCase() ?? "" ); const [tenant, setTenant] = useState(null); const [branches, setBranches] = useState([]); const [services, setServices] = useState([]); const [selectedBranch, setSelectedBranch] = useState(null); const [ticket, setTicket] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [cooldownLeftMs, setCooldownLeftMs] = useState(0); const bootstrapped = useRef(false); useEffect(() => { if (step !== "ticket" || !ticket) { setCooldownLeftMs(0); return; } const tick = () => { const elapsed = Date.now() - new Date(ticket.createdAt).getTime(); setCooldownLeftMs( Math.max(0, ANOTHER_TICKET_COOLDOWN_MS - elapsed) ); }; tick(); const id = window.setInterval(tick, 250); return () => window.clearInterval(id); }, [step, ticket]); useEffect(() => { if (bootstrapped.current) return; bootstrapped.current = true; async function bootstrap() { setLoading(true); setError(null); try { const deviceToken = getDeviceToken(); if (deviceToken) { const responses = await getTicketsForDevice(deviceToken); const latest = pickLatestTicket( responses.map((response) => response.ticket), fromQr ? initialBranchId : undefined ); if (latest) { setTicket(latest); setSelectedBranch({ id: latest.branch.id, name: latest.branch.name, tellerStations: latest.branch.tellerStations ?? [], }); setStep("ticket"); setLoading(false); return; } } } catch { // No saved ticket — continue into the normal flow. } if (!fromQr) { setLoading(false); return; } const code = initialTenantCode!.trim().toUpperCase(); const branchId = initialBranchId!; try { const [tenantData, branchData] = await Promise.all([ getTenant(code), getBranches(code), ]); const branch = branchData.find((item) => item.id === branchId); if (!branch) { setError( "This branch was not found. Scan the QR again or enter a company code." ); setTenant(tenantData); setBranches(branchData); setTenantCode(code); setStep("branch"); return; } const branchServices = await getBranchServices(code, branch.id); setTenant(tenantData); setBranches(branchData); setTenantCode(code); setSelectedBranch(branch); setServices(branchServices); setStep("service"); } catch { setError( "Could not open this branch link. Scan the QR again or enter a company code." ); setStep("tenant"); } finally { setLoading(false); } } void bootstrap(); }, [fromQr, initialTenantCode, initialBranchId]); async function handleTenantSubmit(event: FormEvent) { event.preventDefault(); const code = tenantCode.trim().toUpperCase(); if (!code) { setError("Enter a company code."); return; } setLoading(true); setError(null); try { const [tenantData, branchData] = await Promise.all([ getTenant(code), getBranches(code), ]); setTenant(tenantData); setBranches(branchData); setTenantCode(code); setStep("branch"); } catch { setError("Could not find that company. Check the code and try again."); } finally { setLoading(false); } } async function handleSelectBranch(branch: Branch) { if (!tenant) return; setLoading(true); setError(null); setSelectedBranch(branch); try { const branchServices = await getBranchServices(tenant.code, branch.id); setServices(branchServices); setStep("service"); } catch { setError("Could not load services for this branch."); } finally { setLoading(false); } } async function handleSelectService(service: Service) { if (!selectedBranch) return; setLoading(true); setError(null); try { const response = await createTicket({ branchId: selectedBranch.id, serviceId: service.id, deviceToken: getDeviceToken(), }); setTicket(response.ticket); setSelectedBranch(response.ticket.branch); setStep("ticket"); } catch { setError( "Could not get a ticket number. Make sure this branch has services assigned." ); } finally { setLoading(false); } } async function resetFlow() { setTicket(null); setError(null); if (fromQr && initialTenantCode && initialBranchId != null) { setLoading(true); try { const code = initialTenantCode.trim().toUpperCase(); const [tenantData, branchData] = await Promise.all([ getTenant(code), getBranches(code), ]); const branch = branchData.find((item) => item.id === initialBranchId); if (!branch) { setTenant(tenantData); setBranches(branchData); setTenantCode(code); setStep("branch"); return; } const branchServices = await getBranchServices(code, branch.id); setTenant(tenantData); setBranches(branchData); setTenantCode(code); setSelectedBranch(branch); setServices(branchServices); setStep("service"); } catch { setError("Could not reload services. Try scanning the QR again."); setStep("tenant"); } finally { setLoading(false); } return; } if (selectedBranch && tenant) { setLoading(true); try { const branchServices = await getBranchServices( tenant.code, selectedBranch.id ); setServices(branchServices); setStep("service"); } catch { setError("Could not load services for this branch."); setStep("branch"); } finally { setLoading(false); } return; } setStep(tenant ? "branch" : "tenant"); } function startOver() { setStep("tenant"); setTenantCode(""); setTenant(null); setBranches([]); setSelectedBranch(null); setServices([]); setTicket(null); setError(null); } const welcomeCopy = fromQr ? "Pilih perkhidmatan untuk mendapatkan nombor." : "Imbas QR code di branch anda, atau masukkan kod syarikat di bawah."; const canGetAnotherTicket = cooldownLeftMs <= 0; const cooldownSeconds = Math.ceil(cooldownLeftMs / 1000); return (

Sistem Nombor Giliran

{tenant?.name ?? "Dapatkan tiket"}

{tenant?.welcomeMessage ?? welcomeCopy}

{error ? (

{error}

) : null} {fromQr && loading && step === "service" && !selectedBranch ? (

Loading perkhidmatan branch…

) : null} {step === "tenant" ? (
) : null} {step === "branch" ? (

Choose a branch

{branches.length === 0 ? (

No branches available.

) : (
    {branches.map((branch) => (
  • ))}
)}
) : null} {step === "service" && selectedBranch ? (

Perkhidmatan di Cawangan {selectedBranch.name}

{fromQr ? ( Masukkan kod perkhidmatan ) : ( )}
{services.length === 0 ? (

Tiada perkhidmatan yang ditugaskan kepada branch ini. Hubungkan perkhidmatan melalui grup branch dalam aplikasi admin, kemudian cuba lagi.

) : (
    {services.map((service) => (
  • ))}
)}
) : null} {step === "ticket" && ticket ? (

Nombor giliran anda

{ticket.number}

{ticket.service.name}

{ticket.branch.name}

Anda sudah mempunyai nombor giliran.

Sila tunggu sehingga nombor anda dipanggil.

{!canGetAnotherTicket ? (

Tiket lain boleh diambil dalam {cooldownSeconds}s

) : null}
) : null}
); }