// Datas específicas (não consecutivas) de um evento — migration 060. // // Evento de "período contínuo" continua usando event_date..event_end_date. // Evento de "datas específicas" guarda em events.datas_especificas a lista // [{ data: '2026-10-15', inicio: '09:00', fim: '12:00' }, ...] e mantém // event_date = primeira data e event_end_date = última, para as telas que só // exibem ou ordenam pelo início continuarem funcionando. // Lista válida e ordenada, ou null quando o evento é período contínuo. function evDatasDe(ev) { const lista = ev && (ev.datas_especificas || ev.datas); return Array.isArray(lista) && lista.length ? lista : null; } // O evento acontece no dia `iso` ('AAAA-MM-DD')? function evOcorreNoDia(ev, iso, inicio, fim) { const datas = evDatasDe(ev); if (datas) return datas.some(d => d.data === iso); return !!inicio && iso >= inicio && iso <= (fim || inicio); } const evFaixaHora = (d) => d && d.inicio ? `${d.inicio}${d.fim ? ' às ' + d.fim : ''}` : ''; // Um mesmo dia pode ter mais de um turno (16/10 11:00–14:00 e 17:00–20:00). // Dias distintos, na ordem da lista. const evDiasUnicos = (datas) => [...new Set((datas || []).map(d => d.data))]; // Todos os turnos do dia `iso`: "11:00 às 14:00 e 17:00 às 20:00". function evFaixasDoDia(datas, iso) { const faixas = (datas || []).filter(d => d.data === iso).map(evFaixaHora).filter(Boolean); return faixas.length > 1 ? `${faixas.slice(0, -1).join(', ')} e ${faixas[faixas.length - 1]}` : (faixas[0] || ''); } // "15, 18 e 21/10" — agrupa por mês: "28/09, 02 e 05/10" function evResumoDatas(datas) { if (!datas || !datas.length) return ''; const porMes = []; evDiasUnicos(datas).forEach(iso => { const [, mes, dia] = iso.split('-'); const grupo = porMes.find(g => g.mes === mes); if (grupo) grupo.dias.push(dia); else porMes.push({ mes, dias: [dia] }); }); const juntar = (arr) => arr.length > 1 ? `${arr.slice(0, -1).join(', ')} e ${arr[arr.length - 1]}` : arr[0]; return porMes.map(g => `${juntar(g.dias)}/${g.mes}`).join(', '); } // Ordena (dia, depois início), tira linhas sem data e acusa linha repetida, // horário invertido e turnos que se sobrepõem no mesmo dia. Devolve { datas, erro }. function evNormalizarDatas(lista) { const validas = (lista || []).filter(d => d.data); if (!validas.length) return { datas: [], erro: 'Adicione pelo menos uma data.' }; const fmt = (iso) => iso.split('-').reverse().join('/'); const unicas = [...new Map(validas.map(d => { const item = { data: d.data, inicio: d.inicio || '', fim: d.fim || '' }; return [`${item.data}|${item.inicio}|${item.fim}`, item]; })).values()] .sort((a, b) => a.data.localeCompare(b.data) || a.inicio.localeCompare(b.inicio)); if (unicas.length < validas.length) return { datas: unicas, erro: 'Há uma data repetida com o mesmo horário.' }; const horaErrada = unicas.find(d => d.inicio && d.fim && d.fim <= d.inicio); if (horaErrada) return { datas: unicas, erro: `Em ${fmt(horaErrada.data)} o horário de fim é antes do início.` }; // Mesmo dia em dois turnos: o segundo tem que começar depois que o primeiro acaba. const sobreposta = unicas.find((d, i) => { const ant = unicas[i - 1]; return ant && ant.data === d.data && (!ant.fim || !d.inicio || d.inicio < ant.fim); }); if (sobreposta) return { datas: unicas, erro: `Em ${fmt(sobreposta.data)} os horários se sobrepõem. Preencha início e fim de cada turno sem sobreposição.` }; return { datas: unicas, erro: null }; } // Campos de data do evento a gravar, a partir do formulário (formato, date, date_end, time, datas). function evCamposDeData(form) { if (form.formato !== 'especificas') { return { campos: { event_date: form.date, event_end_date: form.date_end || null, event_time: form.time, datas_especificas: null }, erro: null }; } const { datas, erro } = evNormalizarDatas(form.datas); if (erro) return { campos: null, erro }; const dias = evDiasUnicos(datas).length; return { erro: null, campos: { event_date: datas[0].data, event_end_date: dias > 1 ? datas[datas.length - 1].data : null, event_time: evFaixaHora(datas[0]) || form.time, duration: `${dias} diária${dias > 1 ? 's' : ''}`, datas_especificas: datas, }, }; } const evdInput = { border: `1px solid ${DS.colors.border}`, borderRadius: DS.radius.sm, padding: '8px 10px', fontSize: 13, fontFamily: 'inherit', background: '#fff', minWidth: 0 }; // Seletor "Período contínuo / Datas específicas" + campos de cada formato. function EventoDatasCampos({ form, setForm }) { const formato = form.formato || 'periodo'; const datas = form.datas && form.datas.length ? form.datas : [{ data: form.date || '', inicio: form.time || '09:00', fim: '' }]; const setDatas = (fn) => setForm(f => ({ ...f, datas: fn(f.datas && f.datas.length ? f.datas : datas) })); const atualizar = (i, campo, v) => setDatas(arr => arr.map((d, j) => j === i ? { ...d, [campo]: v } : d)); const adicionar = () => setDatas(arr => { const ultima = arr[arr.length - 1] || {}; return [...arr, { data: '', inicio: ultima.inicio || '09:00', fim: ultima.fim || '' }]; }); const remover = (i) => setDatas(arr => arr.length > 1 ? arr.filter((_, j) => j !== i) : arr); const replicarHorario = () => setDatas(arr => arr.map(d => ({ ...d, inicio: arr[0].inicio, fim: arr[0].fim }))); const escolher = (f) => setForm(prev => ({ ...prev, formato: f, datas: prev.datas && prev.datas.length ? prev.datas : datas })); return (