// Tasks screen — list, filter, IA suggestions, new task sheet // Helpers de recorrência (compartilhados via window) const todayISO = () => new Date().toISOString().slice(0, 10); const RECURRENCE_LABEL = { diaria: 'Todos os dias', 'dias-uteis': 'Dias úteis', 'fim-de-semana': 'Fim de semana', semanal: 'Semanal', mensal: 'Mensal', }; // Decide se uma tarefa recorrente deve aparecer hoje function recurrenceAppliesToday(rec) { if (!rec) return true; const dow = new Date().getDay(); // 0 dom .. 6 sab if (rec === 'diaria') return true; if (rec === 'dias-uteis') return dow >= 1 && dow <= 5; if (rec === 'fim-de-semana') return dow === 0 || dow === 6; if (rec === 'semanal' || rec === 'mensal') return true; // simplificado: sempre visível return true; } // Reseta tarefas recorrentes cujo lastCompletedAt é de outro dia function resetRecurringTasks(tasks) { const today = todayISO(); let changed = false; const next = tasks.map(t => { if (!t.recurrence) return t; if (t.lastCompletedAt && t.lastCompletedAt !== today && t.done) { changed = true; return { ...t, done: false }; } return t; }); return changed ? next : tasks; } window.recurrenceAppliesToday = recurrenceAppliesToday; window.resetRecurringTasks = resetRecurringTasks; window.RECURRENCE_LABEL = RECURRENCE_LABEL; const TasksScreen = ({ state, setState, openAI, navigate, showToast }) => { const [filter, setFilter] = React.useState('hoje'); const [showNew, setShowNew] = React.useState(false); const [detailId, setDetailId] = React.useState(null); // Reset diário ao abrir a tela React.useEffect(() => { const next = resetRecurringTasks(state.tasks); if (next !== state.tasks) setState(s => ({ ...s, tasks: next })); }, []); // Intent: abrir modal de nova tarefa via evento global (atalho do dashboard) React.useEffect(() => { const handler = () => setShowNew(true); window.addEventListener('cdv:open-new-task', handler); return () => window.removeEventListener('cdv:open-new-task', handler); }, []); const filtered = state.tasks.filter(t => { if (t.date !== filter) return false; if (t.recurrence && !recurrenceAppliesToday(t.recurrence)) return false; return true; }); const done = filtered.filter(t => t.done).length; const pct = filtered.length ? (done / filtered.length) * 100 : 0; const detailTask = state.tasks.find(t => t.id === detailId) || null; const toggleTask = (id) => { setState(s => ({ ...s, tasks: s.tasks.map(t => { if (t.id !== id) return t; const nextDone = !t.done; return { ...t, done: nextDone, lastCompletedAt: nextDone && t.recurrence ? todayISO() : t.lastCompletedAt, }; }) })); showToast('Tarefa atualizada'); }; const toggleSubtask = (taskId, subtaskId) => { setState(s => ({ ...s, tasks: s.tasks.map(t => t.id === taskId ? { ...t, subtasks: t.subtasks.map(sub => sub.id === subtaskId ? { ...sub, done: !sub.done } : sub) } : t ) })); }; const addSubtask = (taskId, title) => { setState(s => ({ ...s, tasks: s.tasks.map(t => t.id === taskId ? { ...t, subtasks: [...(t.subtasks || []), { id: 'st' + Date.now(), title, done: false }] } : t ) })); }; const deleteTask = (id) => { setState(s => ({ ...s, tasks: s.tasks.filter(t => t.id !== id) })); setDetailId(null); showToast('Tarefa excluída'); }; const updateTaskField = (id, patch) => { setState(s => ({ ...s, tasks: s.tasks.map(t => t.id === id ? { ...t, ...patch } : t) })); }; return (
setShowNew(true)} style={{ background: CDV.brandGrad, border: 'none', width: 38, height: 38, borderRadius: 99, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', boxShadow: '0 6px 20px -6px rgba(177,151,252,0.6)', }}> } /> {/* Progress strip */}
{done}
de {filtered.length} concluídas
+85 XP
{/* Filter pills */}
{[ { id: 'hoje', label: 'Hoje' }, { id: 'amanha', label: 'Amanhã' }, { id: 'semana', label: 'Esta semana' }, { id: 'todos', label: 'Todos' }, ].map(f => ( ))}
{/* AI suggestion banner */}
Sofia sugere reorganizar suas tarefas
Você produz mais entre 9h e 11h. Posso reagendar 2 tarefas?
{/* Task groups */}
{filtered.filter(t => t.priority === 'alta').map(t => ( setDetailId(t.id)} /> ))} {filtered.filter(t => t.priority === 'alta').length === 0 && Nenhuma tarefa de alta prioridade.}
{filtered.filter(t => t.priority !== 'alta').map(t => ( setDetailId(t.id)} /> ))}
setShowNew(false)} title="Nova tarefa"> { setState(s => ({ ...s, tasks: [...s.tasks, { ...task, id: 'tn' + Date.now() }] })); setShowNew(false); showToast('Tarefa criada'); }} onVoice={() => { setShowNew(false); openAI(); }} /> setDetailId(null)} title="Detalhes"> {detailTask && ( setDetailId(null)} /> )}
); }; const TaskRow = ({ task, onToggle, onOpenDetail }) => { const catColor = CDV.cats[task.category] || CDV.brand; const [pressed, setPressed] = React.useState(false); return (
setPressed(true)} onTouchEnd={() => setPressed(false)} onMouseDown={() => setPressed(true)} onMouseUp={() => setPressed(false)} onMouseLeave={() => setPressed(false)} style={{ borderRadius: 16, background: task.done ? CDV.surface : `linear-gradient(135deg, ${catColor}0d 0%, ${CDV.surface} 50%)`, border: `1px solid ${task.done ? CDV.stroke : catColor + '22'}`, overflow: 'hidden', cursor: 'pointer', position: 'relative', transform: pressed ? 'scale(0.98)' : 'scale(1)', transition: 'transform .15s, box-shadow .2s', boxShadow: task.done ? 'none' : `0 4px 14px -10px ${catColor}40`, opacity: task.done ? 0.7 : 1, }} > {/* Barra lateral categoria */} {!task.done && (
)}
e.stopPropagation()}> onToggle(task.id)} color={catColor} />
{task.title}
{task.category}
{task.time} {task.duration && ` · ${task.duration}min`}
{task.priority === 'alta' && Alta} {task.aiSuggested && IA} {task.recurrence && (
{RECURRENCE_LABEL[task.recurrence] || 'recorrente'}
)} {task.subtasks && task.subtasks.length > 0 && (
{task.subtasks.filter(s => s.done).length}/{task.subtasks.length} subtarefas
)}
); }; // ── Task detail (sheet) ────────────────────────────────────── const TaskDetail = ({ task, onToggle, onToggleSubtask, onAddSubtask, onUpdate, onDelete, onClose }) => { const catColor = CDV.cats[task.category] || CDV.brand; const priColor = task.priority === 'alta' ? CDV.coral : task.priority === 'media' ? CDV.amber : CDV.sky; const [newSub, setNewSub] = React.useState(''); const [editing, setEditing] = React.useState(false); const [draftTitle, setDraftTitle] = React.useState(task.title); const [draftTime, setDraftTime] = React.useState(task.time === '—' ? '' : task.time); const [draftPriority, setDraftPriority] = React.useState(task.priority); const [draftCategory, setDraftCategory] = React.useState(task.category); const [draftRecurrence, setDraftRecurrence] = React.useState(task.recurrence || 'unico'); const cats = ['trabalho', 'pessoal', 'saude', 'estudo', 'casa', 'lazer', 'outros']; const subs = task.subtasks || []; const doneSubs = subs.filter(s => s.done).length; const saveEdit = () => { onUpdate && onUpdate(task.id, { title: draftTitle.trim() || task.title, time: draftTime || '—', priority: draftPriority, category: draftCategory, recurrence: draftRecurrence === 'unico' ? null : draftRecurrence, }); setEditing(false); }; const confirmDelete = () => { if (window.confirm && window.confirm('Excluir esta tarefa? Esta ação não pode ser desfeita.')) { onDelete && onDelete(task.id); } }; return (
{/* Status / Título */} {!editing ? ( <>
{task.category} {task.priority === 'alta' ? 'Alta prioridade' : task.priority === 'media' ? 'Média' : 'Baixa'} {task.recurrence && ( {RECURRENCE_LABEL[task.recurrence] || 'recorrente'} )} {task.aiSuggested && Sugerido pela IA}
{task.title}
) : ( <> setDraftTitle(e.target.value)} placeholder="Título" style={{ width: '100%', height: 54, borderRadius: 14, border: `1px solid ${CDV.stroke}`, background: CDV.surfaceHi, color: CDV.text, padding: '0 16px', fontSize: 16, outline: 'none', fontFamily: 'inherit', marginBottom: 12, }} />
Categoria
{cats.map(c => { const active = draftCategory === c; const cc = CDV.cats[c] || CDV.brand; return ( ); })}
Prioridade
{[{ id: 'baixa', label: 'Baixa', c: CDV.sky }, { id: 'media', label: 'Média', c: CDV.amber }, { id: 'alta', label: 'Alta', c: CDV.coral }].map(p => { const active = draftPriority === p.id; return ( ); })}
Recorrência
{RECURRENCE_OPTIONS.map(r => { const active = draftRecurrence === r.id; return ( ); })}
)} {/* Stats */}
{!editing ? ( ) : ( )}
{/* Subtarefas */} {(subs.length > 0 || !editing) && ( <>
Subtarefas {subs.length > 0 && `· ${doneSubs}/${subs.length}`}
{subs.length > 0 && ( )}
{subs.map(s => (
onToggleSubtask && onToggleSubtask(task.id, s.id)} color={catColor} size={18} />
{s.title}
))}
setNewSub(e.target.value)} placeholder="+ Adicionar subtarefa" onKeyDown={e => { if (e.key === 'Enter' && newSub.trim()) { onAddSubtask(task.id, newSub.trim()); setNewSub(''); } }} style={{ flex: 1, height: 42, borderRadius: 10, border: `1px solid ${CDV.stroke}`, background: CDV.surfaceHi, color: CDV.text, padding: '0 12px', fontSize: 13, outline: 'none', fontFamily: 'inherit', }} /> {newSub.trim() && ( )}
)} {/* Ações principais */}
{!editing ? ( <> ) : ( <> )}
{!editing && ( )}
); }; const Empty = ({ children }) => (
{children}
); const RECURRENCE_OPTIONS = [ { id: 'unico', label: 'Único', icon: 'check' }, { id: 'diaria', label: 'Todos os dias', icon: 'refresh' }, { id: 'dias-uteis', label: 'Dias úteis', icon: 'briefcase' }, { id: 'fim-de-semana', label: 'Fim de semana', icon: 'star' }, { id: 'semanal', label: 'Semanal', icon: 'calendar' }, { id: 'mensal', label: 'Mensal', icon: 'calendar' }, ]; const NewTaskForm = ({ onSubmit, onVoice }) => { const [title, setTitle] = React.useState(''); const [cat, setCat] = React.useState('trabalho'); const [priority, setPriority] = React.useState('media'); const [time, setTime] = React.useState('14:00'); const [recurrence, setRecurrence] = React.useState('unico'); const cats = ['trabalho', 'pessoal', 'saude', 'estudo', 'casa', 'lazer', 'outros']; const pris = [{ id: 'baixa', label: 'Baixa', c: CDV.sky }, { id: 'media', label: 'Média', c: CDV.amber }, { id: 'alta', label: 'Alta', c: CDV.coral }]; return (
setTitle(e.target.value)} placeholder="Ex: Ligar para o Roberto às 15h" style={{ width: '100%', height: 54, borderRadius: 16, border: `1px solid ${CDV.stroke}`, background: CDV.surfaceHi, color: CDV.text, padding: '0 50px 0 16px', fontSize: 15, outline: 'none', fontFamily: 'inherit', }} />
A IA detecta automaticamente data, hora e prioridade
Categoria
{cats.map(c => { const active = cat === c; const cc = CDV.cats[c]; return ( ); })}
Prioridade
{pris.map(p => { const active = priority === p.id; return ( ); })}
Recorrência
{RECURRENCE_OPTIONS.map(r => { const active = recurrence === r.id; return ( ); })}
); }; const FieldCard = ({ label, value, icon }) => (
{label}
{value}
); const TimeInputCard = ({ label, value, onChange, icon }) => { const ref = React.useRef(null); const openPicker = () => { if (ref.current) { try { ref.current.showPicker && ref.current.showPicker(); } catch (e) {} ref.current.focus(); } }; return (
{label}
onChange(e.target.value)} style={{ background: 'transparent', border: 'none', outline: 'none', padding: 0, marginTop: 1, color: CDV.text, fontSize: 14, fontWeight: 500, fontFamily: 'inherit', colorScheme: 'dark', width: '100%', }} />
); }; Object.assign(window, { TasksScreen, TaskRow, NewTaskForm, Empty, FieldCard, TimeInputCard });