82 lines
5.1 KiB
TypeScript
82 lines
5.1 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
|
import { commissionIncomeDay, commissionIncomeShares } from "@/lib/commission/income";
|
|
|
|
type IncomeRow = { ticketId: string; title: string | null; creatorName: string; amountBaht: number; transferredAt: Date };
|
|
type Period = { month: string; year: number; number: number; previous: string; next: string; days: number; leadingDays: number };
|
|
const weekdays = ["จ.", "อ.", "พ.", "พฤ.", "ศ.", "ส.", "อา."];
|
|
const money = (satang: number) => `฿${(satang / 100).toLocaleString("th-TH", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
const time = new Intl.DateTimeFormat("th-TH", { timeZone: "Asia/Bangkok", hour: "2-digit", minute: "2-digit" });
|
|
const date = new Intl.DateTimeFormat("th-TH", { timeZone: "Asia/Bangkok", day: "numeric", month: "long", year: "numeric" });
|
|
|
|
function Totals({ amount }: { amount: number }) {
|
|
const shares = commissionIncomeShares(amount);
|
|
return <span>รวม {money(amount * 100)} · Buzz {money(shares.buzzSatang)} · Gunshin {money(shares.gunshinSatang)}</span>;
|
|
}
|
|
|
|
export function CommissionIncomeCalendar({ period, rows }: { period: Period; rows: IncomeRow[] }) {
|
|
const byDay = new Map<number, IncomeRow[]>();
|
|
for (const row of rows) {
|
|
const day = commissionIncomeDay(row.transferredAt);
|
|
const list = byDay.get(day) ?? [];
|
|
list.push(row);
|
|
byDay.set(day, list);
|
|
}
|
|
const total = rows.reduce((sum, row) => sum + row.amountBaht, 0);
|
|
const monthLabel = new Intl.DateTimeFormat("th-TH", { month: "long", year: "numeric", timeZone: "UTC" })
|
|
.format(new Date(Date.UTC(period.year, period.number - 1, 1)));
|
|
|
|
return <>
|
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
<nav className="flex items-center gap-2" aria-label="เดือนของรายงาน">
|
|
<Button variant="outline" size="icon" nativeButton={false} render={<Link href={`/admin/commission/income?month=${period.previous}`} prefetch={false} />} aria-label="เดือนก่อนหน้า"><ChevronLeft /></Button>
|
|
<span className="min-w-36 text-center text-lg font-semibold">{monthLabel}</span>
|
|
<Button variant="outline" size="icon" nativeButton={false} render={<Link href={`/admin/commission/income?month=${period.next}`} prefetch={false} />} aria-label="เดือนถัดไป"><ChevronRight /></Button>
|
|
</nav>
|
|
<p className="text-sm font-medium"><Totals amount={total} /></p>
|
|
</div>
|
|
<div className="overflow-x-auto">
|
|
<div className="grid min-w-[700px] grid-cols-7 gap-1">
|
|
{weekdays.map((day) => <div key={day} className="p-2 text-center text-sm text-muted-foreground">{day}</div>)}
|
|
{Array.from({ length: period.leadingDays }, (_, index) => <div key={`pad-${index}`} />)}
|
|
{Array.from({ length: period.days }, (_, index) => {
|
|
const day = index + 1;
|
|
const tickets = byDay.get(day) ?? [];
|
|
const dayTotal = tickets.reduce((sum, row) => sum + row.amountBaht, 0);
|
|
const label = date.format(new Date(`${period.month}-${String(day).padStart(2, "0")}T12:00:00+07:00`));
|
|
if (tickets.length === 0) return <div key={day} className="min-h-24 rounded-lg border p-2 font-medium">{day}</div>;
|
|
return <Dialog key={day}>
|
|
<DialogTrigger render={<button type="button" className="flex min-h-24 flex-col gap-1 rounded-lg border p-2 text-left hover:bg-muted/50" />}>
|
|
<span className="font-medium">{day}</span>
|
|
<span className="text-xs text-muted-foreground">{tickets.length} tickets</span>
|
|
<span className="text-sm">{money(dayTotal * 100)}</span>
|
|
</DialogTrigger>
|
|
<DialogContent className="max-h-[85svh] overflow-y-auto sm:max-w-2xl">
|
|
<DialogHeader><DialogTitle>{label}</DialogTitle></DialogHeader>
|
|
<p className="text-sm"><Totals amount={dayTotal} /></p>
|
|
<div className="space-y-2">
|
|
{tickets.map((row) => <div key={row.ticketId} className="rounded-lg border p-3 text-sm">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div className="min-w-0">
|
|
<Link href={`/admin/commission/${row.ticketId}`} className="font-medium underline">{row.title || `Ticket #${row.ticketId.slice(0, 8)}`}</Link>
|
|
<p className="text-muted-foreground">สร้างโดย {row.creatorName}</p>
|
|
</div>
|
|
<div className="shrink-0 text-right">
|
|
<time className="block text-muted-foreground">{time.format(row.transferredAt)}</time>
|
|
<span className="font-medium">{money(row.amountBaht * 100)}</span>
|
|
</div>
|
|
</div>
|
|
</div>)}
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>;
|
|
})}
|
|
</div>
|
|
</div>
|
|
</>;
|
|
}
|