/* ============================================================
KeS Metas — Tarefas (agenda de afazeres da equipe)
- Padrão: tarefas de hoje + amanhã
- Atrasadas (vencidas e não concluídas) sempre aparecem
- Filtro de datas para ver tarefas anteriores
- Permissão: líder/admin veem todas; comum vê só as próprias
============================================================ */
const { useState, useRef, useEffect } = React;
/* ---- Períodos do filtro de tarefas (inclui datas futuras) ---- */
function rangePreset(name) {
const K = window.KES;
const iso = K.iso;
const today = new Date(K.iso(K.SIM_TODAY) + "T00:00:00");
if (name === "hoje") return { preset: "hoje", start: iso(today), end: iso(today), label: "Hoje" };
if (name === "hojeAmanha") return { preset: "hojeAmanha", start: iso(today), end: iso(K.addDays(today, 1)), label: "Hoje e amanhã" };
if (name === "semana") {
const dow = (today.getDay() + 6) % 7; // 0 = segunda
const s = K.addDays(today, -dow);
return { preset: "semana", start: iso(s), end: iso(K.addDays(s, 6)), label: "Esta semana" };
}
if (name === "mes") {
const s = new Date(today.getFullYear(), today.getMonth(), 1);
const e = new Date(today.getFullYear(), today.getMonth() + 1, 0);
return { preset: "mes", start: iso(s), end: iso(e), label: "Este mês" };
}
if (name === "mesPassado") {
const s = new Date(today.getFullYear(), today.getMonth() - 1, 1);
const e = new Date(today.getFullYear(), today.getMonth(), 0);
return { preset: "mesPassado", start: iso(s), end: iso(e), label: "Mês passado" };
}
return rangePreset("hojeAmanha");
}
/* ---- Filtro de período (mesmo visual dos painéis), com datas futuras ---- */
function FiltroPeriodoTarefas({ value, onChange }) {
const K = window.KES;
const [open, setOpen] = useState(false);
const ref = useRef(null);
const popRef = useRef(null);
useEffect(() => {
const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
document.addEventListener("mousedown", h);
return () => document.removeEventListener("mousedown", h);
}, []);
// Mantém o popup dentro da tela em qualquer posição/tamanho.
useEffect(() => {
if (!open || !popRef.current) return;
const el = popRef.current; el.style.transform = "";
const r = el.getBoundingClientRect(); const m = 8; let dx = 0;
if (r.right > window.innerWidth - m) dx = (window.innerWidth - m) - r.right;
else if (r.left < m) dx = m - r.left;
if (dx) el.style.transform = "translateX(" + Math.round(dx) + "px)";
}, [open]);
const atalhos = [["hojeAmanha", "Hoje e amanhã"], ["hoje", "Hoje"], ["semana", "Esta semana"], ["mes", "Este mês"], ["mesPassado", "Mês passado"]];
const labelText = value.preset === "custom" ? `${K.fmtBR(value.start)} – ${K.fmtBR(value.end)}` : value.label;
return (
{open && (
)}
);
}
/* ---- Modal para criar/editar tarefa (reutilizável) ---- */
function TarefaModal({ initial, state, viewer, dispatch, onClose, leadFixo }) {
const K = window.KES;
const isAdmin = viewer.papel === "admin" || viewer.cargo === "lider";
const editing = !!(initial && initial.id);
const hojeISO = K.iso(K.SIM_TODAY);
const [f, setF] = useState(initial || {
titulo: "", descricao: "", data: hojeISO, hora: "09:00",
responsavelId: viewer.id, leadId: leadFixo ? leadFixo.id : "",
});
const [err, setErr] = useState("");
const set = (k, v) => setF((s) => ({ ...s, [k]: v }));
function salvar() {
if (!f.titulo.trim()) return setErr("Descreva a tarefa.");
if (!f.data) return setErr("Escolha a data.");
const dados = {
titulo: f.titulo.trim(), descricao: (f.descricao || "").trim(),
data: f.data, hora: f.hora || "", responsavelId: f.responsavelId || viewer.id,
leadId: f.leadId || null, criadaPor: viewer.id,
};
if (editing) dispatch({ type: "UPDATE_TAREFA", id: initial.id, patch: dados });
else dispatch({ type: "ADD_TAREFA", tarefa: dados });
dispatch({ type: "TOAST", msg: editing ? "Tarefa atualizada." : "Tarefa agendada." });
onClose();
}
const leadVinc = leadFixo || (f.leadId ? (state.leads || []).find((l) => l.id === f.leadId) : null);
return (
Cancelar
{editing ? "Salvar" : "Agendar tarefa"}>}>
{leadVinc && (
Vinculada ao lead: {leadVinc.nome}{leadVinc.empresa ? " · " + leadVinc.empresa : ""}
)}
set("titulo", e.target.value)} placeholder="Ex.: Ligar para o cliente, enviar proposta…" />
set("data", e.target.value)} />
set("hora", e.target.value)} />
{/* Admin/líder pode atribuir a outra pessoa; comum cria só para si. */}
{isAdmin ? (
) : null}
{!leadFixo && (
)}
{err && {err}
}
);
}
/* ---- WhatsApp nas tarefas ---- */
// Link de WhatsApp a partir de um telefone (adiciona DDI 55 se faltar).
function waTarefa(tel) {
const d = String(tel || "").replace(/\D/g, "");
if (d.length < 10) return null;
return "https://wa.me/" + (d.length <= 11 ? "55" + d : d);
}
// Deixa números de telefone no TEXTO clicáveis — 1 clique abre o WhatsApp.
function TextoComWhats({ texto }) {
const s = String(texto || "");
const re = /(\+?55[\s.-]?)?\(?\d{2}\)?[\s.-]?\d{4,5}[\s.-]?\d{4}/g;
const partes = [];
let last = 0, m;
while ((m = re.exec(s))) {
if (m.index > last) partes.push(s.slice(last, m.index));
const link = waTarefa(m[0]);
if (link) partes.push(
e.stopPropagation()}>{m[0]}
);
else partes.push(m[0]);
last = m.index + m[0].length;
}
if (last < s.length) partes.push(s.slice(last));
return <>{partes}>;
}
/* ---- Linha de uma tarefa ---- */
function TarefaRow({ t, usersById, leadsById, viewer, onToggle, onEdit, onDelete, atrasada }) {
const K = window.KES;
const resp = usersById[t.responsavelId];
const lead = t.leadId ? leadsById[t.leadId] : null;
const waLead = lead ? waTarefa(lead.telefone || lead.telefone2 || lead.telefone3) : null;
const podeMexer = viewer.papel === "admin" || viewer.cargo === "lider" || t.responsavelId === viewer.id || t.criadaPor === viewer.id;
return (
{podeMexer && (
)}
);
}
/* ---- Tela de Tarefas ---- */
function TarefasScreen({ state, dispatch, viewer }) {
const K = window.KES;
const isGestor = viewer.papel === "admin" || viewer.cargo === "lider";
const [modal, setModal] = useState(null); // null | {} | tarefa
const [periodo, setPeriodo] = useState(rangePreset("hojeAmanha")); // filtro de período (topo)
const [verConcluidas, setVerConcluidas] = useState(false); // mostrar/ocultar concluídas
const [confirmDel, setConfirmDel] = useState(null); // tarefa a excluir
const usersById = {}; (state.users || []).forEach((u) => { usersById[u.id] = u; });
const leadsById = {}; (state.leads || []).forEach((l) => { leadsById[l.id] = l; });
const hojeISO = K.iso(K.SIM_TODAY);
const amanhaISO = K.iso(K.addDays(K.SIM_TODAY, 1));
const dataDe = (t) => (t.data || "").slice(0, 10);
// Permissão: gestor vê todas; comum vê só as suas (responsável ou criadas por ele).
const minhas = (state.tarefas || []).filter((t) => isGestor || t.responsavelId === viewer.id || t.criadaPor === viewer.id);
// Esconde concluídas por padrão; o filtro "Concluídas" revela.
const vis = (lista) => verConcluidas ? lista : lista.filter((t) => !t.concluida);
const ordHora = (a, b) => (a.hora || "").localeCompare(b.hora || "");
const ordDataHora = (a, b) => (dataDe(a) + (a.hora || "")).localeCompare(dataDe(b) + (b.hora || ""));
// Atrasadas: pendentes vencidas — sempre visíveis no topo até serem concluídas.
const atrasadas = minhas.filter((t) => !t.concluida && dataDe(t) < hojeISO).sort(ordDataHora);
const ehDefault = periodo.preset === "hojeAmanha";
const hoje = vis(minhas.filter((t) => dataDe(t) === hojeISO)).sort(ordHora);
const amanha = vis(minhas.filter((t) => dataDe(t) === amanhaISO)).sort(ordHora);
const noPeriodo = vis(minhas.filter((t) => { const d = dataDe(t); return d >= periodo.start && d <= periodo.end; })).sort(ordDataHora);
const toggle = (t) => dispatch({ type: "TOGGLE_TAREFA", id: t.id });
const del = (t) => setConfirmDel(t);
function Secao({ titulo, lista, atrasada, vazio }) {
return (
{atrasada && }{titulo}
{lista.length}
{lista.length === 0
?
{vazio}
: lista.map((t) =>
setModal(x)} onDelete={del} atrasada={atrasada || t.data < hojeISO} />)}
);
}
return (
Tarefas
Suas tarefas de hoje e amanhã. As atrasadas ficam visíveis até serem concluídas.
setVerConcluidas((v) => !v)}>Concluídas
setModal({})}>Agendar tarefa
{atrasadas.length > 0 &&
}
{ehDefault ? (
<>
>
) : (
)}
{modal &&
setModal(null)} />}
{confirmDel && (
setConfirmDel(null)}
footer={<>
setConfirmDel(null)}>Cancelar
{ dispatch({ type: "DELETE_TAREFA", id: confirmDel.id }); setConfirmDel(null); }}>Excluir
>}>
Tem certeza que deseja excluir a tarefa "{confirmDel.titulo}"? Essa ação não pode ser desfeita.
)}
);
}
window.TarefasScreen = TarefasScreen;
window.TarefaModal = TarefaModal;