// Reusable parts: Sidebar, Topbar, Stat, Quest row, Heatmap, ConfirmModal // ── Local helpers ───────────────────────────────────────────────────────────── const _toDate = (d) => { const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); return `${y}-${m}-${day}`; }; const _recLabel = (rec) => { if (!rec) return ''; if (rec === 'daily') return 'Todo dia'; if (rec === 'weekdays') return 'Seg – Sex'; if (rec === 'weekend') return 'Sab – Dom'; const map = { seg:'Seg', ter:'Ter', qua:'Qua', qui:'Qui', sex:'Sex', sab:'Sab', dom:'Dom' }; return rec.split(',').map(k => map[k] || k).join(', '); }; // Build week grid entries for a recurring quest const getWeekGrid = (recurrence, weekCompletions) => { const KEYS = ['seg','ter','qua','qui','sex','sab','dom']; // index 0=Mon const LABELS = ['Seg','Ter','Qua','Qui','Sex','Sab','Dom']; // Monday of current week const now = new Date(); const dayJS = now.getDay(); // 0=Sun const diff = dayJS === 0 ? -6 : 1 - dayJS; const mon = new Date(now); mon.setDate(now.getDate() + diff); mon.setHours(0, 0, 0, 0); let indices; if (recurrence === 'daily') indices = [0,1,2,3,4,5,6]; else if (recurrence === 'weekdays') indices = [0,1,2,3,4]; else if (recurrence === 'weekend') indices = [5,6]; else indices = recurrence.split(',').map(k => KEYS.indexOf(k)).filter(i => i >= 0); const doneSet = new Set(weekCompletions || []); const todayISO = _toDate(now); const todayMs = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); return indices.map(i => { const d = new Date(mon); d.setDate(mon.getDate() + i); const dateStr = _toDate(d); const dMs = d.getTime(); return { date: dateStr, label: LABELS[i], done: doneSet.has(dateStr), isToday: dateStr === todayISO, isPast: dMs < todayMs, isFuture: dMs > todayMs, }; }); }; // ── Confirm Modal ───────────────────────────────────────────────────────────── const ConfirmModal = ({ title, body, confirmLabel = 'Excluir', onConfirm, onCancel }) => (
e.stopPropagation()}>

{title}

{body}

); // ── Sidebar ─────────────────────────────────────────────────────────────────── const Sidebar = ({ route, setRoute, unread, user, onLogout }) => { const items = [ { id: 'home', label: 'Modo Campanha', ico: 'shield' }, { id: 'quests', label: 'Quest Log', ico: 'scroll', badge: unread }, { id: 'badges', label: 'Salão de troféus', ico: 'trophy' }, { id: 'rank', label: 'Ranking', ico: 'crown' }, { id: 'profile', label: 'Aventureiro', ico: 'user' }, { id: 'settings', label: 'Configurações', ico: 'cog' }, ]; const initials = (user?.adventurerName || 'A').charAt(0).toUpperCase(); return ( ); }; // ── Topbar ──────────────────────────────────────────────────────────────────── const Topbar = ({ crumb, here, streak, onForge }) => (
{crumb}{here}
Streak {streak} dias
); // ── Stat card ───────────────────────────────────────────────────────────────── const Stat = ({ kind, label, value, unit, sub, icoName }) => (
{label}
{React.createElement(I[icoName], {})}
{value}{unit && {unit}}
{sub &&
{sub}
}
); // ── Quest row ───────────────────────────────────────────────────────────────── const Quest = ({ q, onComplete, onCompleteDay, onDelete, compact }) => { const [menuOpen, setMenuOpen] = React.useState(false); const [confirmOpen, setConfirmOpen] = React.useState(false); const cat = CATEGORIES.find(c => c.id === q.category) || CATEGORIES[0]; const diff = DIFFICULTIES.find(d => d.id === q.rarity); const CatIco = I[cat.ico]; const handleDeleteConfirm = () => { setConfirmOpen(false); onDelete(q.id); }; React.useEffect(() => { if (!menuOpen) return; const close = () => setMenuOpen(false); document.addEventListener('click', close); return () => document.removeEventListener('click', close); }, [menuOpen]); // ── Recurring quest: build week grid ── const weekDays = q.recurrence ? getWeekGrid(q.recurrence, q.weekCompletions) : null; const allPastDone = weekDays && weekDays.filter(d => !d.isFuture).length > 0 && weekDays.filter(d => !d.isFuture).every(d => d.done); const isRegDone = !q.recurrence && q.status === 'done'; const questClass = ['quest', q.recurrence ? 'recurring' : '', isRegDone ? 'done' : ''].filter(Boolean).join(' '); return ( <>
{/* Col 1: checkbox for one-time, ↻ badge for recurring */} {q.recurrence ? (
) : ( )} {/* Col 2: meta + week grid */}
{q.title}
{!compact &&
{q.desc}
}
{diff.label} {cat.label} {q.recurrence && ( ↻ {_recLabel(q.recurrence)} )} {!q.recurrence && q.deadline && q.deadline !== '—' && q.deadline !== 'Sem prazo' && ( {q.deadline} )}
{/* Week grid */} {weekDays && (
{weekDays.map(day => ( ))} {allPastDone && ✦ Semana completa!}
)}
{/* Col 3: reward */}
+{q.xp}
{q.recurrence ? 'XP / dia' : 'recompensa'}
{/* Col 4: menu */}
)} {confirmOpen && ( setConfirmOpen(false)} /> )} ); }; // ── Heatmap ─────────────────────────────────────────────────────────────────── const Heatmap = ({ data }) => (
{data.map((lvl, i) =>
)}
Menos Mais
); // ── Empty state ─────────────────────────────────────────────────────────────── const Empty = ({ ico = 'scroll', title, body }) => (
{React.createElement(I[ico], { sw: 1 })}

{title}

{body}

); // ── Badge ───────────────────────────────────────────────────────────────────── const Badge = ({ b, full, state }) => { const unlocked = b.cond(state); const p = Math.min(100, Math.round(b.progress(state) * 100)); return (
{b.glyph}
{b.name}
{b.req}
{unlocked ? 'Conquistada' : `${p}% concluído`}
{!full &&
}
); }; window.ConfirmModal = ConfirmModal; window.Sidebar = Sidebar; window.Topbar = Topbar; window.Stat = Stat; window.Quest = Quest; window.Heatmap = Heatmap; window.Empty = Empty; window.Badge = Badge;