import { useState } from "react";
import { Plus, Pencil, Trash2, X, Ban } from "lucide-react";
import {
useNotificationsOverview,
useNotificationTypes,
useNotifications,
useSmsBlacklist,
useNotificationTypeCreate,
useNotificationTypeUpdate,
useNotificationTypeDelete,
useSmsBlacklistDelete,
type NotificationTypeRow,
type NotificationRow,
type SmsBlacklistRow,
} from "~/requestHandler/use-admin-hooks";
import {
AdminTable,
AdminPagination,
AdminToolbar,
AdminSearch,
AdminSelect,
AdminPageTitle,
StatusBadge,
faNum,
faDate,
useDebouncedValue,
type Column,
} from "~/components/admin/DataTable";
import { KpiCard } from "~/components/admin/KpiCard";
import { cn } from "~/lib/utils";
const PAGE_SIZE = 25;
export default function AdminNotifications() {
const [tab, setTab] = useState<"types" | "sent" | "blacklist">("types");
const TABS = [
{ k: "types", label: "قالبها" },
{ k: "sent", label: "ارسالها" },
{ k: "blacklist", label: "لیست سیاه" },
] as const;
return (
اعلانها
{TABS.map((t) => (
))}
{tab === "types" &&
}
{tab === "sent" &&
}
{tab === "blacklist" &&
}
);
}
/* ------------------------------- templates ------------------------------- */
function TypesTab() {
const [page, setPage] = useState(1);
const [search, setSearch] = useState("");
const [editing, setEditing] = useState(null);
const [creating, setCreating] = useState(false);
const debouncedSearch = useDebouncedValue(search);
const { data, isLoading } = useNotificationTypes({ page, page_size: PAGE_SIZE, search: debouncedSearch });
const del = useNotificationTypeDelete();
const columns: Column[] = [
{
header: "عنوان",
render: (r) => (
),
},
{
header: "کانالها",
render: (r) => (
{r.defaultInApp && }
{r.defaultSms && }
),
},
{
header: "قالب",
render: (r) => (
{r.template || "—"}
),
},
{
header: "",
render: (r) => (
),
},
];
return (
{ setSearch(v); setPage(1); }} placeholder="جستجوی کد / عنوان…" />
String(r.id)} emptyText="قالبی ثبت نشده است" />
{(creating || editing) && (
{ setCreating(false); setEditing(null); }} />
)}
);
}
function TypeModal({ row, onClose }: { row: NotificationTypeRow | null; onClose: () => void }) {
const [code, setCode] = useState(row?.code ?? "");
const [title, setTitle] = useState(row?.title ?? "");
const [defaultSms, setDefaultSms] = useState(row?.defaultSms ?? false);
const [defaultInApp, setDefaultInApp] = useState(row?.defaultInApp ?? true);
const [template, setTemplate] = useState(row?.template ?? "");
const [variables, setVariables] = useState((row?.variables ?? []).join(", "));
const [error, setError] = useState("");
const create = useNotificationTypeCreate();
const update = useNotificationTypeUpdate();
const busy = create.isPending || update.isPending;
const submit = () => {
setError("");
if (!code || !title) return setError("کد و عنوان الزامی است.");
const payload = {
code,
title,
defaultSms,
defaultInApp,
template,
variables: variables.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean),
};
const onErr = (e: unknown) => setError(e instanceof Error ? e.message : "خطا در ذخیره");
if (row) update.mutate({ id: row.id, data: payload }, { onSuccess: onClose, onError: onErr });
else create.mutate(payload, { onSuccess: onClose, onError: onErr });
};
return (
e.stopPropagation()}>
{row ? "ویرایش قالب" : "قالب جدید"}
);
}
/* --------------------------------- sent --------------------------------- */
function SentTab() {
const [page, setPage] = useState(1);
const [search, setSearch] = useState("");
const [read, setRead] = useState("");
const [sentSms, setSentSms] = useState("");
const debouncedSearch = useDebouncedValue(search);
const { data: ov } = useNotificationsOverview();
const { data, isLoading } = useNotifications({
page,
page_size: PAGE_SIZE,
search: debouncedSearch,
read,
sent_sms: sentSms,
});
const reset = (fn: () => void) => { fn(); setPage(1); };
const columns: Column[] = [
{
header: "عنوان",
render: (r) => (
{r.title}
{r.typeTitle || r.typeCode || "—"}
),
},
{ header: "گیرنده", render: (r) => {r.receiverPhone || "—"} },
{
header: "کانال",
render: (r) => (
{r.sentInApp && }
{r.sentSms && }
{!r.sentInApp && !r.sentSms && —}
),
},
{ header: "خوانده", render: (r) => (r.read ? : ) },
{ header: "تاریخ", render: (r) => {faDate(r.createdAt)} },
];
return (
{ov && (
0 ? "red" : "default"} />
)}
reset(() => setSearch(v))} placeholder="جستجوی گیرنده / عنوان…" />
reset(() => setRead(v))} options={[{ value: "", label: "همه" }, { value: "false", label: "خواندهنشده" }, { value: "true", label: "خواندهشده" }]} />
reset(() => setSentSms(v))} options={[{ value: "", label: "همه" }, { value: "true", label: "با پیامک" }, { value: "false", label: "بدون پیامک" }]} />
String(r.id)} emptyText="اعلانی یافت نشد" />
);
}
/* ------------------------------- blacklist ------------------------------- */
function BlacklistTab() {
const [page, setPage] = useState(1);
const [search, setSearch] = useState("");
const debouncedSearch = useDebouncedValue(search);
const { data, isLoading } = useSmsBlacklist({ page, page_size: PAGE_SIZE, search: debouncedSearch });
const del = useSmsBlacklistDelete();
const columns: Column[] = [
{ header: "موبایل", render: (r) => {r.mobile} },
{ header: "خط", render: (r) => {faNum(r.lineNumber)} },
{ header: "دلیل", render: (r) => {r.reason || "—"} },
{ header: "تاریخ", render: (r) => {faDate(r.createdAt)} },
{
header: "",
render: (r) => (
),
},
];
return (
{ setSearch(v); setPage(1); }} placeholder="جستجوی موبایل…" />
String(r.id)} emptyText="لیست سیاه خالی است" />
);
}
const inputCls = "w-full rounded-xl border border-WHITE3 bg-WHITE px-3 py-2 text-[13.5px] focus:outline-none focus:border-GRAY";
const labelCls = "block text-[12px] text-GRAY mb-1";