This repository has been archived on 2026-08-11. You can view files and clone it, but cannot push or open issues or pull requests.
Gishen-Admin/src/pages/pos/CustomerCodesPanel.tsx
kirukib fce0149b1a Ship full Admin console polish and update backend spec.
Add medication Items catalogue, counter POS/QR purchase, profile/login,
detail pages, loyalty ledger, Cmd+K nav, KPI strips/charts, avatars, and
i18n; document the new APIs and open questions for the shared backend team.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 00:03:21 +03:00

86 lines
3.2 KiB
TypeScript

/**
* Printable loyalty QR codes — the payloads the counter scanner actually reads.
*/
import { useMemo, useState } from 'react'
import { Printer } from 'lucide-react'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { PersonAvatar } from '@/components/ui/avatar'
import { customers } from '@/mocks/data'
import { customerQrPayload } from '@/pages/pos/posEngine'
import { QrImage } from '@/pages/pos/QrImage'
import { useAuthStore } from '@/zustand/authStore'
import { branches } from '@/mocks/data'
export function CustomerCodesPanel() {
const user = useAuthStore((s) => s.user)
const [q, setQ] = useState('')
const scopedZone = user?.branchId ? branches.find((b) => b.id === user.branchId)?.zone : undefined
const list = useMemo(() => {
let rows = scopedZone ? customers.filter((c) => c.branch === scopedZone) : customers
if (q) {
const needle = q.toLowerCase()
rows = rows.filter(
(c) =>
c.name.toLowerCase().includes(needle) ||
c.phone.includes(needle) ||
c.id.toLowerCase().includes(needle),
)
}
return rows
}, [scopedZone, q])
return (
<div className="space-y-4">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<p className="text-sm text-muted-foreground">
Each code encodes <code className="rounded bg-muted px-1">GISHEN-CUST:&lt;id&gt;</code>. Scan it
on Quick purchase, or type the code when the camera is unavailable.
</p>
<Input
className="h-10 max-w-sm"
placeholder="Search customer…"
value={q}
onChange={(e) => setQ(e.target.value)}
/>
</div>
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
{list.map((customer) => {
const payload = customerQrPayload(customer.id)
return (
<Card key={customer.id}>
<CardHeader className="flex-row items-center gap-3 space-y-0 pb-2">
<PersonAvatar name={customer.name} avatarUrl={customer.avatarUrl} size="md" />
<div className="min-w-0 flex-1">
<CardTitle className="truncate text-base">{customer.name}</CardTitle>
<p className="text-xs text-muted-foreground">{customer.phone}</p>
</div>
<Badge variant="secondary">{customer.tier}</Badge>
</CardHeader>
<CardContent className="flex flex-col items-center gap-3 pt-2">
<QrImage value={payload} size={160} alt={`Loyalty QR for ${customer.name}`} />
<code className="break-all text-center text-[11px] text-muted-foreground">{payload}</code>
<Button
type="button"
variant="outline"
size="sm"
className="h-9 gap-1.5"
onClick={() => window.print()}
>
<Printer className="h-3.5 w-3.5" />
Print
</Button>
</CardContent>
</Card>
)
})}
</div>
</div>
)
}