mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -1,10 +1,15 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { House, Pencil, Bell, Lock, Send, Clock3 } from "lucide-react";
|
||||
import { House, Pencil, Bell, Lock, Send, Clock3, Plus, Trash2 } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
AlertDialog, AlertDialogAction, AlertDialogCancel,
|
||||
AlertDialogContent, AlertDialogDescription,
|
||||
AlertDialogFooter, AlertDialogHeader, AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import AppBreadcrumb from "@/components/generic/Breadcrumb/AppBreadcrumb";
|
||||
import { PageMeta } from "@/contexts/MetadataContext";
|
||||
import {
|
||||
@@ -15,7 +20,7 @@ import { NOTIFICATION_TEMPLATE_TYPES, getNotificationTemplateType } from "@/data
|
||||
import { STATUS_META, hasPendingChanges } from "@/data/notificationTemplateStatus.data";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function TemplateCard({ item, onEdit }) {
|
||||
function TemplateCard({ item, onEdit, onDelete }) {
|
||||
const typeMeta = getNotificationTemplateType(item.notify_type);
|
||||
const TypeIcon = typeMeta?.icon ?? Bell;
|
||||
const status = STATUS_META[item.status] ?? STATUS_META.draft;
|
||||
@@ -27,9 +32,16 @@ function TemplateCard({ item, onEdit }) {
|
||||
<div className="w-10 h-10 rounded-lg border bg-muted flex items-center justify-center shrink-0">
|
||||
<TypeIcon className="h-4.5 w-4.5 text-muted-foreground" />
|
||||
</div>
|
||||
<Button type="button" variant="ghost" size="icon" onClick={() => onEdit(item)}>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="flex items-center gap-0.5">
|
||||
{!item.is_system && (
|
||||
<Button type="button" variant="ghost" size="icon" className="text-destructive hover:text-destructive" onClick={() => onDelete(item)}>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button type="button" variant="ghost" size="icon" onClick={() => onEdit(item)}>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
@@ -68,11 +80,18 @@ function TemplateCard({ item, onEdit }) {
|
||||
|
||||
function NotificationTemplatesInner() {
|
||||
const navigate = useNavigate();
|
||||
const { templates, loading, fetchTemplates } = useAdminNotificationTemplates();
|
||||
const { templates, loading, fetchTemplates, deleteTemplate } = useAdminNotificationTemplates();
|
||||
const [activeType, setActiveType] = useState("all");
|
||||
const [deleteTarget, setDeleteTarget] = useState(null);
|
||||
|
||||
useEffect(() => { fetchTemplates(); }, []);
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!deleteTarget) return;
|
||||
await deleteTemplate(deleteTarget.notification_template_id);
|
||||
setDeleteTarget(null);
|
||||
};
|
||||
|
||||
const filtered = useMemo(
|
||||
() => activeType === "all" ? templates : templates.filter((t) => t.notify_type === activeType),
|
||||
[templates, activeType]
|
||||
@@ -120,25 +139,30 @@ function NotificationTemplatesInner() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button type="button" onClick={() => navigate("/admin/announcement-templates/add")} className="gap-1.5">
|
||||
<Plus className="h-4 w-4" /> Add Template
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border bg-card p-4 flex items-start gap-3 mb-5">
|
||||
<Lock className="h-4 w-4 text-muted-foreground mt-0.5 shrink-0" />
|
||||
<div className="text-xs text-muted-foreground space-y-1">
|
||||
<p>
|
||||
Every template here is <strong>system</strong>-triggered — code fires it by referencing its
|
||||
exact type, so no template can be added or removed from this screen. Only the title and
|
||||
message wording is editable.
|
||||
<strong>System</strong> templates are locked — code fires them by referencing their exact
|
||||
type, so only the title and message wording is editable, never the type itself, and they
|
||||
can't be deleted.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Draft vs. Sent:</strong> a <strong>Sent</strong> template is the version actually
|
||||
used for real notifications right now. Editing a Sent template doesn't change what goes out
|
||||
immediately — it's held as a pending change until you press <strong>Publish</strong> again.
|
||||
<strong>Custom</strong> templates (no lock icon) are reusable title/message presets you
|
||||
create — pick one from the "Load from template" dropdown when composing a new announcement
|
||||
to skip retyping recurring wording. You can freely create, edit, and delete these.
|
||||
</p>
|
||||
<p>
|
||||
Only plain text is supported — no HTML, no conditional logic, just straight{" "}
|
||||
<code className="bg-muted px-1 rounded">{"{{placeholder}}"}</code> tokens that get swapped
|
||||
for real values when the notification fires.
|
||||
<strong>Draft vs. Sent</strong> (system templates only): a <strong>Sent</strong> template
|
||||
is the version actually used for real notifications right now. Editing a Sent template
|
||||
doesn't change what goes out immediately — it's held as a pending change until you press{" "}
|
||||
<strong>Publish</strong> again.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -180,12 +204,31 @@ function NotificationTemplatesInner() {
|
||||
key={item.notification_template_id}
|
||||
item={item}
|
||||
onEdit={(t) => navigate(`/admin/announcement-templates/${t.notification_template_id}/edit`)}
|
||||
onDelete={setDeleteTarget}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AlertDialog open={!!deleteTarget} onOpenChange={(open) => !open && setDeleteTarget(null)}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete this template?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
"{deleteTarget?.label}" will be permanently removed. It won't affect any announcements already sent.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleDelete} disabled={loading} className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
|
||||
{loading ? <Spinner className="h-4 w-4 mr-2" /> : <Trash2 className="h-4 w-4 mr-2" />}
|
||||
Delete
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user