"use client"; import type { ColumnDef } from "@tanstack/react-table"; import { BitcoinIcon, BugPlay, ChevronsLeftRightEllipsis, CoinsIcon, Copy, Goal, ImageIcon, MessageCircleWarning, WalletIcon, QrCodeIcon as PromptpayIcon, } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import posthog from "posthog-js"; import { type ComponentProps, useEffect, useRef, useState } from "react"; import { useEllipsisVisible } from "react-hook-text-overflow"; import { DataTable } from "@/components/tantable"; import { SimpleTooltip } from "@/components/tooltip"; import { Button } from "@/components/ui/button"; import { HoverCard, HoverCardContent, HoverCardTrigger, } from "@/components/ui/hover-card"; import type { donations } from "@/lib/db/schema"; import { sse } from "@/lib/db/sse-endpoints"; import { ActionButton } from "../../../../components/action-button"; import { getImage, reloadWidget, resendPopup, testPopup } from "./api"; import { formatDistanceToNow } from "date-fns"; import { lePalette } from "../../rubgram/admin/[id]/client"; import { th } from "date-fns/locale"; import TruemoneyIcon from "#/assets/tmn.webp"; import Image from "@/components/image"; import { Checkbox } from "@/components/ui/checkbox"; import { toggleCheck } from "@/lib/api"; // directly adapted for date instead of UUID function colorFor(date: Date) { const seed = date.getUTCFullYear() * 10000 + (date.getUTCMonth() + 1) * 100 + date.getUTCDate(); return lePalette[seed % lePalette.length]; } function ArtifactCheckbox({ checked, submissionId, }: { checked: boolean; submissionId: string; }) { const [value, setValue] = useState(checked); useEffect(() => { setValue(checked); }, [checked]); return ( { setValue(checked === true); await toggleCheck(submissionId); }} /> ); } const columns: ColumnDef< typeof donations.$inferSelect & { checked: boolean | null; artifactSubmissionId: string | null; } >[] = [ { accessorKey: "created", header: "", cell(row) { return (
); }, meta: { className: "w-1 p-0 relative" }, }, { accessorKey: "name", header: "ชื่อ", meta: { className: "w-50 truncate" } }, { accessorKey: "art_check", header: "", meta: { className: "w-6 truncate" }, cell(row) { const { artifactSubmissionId, checked } = row.row.original; if (checked === null || !artifactSubmissionId) return; return ( ); }, }, { accessorFn: (row) => `${row.amount}฿`, header: "จำนวน", meta: { className: "w-24" }, }, { accessorKey: "message", cell(props) { const [overflow, ref] = useEllipsisVisible(); return overflow ? (
{props.row.original.message}
{props.row.original.name}
{props.row.original.message}
{`${props.row.original.amount}฿${props.row.original.uid ? ` - UID ${props.row.original.uid}` : ""}`}
) : (
{props.row.original.message}
); }, header: "ข้อความ", meta: { className: "w-full" }, }, { accessorKey: "id", header() { return (
{ posthog.capture("donation_admin_test_popup"); return testPopup(); }} > { posthog.capture("donation_admin_widget_reload"); return reloadWidget(); }} >
); }, cell(row) { return ; }, meta: { className: "w-36 p-0" }, }, ]; function ActionRow({ row, filler, }: { row: typeof donations.$inferSelect; filler?: boolean; }) { return (
{row.method === "pp" ? ( ) : ( truemoney )} { posthog.capture("donation_admin_resend", { id: row.id, }); return resendPopup(row.id); }} > {row.uid ? ( navigator.clipboard.writeText(row.uid!)} > ) : filler ? (
) : undefined} {row.image && ( { await getImage(row.id).then((blob) => { const url = URL.createObjectURL(blob); const tab = window.open("about:blank", "_blank"); if (tab) { tab.location.href = url; } }); }} > )}
); } function TopRow({ row, stats, ...props }: { row?: typeof donations.$inferSelect; stats: { total: number; today: number; }; } & ComponentProps<"div">) { const [time, setTime] = useState(""); useEffect(() => { if (!row) return setTime(""); function update() { setTime(`${formatDistanceToNow(row!.created, { locale: th })}ที่แล้ว`); } const interval = setInterval(update, 1000); update(); return () => clearInterval(interval); }, [row]); return (
{row ? (
{row.name} {row.amount}฿
{time}
{row.message}
) : (
No donation yet
)}
รวมวันนี้ {stats.today}฿
รวมเดือนนี้ {stats.total}฿
); } export function DonateAdminPage({ data, stats, }: { data: (typeof donations.$inferSelect & { checked: boolean | null; artifactSubmissionId: string | null; })[]; stats: { total: number; today: number; }; }) { const [maxHeight, setMaxHeight] = useState(""); const topRowRef = useRef(null); useEffect(() => { setMaxHeight(`calc(100vh - 93px - ${topRowRef.current?.clientHeight}px)`); }, [data]); return (
โดเนทเดือนนี้
); } // component cuz simplicity sake export function DonateWatcher() { const router = useRouter(); const [newDonates, setNewDonates] = useState(0); useEffect(() => { if (newDonates <= 0) return ((document.title = "โดเนททั้งหมด"), undefined); var blink = true; const interval = setInterval(() => { blink = !blink; document.title = blink ? "โดเนททั้งหมด" : `${newDonates} โดเนทใหม่`; }, 1000); return () => clearInterval(interval); }, [newDonates]); useEffect(() => { function listener() { setNewDonates(0); } window.addEventListener("focus", listener); return () => window.removeEventListener("focus", listener); }, []); useEffect(() => { if (!router) return; return sse.donate.subMany({ ping: () => (router.refresh(), setNewDonates((x) => x + 1)), update: router.refresh.bind(router), }).clean; }, [router]); return undefined; }