"use client"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useEffect, useMemo, useState } from "react"; import { Loader2, Trash2, Upload } from "lucide-react"; import { http, toUserError } from "@/lib/api"; import type { RegistrationPhoneWhitelist, WhitelistStatus } from "@/lib/types"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { toast } from "@/components/providers"; import { formatFaNumber } from "@/lib/utils"; import { DEFAULT_PAGE_SIZE, ListPagination, buildPageQuery, type Paginated, } from "@/components/admin/list-pagination"; import { ListToolbar } from "@/components/admin/list-toolbar"; import { useConfirm } from "@/components/admin/use-confirm"; const STATUS_LABEL: Record = { available: "آزاد", consumed: "مصرف‌شده", }; type StatusFilter = "" | WhitelistStatus; type ImportResult = { inserted: number; skipped: number; invalid: number; rejected?: number; rejectedPhones?: string[]; blacklistedConflicts?: string[]; removedBlacklist?: number; }; export function WhitelistPanel() { const qc = useQueryClient(); const { confirm, dialog } = useConfirm(); const [text, setText] = useState(""); const [filter, setFilter] = useState(""); const [q, setQ] = useState(""); const [page, setPage] = useState(1); const [busy, setBusy] = useState(false); const [deleteBusy, setDeleteBusy] = useState(null); const [pendingConflicts, setPendingConflicts] = useState([]); useEffect(() => { setPage(1); }, [filter, q]); const { data, isLoading } = useQuery({ queryKey: ["admin", "phone-whitelist", filter, q, page], queryFn: () => { const params = new URLSearchParams(buildPageQuery(page)); if (filter) params.set("status", filter); if (q.trim()) params.set("q", q.trim()); return http.get>( `/api/admin/phone-whitelist?${params.toString()}`, ); }, }); const entries = useMemo(() => data?.items ?? [], [data]); const total = data?.total ?? 0; const pageSize = data?.pageSize ?? DEFAULT_PAGE_SIZE; const availableCount = useMemo( () => entries.filter((e) => e.status === "available").length, [entries], ); const summarizeImport = (res: ImportResult) => { const parts = [ `${formatFaNumber(res.inserted)} افزوده`, res.skipped ? `${formatFaNumber(res.skipped)} تکراری` : null, res.removedBlacklist ? `${formatFaNumber(res.removedBlacklist)} از فهرست سیاه حذف` : null, res.invalid ? `${formatFaNumber(res.invalid)} نامعتبر` : null, ].filter(Boolean); if (parts.length > 0) { toast.success(parts.join(" · ")); } if ((res.rejected ?? 0) > 0) { const phones = res.rejectedPhones ?? []; const detail = phones.length > 0 ? ` (${phones.slice(0, 3).join("، ")}${phones.length > 3 ? "…" : ""})` : ""; toast.error( `${formatFaNumber(res.rejected ?? 0)} شماره قبلاً ثبت‌نام شده و قابل افزودن به فهرست مجاز نیست${detail}`, ); } }; const postImport = async (payload: { text?: string; phones?: string[]; resolveConflicts?: boolean; }) => { return http.post("/api/admin/phone-whitelist/import", payload); }; const finishImport = (res: ImportResult) => { summarizeImport(res); setText(""); setPendingConflicts([]); setPage(1); qc.invalidateQueries({ queryKey: ["admin", "phone-whitelist"] }); if (res.removedBlacklist) { qc.invalidateQueries({ queryKey: ["admin", "phone-blacklist"] }); } }; const importPhones = async () => { if (!text.trim()) { toast.error("حداقل یک شماره وارد کنید"); return; } setBusy(true); setPendingConflicts([]); try { const res = await postImport({ text }); const conflicts = res.blacklistedConflicts ?? []; if (conflicts.length > 0) { summarizeImport(res); if (conflicts.length === 1) { const phone = conflicts[0]; const ok = await confirm( "این شماره در فهرست سیاه است", `شماره ${phone} در فهرست سیاه است. حذف از فهرست سیاه و افزودن به فهرست مجاز؟`, "حذف از فهرست سیاه و افزودن", ); if (!ok) { setPendingConflicts(conflicts); return; } const resolved = await postImport({ phones: conflicts, resolveConflicts: true }); finishImport(resolved); return; } setPendingConflicts(conflicts); toast.error( `${formatFaNumber(conflicts.length)} شماره در فهرست سیاه است — برای ادامه تعارض را حل کنید`, ); return; } finishImport(res); } catch (e) { toast.error(toUserError(e)); } finally { setBusy(false); } }; const resolveConflicts = async () => { if (pendingConflicts.length === 0) { return; } setBusy(true); try { const res = await postImport({ phones: pendingConflicts, resolveConflicts: true }); finishImport(res); } catch (e) { toast.error(toUserError(e)); } finally { setBusy(false); } }; const remove = async (id: number) => { setDeleteBusy(id); try { await http.del(`/api/admin/phone-whitelist/${id}`); toast.success("شماره از فهرست حذف شد"); qc.invalidateQueries({ queryKey: ["admin", "phone-whitelist"] }); } catch (e) { toast.error(toUserError(e)); } finally { setDeleteBusy(null); } }; return (
{dialog} واردات شماره

هر شماره پس از یک ثبت‌نام موفق مصرف می‌شود. فرمت: هر خط یا جدا با ویرگول.