const { useState: useS, useEffect: useE, useCallback } = React; const EVENT_ARTIST_ROLES = [ { id: "acteur", label: "Acteur" }, { id: "musicien", label: "Musicien" }, { id: "metteur_en_scene", label: "Metteur en scène" }, { id: "association", label: "Association" }, { id: "autre", label: "Autre" }, ]; const EVENTS_MAX = 200; const EVENT_STATUS_LABEL = { draft: "Brouillon", scheduled: "À venir", past: "Passé", cancelled: "Annulé", }; function toDatetimeLocalValue(iso) { if (!iso) return ""; const d = new Date(iso); if (Number.isNaN(d.getTime())) return ""; const pad = (n) => String(n).padStart(2, "0"); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; } function formatEventDateLabel(startsAt, endsAt) { const start = new Date(startsAt); if (Number.isNaN(start.getTime())) return ""; const fmt = (d) => d.toLocaleString("fr-FR", { weekday: "short", day: "numeric", month: "short", hour: "2-digit", minute: "2-digit", }); if (!endsAt) return fmt(start); const end = new Date(endsAt); if (Number.isNaN(end.getTime())) return fmt(start); return `${fmt(start)} → ${fmt(end)}`; } function emptyEventDraft() { return { id: "", title: "", startsAt: "", endsAt: "", location: "", shortDescription: "", posterUrl: "", sourceUrl: "", status: "scheduled", artists: [], }; } function eventToDraft(ev) { return { id: ev.id, title: ev.title || "", startsAt: toDatetimeLocalValue(ev.startsAt), endsAt: toDatetimeLocalValue(ev.endsAt), location: ev.location || "", shortDescription: ev.shortDescription || "", posterUrl: ev.posterUrl || "", sourceUrl: ev.sourceUrl || "", status: ev.status, artists: (ev.artists || []).map((a) => ({ ...a })), }; } function EventsScreen({ initialEvents = [], busy }) { const [events, setEvents] = useS(() => initialEvents || []); const [draft, setDraft] = useS(null); const [loading, setLoading] = useS(false); const [saving, setSaving] = useS(false); const [msg, setMsg] = useS(""); const [err, setErr] = useS(""); const [describeBusy, setDescribeBusy] = useS(false); const [posterBusy, setPosterBusy] = useS(false); const [importUrl, setImportUrl] = useS(""); const [importText, setImportText] = useS(""); const [importing, setImporting] = useS(false); const load = useCallback(async () => { setLoading(true); setErr(""); try { const res = await GuyAPI.listEvents(); setEvents(Array.isArray(res?.events) ? res.events : []); } catch (e) { setErr(e?.message || "Impossible de charger les événements."); } finally { setLoading(false); } }, []); useE(() => { void load(); }, [load]); const upcoming = events.filter( (e) => e.displayStatus === "scheduled" || e.displayStatus === "draft", ); const past = events.filter((e) => e.displayStatus === "past"); const cancelled = events.filter((e) => e.displayStatus === "cancelled"); function patchDraft(partial) { setDraft((prev) => (prev ? { ...prev, ...partial } : prev)); } function openNew() { setErr(""); setMsg(""); setDraft(emptyEventDraft()); } function openEdit(ev) { setErr(""); setMsg(""); setDraft(eventToDraft(ev)); } async function saveDraft() { if (!draft) return; if (!(draft.title || "").trim() || !draft.startsAt) { setErr("Indiquez au moins un nom et une date de début."); return; } setSaving(true); setErr(""); setMsg(""); const payload = { title: draft.title.trim(), startsAt: draft.startsAt, endsAt: draft.endsAt || null, location: draft.location.trim(), shortDescription: draft.shortDescription.trim(), posterUrl: draft.posterUrl.trim() || null, sourceUrl: draft.sourceUrl.trim() || null, status: draft.status, artists: (draft.artists || []).filter((a) => (a.name || "").trim()), }; try { const res = draft.id ? await GuyAPI.updateEvent(draft.id, payload) : await GuyAPI.createEvent(payload); const saved = res?.event; if (saved) { setEvents((prev) => { const others = prev.filter((e) => e.id !== saved.id); return [...others, saved].sort((a, b) => a.startsAt.localeCompare(b.startsAt)); }); setDraft(eventToDraft(saved)); } else { await load(); } setMsg("Événement enregistré."); } catch (e) { setErr(e?.message || "Enregistrement impossible."); } finally { setSaving(false); } } async function removeDraft() { if (!draft?.id) { setDraft(null); return; } if (!window.confirm("Supprimer cet événement ?")) return; setSaving(true); try { await GuyAPI.deleteEvent(draft.id); setEvents((prev) => prev.filter((e) => e.id !== draft.id)); setDraft(null); setMsg("Événement supprimé."); } catch (e) { setErr(e?.message || "Suppression impossible."); } finally { setSaving(false); } } async function describeDraft() { if (!draft?.id) { setErr("Enregistrez l’événement avant de générer le descriptif."); return; } setDescribeBusy(true); setErr(""); try { const res = await GuyAPI.describeEvent(draft.id); const saved = res?.event; if (saved) { setEvents((prev) => prev.map((e) => (e.id === saved.id ? saved : e))); setDraft(eventToDraft(saved)); } else if (res?.description) { patchDraft({ shortDescription: res.description }); } setMsg("Descriptif rédigé. Relisez-le avant de le laisser tel quel."); } catch (e) { setErr(e?.message || "Génération impossible."); } finally { setDescribeBusy(false); } } async function onPosterFile(file) { if (!file) return; setPosterBusy(true); setErr(""); try { const dataUrl = await resizeProductRefFile(file); const res = await GuyAPI.uploadEventPoster(dataUrl); if (!res?.url) throw new Error("Hébergement de l’affiche impossible."); patchDraft({ posterUrl: res.url }); } catch (e) { setErr(e?.message || "Import de l’affiche impossible."); } finally { setPosterBusy(false); } } async function runImport(payload) { setImporting(true); setErr(""); setMsg(""); try { const res = await GuyAPI.importEvents(payload); const incoming = Array.isArray(res?.events) ? res.events : []; if (incoming.length) { setEvents((prev) => { const byId = new Map(prev.map((e) => [e.id, e])); for (const ev of incoming) byId.set(ev.id, ev); return [...byId.values()].sort((a, b) => a.startsAt.localeCompare(b.startsAt)); }); } else { await load(); } setMsg(res?.message || "Import terminé."); if (payload.text) setImportText(""); } catch (e) { setErr(e?.message || "Import impossible."); } finally { setImporting(false); } } async function onImportFile(file) { if (!file) return; const text = await file.text(); await runImport({ text, filename: file.name }); } function addArtist() { patchDraft({ artists: [...(draft?.artists || []), { name: "", role: "autre" }], }); } function patchArtist(i, key, value) { const next = (draft?.artists || []).map((a, idx) => idx === i ? { ...a, [key]: value } : a, ); patchDraft({ artists: next }); } function removeArtist(i) { patchDraft({ artists: (draft?.artists || []).filter((_, idx) => idx !== i), }); } function renderGroup(title, list) { if (!list.length) return null; return (

{title}

{list.map((ev) => ( ))}
); } return (

Événements

Calendrier interne : Guy s’en sert comme des produits pour les articles et l’autopilote.

{err ?

{err}

: null} {msg ?

{msg}

: null} {loading ?

Chargement…

: null} {draft ? (
{draft.posterUrl ? ( ) : (
Affiche
)}
patchDraft({ title: e.target.value })} />
patchDraft({ location: e.target.value })} />