Files
starr-philproperties/src/modules/admin/pages/advertisements/ViewAdvertisement.jsx
T
kennethobsequio 17326b2c2e testing 101
Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
2026-06-30 19:32:00 +08:00

198 lines
11 KiB
React

// modules/admin/pages/advertisements/ViewAdvertisement.jsx
import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { House, Edit, ArrowLeft, Megaphone, MousePointerClick, ExternalLink } from "lucide-react";
import { useAdvertisements } from "@/contexts/AdminAdvertisementContext";
import AppBreadcrumb from "@/components/generic/Breadcrumb/AppBreadcrumb";
import { useDateFormat } from "@/hooks/useDateFormat";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Spinner } from "@/components/ui/spinner";
import { ADVERTISEMENT_TYPE_MAP, ADVERTISEMENT_STATUS_MAP } from "@/data/advertisement.data";
// ─── Helpers ────────────────────────────────────────────────────────────────
function SectionCard({ title, description, children }) {
return (
<div className="rounded-lg border bg-card p-6 space-y-4">
{(title || description) && (
<div className="space-y-0.5 pb-1 border-b">
{title && <h2 className="text-sm font-semibold">{title}</h2>}
{description && <p className="text-xs text-muted-foreground">{description}</p>}
</div>
)}
{children}
</div>
);
}
function Field({ label, children }) {
return (
<div>
<p className="text-xs text-muted-foreground mb-0.5">{label}</p>
<div className="text-sm">{children}</div>
</div>
);
}
// ─── Page ───────────────────────────────────────────────────────────────────
export default function ViewAdvertisement() {
const navigate = useNavigate();
const { advertisementId } = useParams();
const { fetchAdvertisement, loading } = useAdvertisements();
const { fmtDateTime } = useDateFormat();
const [advertisement, setAdvertisement] = useState(null);
useEffect(() => {
(async () => {
const res = await fetchAdvertisement(advertisementId);
setAdvertisement(res?.data?.data ?? null);
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [advertisementId]);
const breadcrumbItems = [
{ label: "Home", icon: <House className="size-4" />, to: "/admin" },
{ label: "Advertisements", to: "/admin/advertisements" },
{ label: "View" },
];
if (loading && !advertisement) {
return (
<section className="bg-muted h-full">
<div className="flex items-center justify-center py-32">
<Spinner className="size-6" />
</div>
</section>
);
}
if (!advertisement) {
return (
<section className="bg-muted h-full">
<div className="lg:container lg:mx-auto lg:px-0 flex flex-col items-start px-4">
<div className="flex flex-col gap-2 my-6 w-full">
<AppBreadcrumb items={breadcrumbItems} />
</div>
<p className="text-sm text-muted-foreground">Advertisement not found.</p>
</div>
</section>
);
}
const typeMeta = ADVERTISEMENT_TYPE_MAP[advertisement.type] ?? {};
const statusMeta = ADVERTISEMENT_STATUS_MAP[advertisement.status] ?? {};
const TypeIcon = typeMeta.icon ?? Megaphone;
const previewSrc = advertisement.image?.thumbnail_url || advertisement.image?.file_url || advertisement.image_url || null;
const ctas = Array.isArray(advertisement.ctas) ? advertisement.ctas : [];
return (
<section className="bg-muted h-full">
<div className="lg:container lg:mx-auto lg:px-0 flex flex-col items-center px-4">
<div className="flex flex-col gap-2 my-6 w-full">
<AppBreadcrumb items={breadcrumbItems} />
</div>
<div className="w-full max-w-2xl pb-10 space-y-5">
{/* ── Header ─────────────────────────────────────────────────── */}
<div className="flex items-center justify-between flex-wrap gap-3">
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon" onClick={() => navigate("/admin/advertisements")} aria-label="Back">
<ArrowLeft className="size-4" />
</Button>
<div>
<h1 className="text-xl font-semibold tracking-tight">
{advertisement.headline || advertisement.badge_label || "Untitled advertisement"}
</h1>
<div className="flex items-center gap-1.5 mt-1">
<Badge variant="secondary" className="gap-1">
<TypeIcon className="size-3" />
{typeMeta.label ?? advertisement.type}
</Badge>
<Badge variant={advertisement.status === "active" ? "default" : "secondary"}>
{statusMeta.label ?? advertisement.status}
</Badge>
</div>
</div>
</div>
<Button onClick={() => navigate(`/admin/advertisements/${advertisementId}/edit`)}>
<Edit className="size-4" />
Edit
</Button>
</div>
{/* ── Preview ────────────────────────────────────────────────── */}
<SectionCard title="Preview">
<div className="h-48 rounded-lg bg-muted flex items-center justify-center overflow-hidden">
{previewSrc ? (
<img src={previewSrc} alt={advertisement.headline || advertisement.type} className="w-full h-full object-cover" />
) : (
<Megaphone className="size-8 text-muted-foreground" />
)}
</div>
</SectionCard>
{/* ── Content ────────────────────────────────────────────────── */}
<SectionCard title="Content">
<Field label="Badge label">{advertisement.badge_label || "—"}</Field>
<Field label="Headline">{advertisement.headline || "—"}</Field>
<Field label="Description">{advertisement.description || "—"}</Field>
</SectionCard>
{/* ── Calls to action ────────────────────────────────────────── */}
{ctas.length > 0 && (
<SectionCard title="Calls to action">
<div className="flex flex-col gap-2">
{ctas.map((cta, i) => (
<div key={i} className="flex items-center justify-between text-sm border rounded-md px-3 py-2">
<span className="font-medium">{cta.label}</span>
<a href={cta.link} target="_blank" rel="noreferrer" className="text-muted-foreground flex items-center gap-1 hover:text-foreground">
{cta.link}
<ExternalLink className="size-3" />
</a>
</div>
))}
</div>
</SectionCard>
)}
{/* ── Scheduling & display ──────────────────────────────────── */}
<SectionCard title="Scheduling & display">
<div className="grid grid-cols-2 gap-4">
<Field label="Start date">{fmtDateTime(advertisement.start_date)}</Field>
<Field label="End date">{fmtDateTime(advertisement.end_date)}</Field>
<Field label="Order">{advertisement.order ?? 0}</Field>
<Field label="Active">{advertisement.is_active ? "Yes" : "No"}</Field>
</div>
</SectionCard>
{/* ── Metrics ────────────────────────────────────────────────── */}
<SectionCard title="Metrics">
<div className="flex items-center gap-2 text-sm">
<MousePointerClick className="size-4 text-muted-foreground" />
<span className="font-medium">{advertisement.click_count ?? 0}</span>
<span className="text-muted-foreground">clicks</span>
</div>
</SectionCard>
{/* ── Audit ──────────────────────────────────────────────────── */}
<SectionCard title="Audit">
<div className="grid grid-cols-2 gap-4">
<Field label="Created by">{advertisement.creator?.full_name || "—"}</Field>
<Field label="Created at">{fmtDateTime(advertisement.createdAt)}</Field>
<Field label="Last updated by">{advertisement.updater?.full_name || "—"}</Field>
<Field label="Last updated at">{fmtDateTime(advertisement.updatedAt)}</Field>
</div>
</SectionCard>
</div>
</div>
</section>
);
}