import { useState, useEffect } from "react"; // ── Helpers ────────────────────────────────────────────────────────────────── function normalizePhone(raw) { let c = raw.replace(/[\s\-\(\)\+\.]/g, ""); if (/^\d{9}$/.test(c)) return "420" + c; if (c.startsWith("420") && c.length === 12) return c; if (c.startsWith("00420")) return c.slice(2); return c; } function waLink(phone, message) { return `https://wa.me/${normalizePhone(phone)}?text=${encodeURIComponent(message)}`; } function fillMsg(template, vars) { return Object.entries(vars).reduce( (t, [k, v]) => t.replaceAll(`{${k}}`, v || "—"), template ); } function fmtDate(dateStr) { if (!dateStr) return ""; return new Date(dateStr + "T12:00").toLocaleDateString("cs-CZ", { day: "numeric", month: "long", year: "numeric", }); } // ── Default Templates ───────────────────────────────────────────────────────── const DEFAULT_INV = `Dobrý den {jmeno}, rádi bychom Vás pozvali na exkluzivní testování závodních vozů: 🏁 Akce: {akce} 📅 Datum: {datum} ⏰ Čas: {cas} 📍 Místo: {misto} Těšíme se na Vaši účast! V případě dotazů nás neváhejte kontaktovat. S pozdravem, Tým GTSPORTS`; const DEFAULT_REM = `Dobrý den {jmeno}, připomínáme Vám nadcházející testování závodních vozů: 🏁 Akce: {akce} 📅 Datum: {datum} ⏰ Čas: {cas} 📍 Místo: {misto} Pokud máte jakékoli dotazy nebo potřebujete změnu, kontaktujte nás. Těšíme se na Vás! Tým GTSPORTS`; const STORAGE_KEY = "gtsports_racingmsg_v2"; // ── App ─────────────────────────────────────────────────────────────────────── export default function App() { const [tab, setTab] = useState("zakaznici"); const [loaded, setLoaded] = useState(false); const [data, setData] = useState({ event: { name: "", date: "", time: "", location: "" }, customers: [], inviteT: DEFAULT_INV, reminderT: DEFAULT_REM, }); const [newName, setNewName] = useState(""); const [newPhone, setNewPhone] = useState(""); const [bulkText, setBulkText] = useState(""); const [openPreview, setOpenPreview] = useState(null); useEffect(() => { (async () => { try { const r = await window.storage.get(STORAGE_KEY); if (r) setData(JSON.parse(r.value)); } catch {} setLoaded(true); })(); }, []); async function save(next) { setData(next); try { await window.storage.set(STORAGE_KEY, JSON.stringify(next)); } catch {} } const patchEvent = (f, v) => save({ ...data, event: { ...data.event, [f]: v } }); function addOne() { if (!newName.trim() || !newPhone.trim()) return; const c = { id: Date.now(), name: newName.trim(), phone: newPhone.trim(), sentInvite: false, sentReminder: false }; save({ ...data, customers: [...data.customers, c] }); setNewName(""); setNewPhone(""); } function importBulk() { const lines = bulkText.trim().split("\n").filter(Boolean); const added = lines.flatMap((line, i) => { const [name, phone] = line.split(/[,;\t]+/); if (!phone?.trim()) return []; return [{ id: Date.now() + i, name: name.trim(), phone: phone.trim(), sentInvite: false, sentReminder: false }]; }); if (added.length) save({ ...data, customers: [...data.customers, ...added] }); setBulkText(""); } const removeCustomer = (id) => save({ ...data, customers: data.customers.filter(c => c.id !== id) }); const markSent = (id, field) => save({ ...data, customers: data.customers.map(c => c.id === id ? { ...c, [field]: true } : c) }); const resetSent = (id) => save({ ...data, customers: data.customers.map(c => c.id === id ? { ...c, sentInvite: false, sentReminder: false } : c) }); function getVars(cust) { return { jmeno: cust.name, datum: fmtDate(data.event.date), cas: data.event.time || "", misto: data.event.location || "", akce: data.event.name || "Testování závodních vozů", }; } function cardBorderColor(c) { if (c.sentInvite && c.sentReminder) return "#16a34a"; if (c.sentInvite) return "#d97706"; return "#3f3f46"; } const sentInv = data.customers.filter(c => c.sentInvite).length; const sentRem = data.customers.filter(c => c.sentReminder).length; // ── Styles ── const inp = { width: "100%", boxSizing: "border-box", padding: "10px 14px", background: "#151820", border: "1px solid #1E2130", borderRadius: 8, color: "#EEE9E2", fontSize: 14, outline: "none", }; const sec = { fontSize: 11, fontWeight: 800, color: "#DC2020", letterSpacing: "2px", textTransform: "uppercase", marginBottom: 14 }; if (!loaded) return (
Načítám…
); return (
{/* ── HEADER ── */}
🏁
GTSPORTS
Racing Event Messenger
{data.event.date && (
Příští akce
{data.event.name || "Testování"}
{fmtDate(data.event.date)}{data.event.time ? ` · ${data.event.time}` : ""}
)}
{/* ── TAB BAR ── */}
{[["akce","🏎️ Akce"],["sablony","📝 Šablony"],["zakaznici","👥 Zákazníci"],["odeslat","📤 Odeslat"]].map(([id, label]) => ( ))}
{/* ════ AKCE ════ */} {tab === "akce" && (
Detail akce
{[ ["name","Název akce","text","např. Trackday Brno 2025"], ["date","Datum","date",""], ["time","Čas začátku","time",""], ["location","Místo / Okruh","text","např. Autodrom Brno"], ].map(([f, label, type, ph]) => (
patchEvent(f, e.target.value)} style={inp} />
))} {data.event.name && data.event.date && (
✓ AKCE NASTAVENA
{data.event.name}
{fmtDate(data.event.date)} {data.event.time && ` · ${data.event.time}`} {data.event.location && ` · ${data.event.location}`}
)}
)} {/* ════ ŠABLONY ════ */} {tab === "sablony" && (
Šablony zpráv
Proměnné:{" "} {["{jmeno}","{datum}","{cas}","{misto}","{akce}"].map(v => ( {v} ))}