"use client";
import React, { Suspense, useEffect, useMemo, useState } from "react";
import { useAuth } from "@/hooks/useAuth";
import { useRouter, useSearchParams } from "next/navigation";
import { apiFetch } from "@/lib/api";
import { useDismissingState } from "@/hooks/useDismissingState";
import { Ticket } from "lucide-react";
// Format a Date (or date-like input) to the value expected by
// This returns local time (browser timezone) as YYYY-MM-DDTHH:mm
function toLocalDateTimeInputValue(input: string | number | Date | null | undefined): string {
if (input == null) return "";
const d = new Date(input);
if (isNaN(d.getTime())) return "";
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
const hh = String(d.getHours()).padStart(2, "0");
const mm = String(d.getMinutes()).padStart(2, "0");
return `${y}-${m}-${day}T${hh}:${mm}`;
}
function EarlyBirdTiersEditor({ option, onSave }: { option: any; onSave: (tiers: { deadline: string; price: number; order?: number }[]) => void }) {
const [rows, setRows] = React.useState<{ id?: string; deadline: string; price: string; order?: number }[]>([]);
const [open, setOpen] = React.useState(false);
const [saving, setSaving] = React.useState(false);
React.useEffect(() => {
const tiers = Array.isArray(option?.earlyBirdTiers) ? option.earlyBirdTiers : [];
const normalized = tiers
.slice()
.sort((a: any, b: any) => new Date(a.deadline).getTime() - new Date(b.deadline).getTime() || (a.order || 0) - (b.order || 0))
.map((t: any, i: number) => ({ id: t.id, deadline: toLocalDateTimeInputValue(t.deadline), price: String(t.price ?? ''), order: typeof t.order === 'number' ? t.order : i }));
setRows(normalized);
}, [option?.id, option?.earlyBirdTiers]);
const addRow = () => {
setRows((r) => [...r, { deadline: '', price: '', order: (r.length || 0) }]);
};
const removeRow = (idx: number) => {
const copy = rows.slice();
copy.splice(idx, 1);
setRows(copy);
};
const updateRow = (idx: number, patch: Partial<{ deadline: string; price: string; order?: number }>) => {
const copy = rows.slice();
copy[idx] = { ...copy[idx], ...patch } as any;
setRows(copy);
};
const save = async () => {
setSaving(true);
try {
const tiers = rows
.filter((r) => !!r.deadline && String(r.price).trim() !== '')
.map((r, i) => ({ deadline: new Date(r.deadline).toISOString(), price: parseFloat(r.price), order: typeof r.order === 'number' ? r.order : i }))
.filter((t) => t.price >= 0 && !isNaN(new Date(t.deadline).getTime()));
onSave(tiers);
} finally {
setSaving(false);
}
};
return (
setOpen(!open)}>
{open ? 'Hide early-bird tiers' : 'Manage early-bird tiers'}
{open && (
{rows.length === 0 ? (
No tiers yet. Add deadlines and prices for early-bird discounts.
) : (
)}
Add tier
{saving ? 'Saving…' : 'Save tiers'}
)}
);
}
function EventOptionsContent() {
const { user, loading, token } = useAuth();
const router = useRouter();
const search = useSearchParams();
const canView = useMemo(() => {
const role = user?.role;
return role === "admin" || role === "supervisor";
}, [user]);
useEffect(() => {
if (loading) return;
if (!user) router.replace("/login");
}, [user, loading, router]);
const [events, setEvents] = useState([]);
const [selectedEventId, setSelectedEventId] = useState("");
const [options, setOptions] = useState([]);
const [loadingEv, setLoadingEv] = useState(false);
const [error, setError] = useDismissingState(null);
const [info, setInfo] = useDismissingState(null);
const loadEvents = async () => {
if (!token) return;
try {
setLoadingEv(true);
const evs = await apiFetch("/api/events/all", { authToken: token });
setEvents(evs || []);
} catch (e) {
// ignore
} finally {
setLoadingEv(false);
}
};
useEffect(() => { loadEvents(); }, [token]);
// Preselect event from query (?eventId=)
useEffect(() => {
const q = search?.get("eventId");
if (!q) return;
// if events already loaded, ensure it exists then set; otherwise, set directly and let options hook handle
setSelectedEventId(q);
}, [search]);
useEffect(() => {
const ev = events.find(e => e.id === selectedEventId);
if (ev) {
setOptions(ev.options || ev.eventOptions || []);
} else {
setOptions([]);
}
}, [selectedEventId, events]);
const [newOpt, setNewOpt] = useState({ name: "", price: "", isMainTicket: false });
const createOption = async () => {
if (!token || !selectedEventId) return;
setError(null); setInfo(null);
if (!newOpt.name) { setError("Option name is required"); return; }
const priceNum = parseFloat(newOpt.price || "0");
try {
await apiFetch(`/api/events/${encodeURIComponent(selectedEventId)}/options`, {
method: "POST",
authToken: token,
body: { name: newOpt.name, price: priceNum || 0, isMainTicket: !!newOpt.isMainTicket }
});
setNewOpt({ name: "", price: "", isMainTicket: false });
setInfo("Option created");
await loadEvents();
} catch (e: any) {
setError(e?.message || "Failed to create option");
}
};
const updateOption = async (opt: any, patch: any) => {
if (!token) return;
setError(null); setInfo(null);
try {
await apiFetch(`/api/events/options/${encodeURIComponent(opt.id)}`, {
method: "PUT",
authToken: token,
body: patch
});
setInfo("Option updated");
await loadEvents();
} catch (e: any) {
setError(e?.message || "Failed to update option");
}
};
const deleteOption = async (opt: any) => {
if (!token) return;
setError(null); setInfo(null);
try {
await apiFetch(`/api/events/options/${encodeURIComponent(opt.id)}`, {
method: "DELETE",
authToken: token,
});
setInfo("Option deleted");
await loadEvents();
} catch (e: any) {
setError(e?.message || "Failed to delete option");
}
};
return (
router.push('/dashboard/supervisor/events')}>Back
{!canView && (
You need supervisor or admin access to use this page.
)}
{error &&
{error}
}
{info &&
{info}
}
Select event
setSelectedEventId(e.target.value)}>
Select an event…
{events.map(ev => (
{ev.title}
))}
{selectedEventId && (
{(() => {
const ev = events.find(e => e.id === selectedEventId);
if (!ev) return null;
return <>
{new Date(ev.startDate).toLocaleString()} - {new Date(ev.endDate).toLocaleString()}
>;
})()}
)}
{selectedEventId && (
Options
{options.length === 0 ? (
No options yet.
) : (
{options.map(opt => (
{/* Early Bird Tiers Editor */}
updateOption(opt, { earlyBirdTiers: tiers })}
/>
))}
)}
)}
);
}
export default function EventOptionsPage() {
return (
Loading...}>
);
}