const PID_IC_WEEKDAYS = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"];
function pidIsoDate(year, month, day) {
const mm = String(month + 1).padStart(2, "0");
const dd = String(day).padStart(2, "0");
return `${year}-${mm}-${dd}`;
}
/** Categoria de exibição/filtro de um compromisso — não é só `ev.visibilidade`:
* um "somente_eu" que não é meu só pode ter chegado por eu liderar o dono (o
* backend só devolve isso pra gerente/coordenador), então vira "equipe". */
function pidEventoCategoria(ev) {
if (ev.eh_evento) return "evento";
if (ev.visibilidade === "somente_eu") {
return ev.sou_dono ? "somente_eu" : "equipe";
}
return ev.visibilidade;
}
const PID_IC_VISIBILIDADE_LABELS = {
somente_eu: "Somente eu",
departamento: "Meu departamento",
todos: "Todos",
};
const PID_IC_MODALIDADE_LABELS = {
presencial: "Presencial",
remoto: "Remoto",
hibrido: "Híbrido",
};
document.addEventListener("DOMContentLoaded", async () => {
const grid = document.getElementById("ic-grid");
if (!grid) return;
const me = await pidGetMe();
if (!me) return;
const titleEl = document.getElementById("ic-title");
const prevBtn = document.getElementById("ic-prev");
const nextBtn = document.getElementById("ic-next");
const todayBtn = document.getElementById("ic-today-btn");
const newBtn = document.getElementById("ic-new-btn");
const modal = document.getElementById("ic-modal");
const modalTitle = document.getElementById("ic-modal-title");
const modalTitleInput = document.getElementById("ic-event-title");
const modalDateInput = document.getElementById("ic-event-date");
const modalTimeInput = document.getElementById("ic-event-time");
const modalVisibilityField = document.getElementById("ic-event-visibility-field");
const modalVisibilitySelect = document.getElementById("ic-event-visibility");
const modalDepartmentField = document.getElementById("ic-event-department-field");
const modalDepartmentSelect = document.getElementById("ic-event-department");
const modalCategoriaSelect = document.getElementById("ic-event-categoria");
const modalCategoriaAddBtn = document.getElementById("ic-event-categoria-add-btn");
const modalEhEventoField = document.getElementById("ic-event-eh-evento-field");
const modalEhEventoCheckbox = document.getElementById("ic-event-eh-evento");
const modalLocalInput = document.getElementById("ic-event-local");
const modalModalidadeSelect = document.getElementById("ic-event-modalidade");
const modalDescricaoInput = document.getElementById("ic-event-descricao");
const modalReminderSelect = document.getElementById("ic-event-reminder");
const modalError = document.getElementById("ic-event-error");
const modalSaveBtn = document.getElementById("ic-event-save-btn");
const modalCancelBtn = document.getElementById("ic-event-cancel-btn");
const modalDeleteBtn = document.getElementById("ic-event-delete-btn");
const categoriaModal = document.getElementById("ic-categoria-modal");
const categoriaNomeInput = document.getElementById("ic-categoria-nome");
const categoriaCorInput = document.getElementById("ic-categoria-cor");
const categoriaError = document.getElementById("ic-categoria-error");
const categoriaSaveBtn = document.getElementById("ic-categoria-save-btn");
const categoriaCancelBtn = document.getElementById("ic-categoria-cancel-btn");
const viewModal = document.getElementById("ic-view-modal");
const viewTitle = document.getElementById("ic-view-title");
const viewDatetime = document.getElementById("ic-view-datetime");
const viewOwner = document.getElementById("ic-view-owner");
const viewVisibility = document.getElementById("ic-view-visibility");
const viewDepartmentField = document.getElementById("ic-view-department-field");
const viewDepartment = document.getElementById("ic-view-department");
const viewCategoriaField = document.getElementById("ic-view-categoria-field");
const viewCategoriaDot = document.getElementById("ic-view-categoria-dot");
const viewCategoria = document.getElementById("ic-view-categoria");
const viewLocalField = document.getElementById("ic-view-local-field");
const viewLocal = document.getElementById("ic-view-local");
const viewModalidadeField = document.getElementById("ic-view-modalidade-field");
const viewModalidade = document.getElementById("ic-view-modalidade");
const viewDescricaoField = document.getElementById("ic-view-descricao-field");
const viewDescricao = document.getElementById("ic-view-descricao");
const viewCloseBtn = document.getElementById("ic-view-close-btn");
const podeCriarEventoAmplo = Boolean(
me.permissoes_efetivas?.["calendario-individual"]?.apps?.["calendario-individual-criar-evento"]
);
modalCategoriaAddBtn.hidden = !podeCriarEventoAmplo;
modalEhEventoField.hidden = !podeCriarEventoAmplo;
modalVisibilitySelect.querySelectorAll('option[value="departamento"], option[value="todos"]').forEach((opt) => {
opt.disabled = !podeCriarEventoAmplo;
});
function updateEhEventoFieldsVisibility() {
if (modalEhEventoCheckbox.checked) {
modalVisibilitySelect.value = "todos";
modalVisibilityField.hidden = true;
} else {
modalVisibilityField.hidden = false;
}
updateDepartmentFieldVisibility();
}
modalEhEventoCheckbox.addEventListener("change", updateEhEventoFieldsVisibility);
let categorias = await pidFetchCategoriasEvento();
function popularSelectCategorias(selecionarId) {
modalCategoriaSelect.innerHTML =
'' +
categorias.map((c) => ``).join("");
if (selecionarId) modalCategoriaSelect.value = String(selecionarId);
}
popularSelectCategorias();
const holidayModal = document.getElementById("ic-holiday-modal");
const holidayDate = document.getElementById("ic-holiday-date");
const holidayName = document.getElementById("ic-holiday-name");
const holidayCloseBtn = document.getElementById("ic-holiday-close-btn");
const dayModal = document.getElementById("ic-day-modal");
const dayModalTitle = document.getElementById("ic-day-title");
const dayModalList = document.getElementById("ic-day-list");
const dayModalEmpty = document.getElementById("ic-day-empty");
const dayModalAddBtn = document.getElementById("ic-day-add-btn");
const dayModalCloseBtn = document.getElementById("ic-day-close-btn");
let dayModalIso = null;
const meusDepartamentos = me.departamentos || [];
modalDepartmentSelect.innerHTML = meusDepartamentos
.map((d) => ``)
.join("");
function updateDepartmentFieldVisibility() {
modalDepartmentField.hidden = modalVisibilitySelect.value !== "departamento";
}
modalVisibilitySelect.addEventListener("change", updateDepartmentFieldVisibility);
modalCategoriaAddBtn.addEventListener("click", () => {
categoriaNomeInput.value = "";
categoriaCorInput.value = "#7c4dff";
categoriaError.textContent = "";
categoriaModal.hidden = false;
categoriaNomeInput.focus();
});
categoriaCancelBtn.addEventListener("click", () => {
categoriaModal.hidden = true;
});
categoriaModal.addEventListener("click", (event) => {
if (event.target === categoriaModal) categoriaModal.hidden = true;
});
categoriaSaveBtn.addEventListener("click", async () => {
const nome = categoriaNomeInput.value.trim();
if (!nome) {
categoriaError.textContent = "Informe o nome da categoria.";
return;
}
try {
const nova = await pidCreateCategoriaEvento({ nome, cor: categoriaCorInput.value });
categorias.push(nova);
popularSelectCategorias(nova.id);
categoriaModal.hidden = true;
} catch (e) {
categoriaError.textContent = e.message;
}
});
const filtersContainer = document.getElementById("ic-filters");
const filterEquipeChip = document.getElementById("ic-filter-equipe");
filterEquipeChip.hidden = !me.lideranca;
const activeFilters = new Set();
function selecionarTodosOsFiltros() {
activeFilters.clear();
filtersContainer.querySelectorAll(".filter-chip").forEach((c) => {
const visivel = !c.hidden;
c.classList.toggle("is-active", visivel);
if (visivel) activeFilters.add(c.dataset.filter);
});
}
selecionarTodosOsFiltros();
filtersContainer.addEventListener("click", (event) => {
const chip = event.target.closest(".filter-chip");
if (!chip) return;
const filtro = chip.dataset.filter;
if (event.shiftKey) {
// Shift mantido: acrescenta/remove esse filtro aos já selecionados.
if (activeFilters.has(filtro)) {
activeFilters.delete(filtro);
chip.classList.remove("is-active");
} else {
activeFilters.add(filtro);
chip.classList.add("is-active");
}
} else if (activeFilters.size === 1 && activeFilters.has(filtro)) {
// Clicar de novo no único filtro já selecionado volta pra visualização padrão (todos selecionados).
selecionarTodosOsFiltros();
} else {
// Clique simples: troca a seleção pra só esse filtro.
activeFilters.clear();
activeFilters.add(filtro);
filtersContainer.querySelectorAll(".filter-chip").forEach((c) => {
c.classList.toggle("is-active", c.dataset.filter === filtro);
});
}
render();
});
const today = new Date();
let viewYear = today.getFullYear();
let viewMonth = today.getMonth();
let editingEventId = null;
let events = [];
function openModal(prefillDate, existingEvent) {
if (existingEvent) {
editingEventId = existingEvent.id;
modalTitle.textContent = "Editar Compromisso";
modalTitleInput.value = existingEvent.titulo;
modalDateInput.value = existingEvent.data;
modalTimeInput.value = existingEvent.horario ? existingEvent.horario.slice(0, 5) : "";
modalVisibilitySelect.value = existingEvent.visibilidade || "somente_eu";
modalDepartmentSelect.value = existingEvent.departamento_compartilhado || "";
modalReminderSelect.value = existingEvent.lembrete_antecedencia || "";
popularSelectCategorias(existingEvent.categoria);
modalLocalInput.value = existingEvent.local || "";
modalModalidadeSelect.value = existingEvent.modalidade || "";
modalDescricaoInput.value = existingEvent.descricao || "";
modalEhEventoCheckbox.checked = Boolean(existingEvent.eh_evento);
modalDeleteBtn.hidden = false;
} else {
editingEventId = null;
modalTitle.textContent = "Novo Compromisso";
modalTitleInput.value = "";
modalDateInput.value = prefillDate || pidIsoDate(today.getFullYear(), today.getMonth(), today.getDate());
modalTimeInput.value = "";
modalVisibilitySelect.value = "somente_eu";
modalDepartmentSelect.value = meusDepartamentos[0] ? String(meusDepartamentos[0].id) : "";
modalReminderSelect.value = "";
popularSelectCategorias();
modalLocalInput.value = "";
modalModalidadeSelect.value = "";
modalDescricaoInput.value = "";
modalEhEventoCheckbox.checked = false;
modalDeleteBtn.hidden = true;
}
updateEhEventoFieldsVisibility();
modalError.textContent = "";
modal.hidden = false;
modalTitleInput.focus();
}
function closeModal() {
modal.hidden = true;
}
function openViewModal(ev) {
const horario = ev.horario ? ev.horario.slice(0, 5) : "";
viewTitle.textContent = ev.titulo;
viewDatetime.textContent = horario
? `${new Date(`${ev.data}T00:00:00`).toLocaleDateString("pt-BR")} às ${horario}`
: new Date(`${ev.data}T00:00:00`).toLocaleDateString("pt-BR");
viewOwner.textContent = ev.dono_nome;
viewVisibility.textContent = PID_IC_VISIBILIDADE_LABELS[ev.visibilidade] || ev.visibilidade;
viewDepartmentField.hidden = ev.visibilidade !== "departamento";
viewDepartment.textContent = ev.departamento_compartilhado_nome || "";
viewCategoriaField.hidden = !ev.categoria_nome;
viewCategoriaDot.style.background = ev.categoria_cor || "transparent";
viewCategoria.textContent = ev.categoria_nome || "";
viewLocalField.hidden = !ev.local;
viewLocal.textContent = ev.local || "";
viewModalidadeField.hidden = !ev.modalidade;
viewModalidade.textContent = PID_IC_MODALIDADE_LABELS[ev.modalidade] || ev.modalidade || "";
viewDescricaoField.hidden = !ev.descricao;
viewDescricao.textContent = ev.descricao || "";
viewModal.hidden = false;
}
viewCloseBtn.addEventListener("click", () => {
viewModal.hidden = true;
});
function openHolidayModal(iso, nome) {
dayModal.hidden = true;
holidayDate.textContent = new Date(`${iso}T00:00:00`).toLocaleDateString("pt-BR");
holidayName.textContent = nome;
holidayModal.hidden = false;
}
holidayCloseBtn.addEventListener("click", () => {
holidayModal.hidden = true;
});
/** Pill de compromisso reaproveitado tanto na grade mensal quanto na agenda
* do dia (popup aberto ao clicar no número do dia) — mesmo visual, mesmo
* comportamento de clique (editar o próprio, ver detalhes dos demais). */
function criarPillCompromisso(ev) {
const categoria = pidEventoCategoria(ev).replace("_", "-");
const horario = ev.horario ? ev.horario.slice(0, 5) : "";
const pill = document.createElement("span");
pill.className = `calendar-event calendar-event--${categoria}`;
pill.textContent = horario ? `${horario} ${ev.titulo}` : ev.titulo;
pill.title = ev.sou_dono
? `${ev.titulo}${horario ? ` às ${horario}` : ""} (clique para editar)`
: `${ev.titulo} — ${ev.dono_nome} (clique para ver detalhes)`;
if (ev.sou_dono) {
pill.addEventListener("click", () => {
dayModal.hidden = true;
openModal(ev.data, ev);
});
} else {
pill.addEventListener("click", () => {
dayModal.hidden = true;
openViewModal(ev);
});
}
return pill;
}
function criarPillFeriado(iso, nome) {
const holidayLabel = document.createElement("span");
holidayLabel.className = "calendar-day__holiday";
holidayLabel.textContent = nome;
holidayLabel.title = `${nome} (clique para ver o nome completo)`;
holidayLabel.addEventListener("click", () => openHolidayModal(iso, nome));
return holidayLabel;
}
function openDayModal(iso, cellDate, nomeFeriado, eventosDoDia) {
dayModalIso = iso;
dayModalTitle.textContent = cellDate.toLocaleDateString("pt-BR", {
day: "numeric",
month: "long",
year: "numeric",
});
dayModalList.innerHTML = "";
if (nomeFeriado) dayModalList.appendChild(criarPillFeriado(iso, nomeFeriado));
eventosDoDia.forEach((ev) => dayModalList.appendChild(criarPillCompromisso(ev)));
dayModalEmpty.hidden = Boolean(nomeFeriado) || eventosDoDia.length > 0;
dayModal.hidden = false;
}
dayModalAddBtn.addEventListener("click", () => {
dayModal.hidden = true;
openModal(dayModalIso, null);
});
dayModalCloseBtn.addEventListener("click", () => {
dayModal.hidden = true;
});
modal.addEventListener("click", (event) => {
if (event.target === modal) closeModal();
});
viewModal.addEventListener("click", (event) => {
if (event.target === viewModal) viewModal.hidden = true;
});
holidayModal.addEventListener("click", (event) => {
if (event.target === holidayModal) holidayModal.hidden = true;
});
dayModal.addEventListener("click", (event) => {
if (event.target === dayModal) dayModal.hidden = true;
});
async function reloadEvents() {
events = await pidFetchEvents();
}
const feriadosPorAno = new Map();
async function carregarFeriados(ano) {
if (!feriadosPorAno.has(ano)) {
const lista = await pidApiRequest(`/feriados/?ano=${ano}`);
feriadosPorAno.set(ano, new Map((lista || []).map((f) => [f.data, f.nome])));
}
return feriadosPorAno.get(ano);
}
async function render() {
// Os dias "fora do mês" no início/fim da grade podem cair no ano anterior/seguinte
// (ex.: dezembro exibe dias de janeiro do ano seguinte) — só busca esses anos extras
// quando o mês exibido realmente encosta numa dessas bordas.
const anosNecessarios = new Set([viewYear]);
if (viewMonth === 0) anosNecessarios.add(viewYear - 1);
if (viewMonth === 11) anosNecessarios.add(viewYear + 1);
const mapasFeriados = await Promise.all(Array.from(anosNecessarios).map(carregarFeriados));
const feriados = new Map();
mapasFeriados.forEach((mapa) => mapa.forEach((nome, data) => feriados.set(data, nome)));
const firstWeekday = new Date(viewYear, viewMonth, 1).getDay();
const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
const daysInPrevMonth = new Date(viewYear, viewMonth, 0).getDate();
const isCurrentMonth = viewYear === today.getFullYear() && viewMonth === today.getMonth();
titleEl.textContent = new Date(viewYear, viewMonth, 1).toLocaleDateString("pt-BR", {
month: "long",
year: "numeric",
});
const cells = [];
for (let i = firstWeekday - 1; i >= 0; i -= 1) {
cells.push({ day: daysInPrevMonth - i, outside: true, monthOffset: -1 });
}
for (let d = 1; d <= daysInMonth; d += 1) {
cells.push({ day: d, outside: false, monthOffset: 0 });
}
while (cells.length % 7 !== 0) {
cells.push({ day: cells.length - (firstWeekday + daysInMonth) + 1, outside: true, monthOffset: 1 });
}
grid.querySelectorAll(".calendar-day").forEach((el) => el.remove());
cells.forEach((cell) => {
const cellDate = new Date(viewYear, viewMonth + cell.monthOffset, cell.day);
const iso = pidIsoDate(cellDate.getFullYear(), cellDate.getMonth(), cellDate.getDate());
const nomeFeriado = feriados.get(iso);
const eventosDoDia = events
.filter((ev) => ev.data === iso && activeFilters.has(pidEventoCategoria(ev)))
.sort((a, b) => (a.horario || "").localeCompare(b.horario || ""));
const dayEl = document.createElement("div");
dayEl.className = "calendar-day" + (cell.outside ? " is-outside" : "");
if (!cell.outside && isCurrentMonth && cell.day === today.getDate()) {
dayEl.classList.add("is-today");
}
if (nomeFeriado) {
dayEl.classList.add("is-feriado");
}
const header = document.createElement("div");
header.className = "calendar-day__header";
const number = document.createElement("span");
number.className = "calendar-day__number";
number.textContent = cell.day;
number.title = "Ver agenda completa do dia";
number.addEventListener("click", () => openDayModal(iso, cellDate, nomeFeriado, eventosDoDia));
header.appendChild(number);
const addBtn = document.createElement("button");
addBtn.type = "button";
addBtn.className = "calendar-day__add";
addBtn.setAttribute("aria-label", "Novo compromisso");
addBtn.innerHTML =
'';
addBtn.addEventListener("click", () => openModal(iso));
header.appendChild(addBtn);
dayEl.appendChild(header);
if (nomeFeriado) {
dayEl.appendChild(criarPillFeriado(iso, nomeFeriado));
}
const eventsEl = document.createElement("div");
eventsEl.className = "calendar-day__events";
eventosDoDia.forEach((ev) => eventsEl.appendChild(criarPillCompromisso(ev)));
dayEl.appendChild(eventsEl);
grid.appendChild(dayEl);
});
}
PID_IC_WEEKDAYS.forEach((label) => {
const el = document.createElement("div");
el.className = "calendar-grid__weekday";
el.textContent = label;
grid.appendChild(el);
});
newBtn.addEventListener("click", () => openModal(null, null));
modalCancelBtn.addEventListener("click", closeModal);
modalDeleteBtn.addEventListener("click", async () => {
if (!editingEventId) return;
if (await pidConfirm("Excluir este compromisso?", { perigoso: true })) {
try {
await pidDeleteEvent(editingEventId);
closeModal();
await reloadEvents();
render();
} catch (e) {
modalError.textContent = e.message;
}
}
});
modalSaveBtn.addEventListener("click", async () => {
const titulo = modalTitleInput.value.trim();
const data = modalDateInput.value;
const horario = modalTimeInput.value;
const visibilidade = modalVisibilitySelect.value;
if (!titulo || !data) {
modalError.textContent = "Informe o título e a data do compromisso.";
return;
}
if (visibilidade === "departamento" && !modalDepartmentSelect.value) {
modalError.textContent = "Selecione um departamento para compartilhar o compromisso, ou você não tem nenhum departamento vinculado.";
return;
}
const payload = {
titulo,
data,
horario: horario || null,
visibilidade,
departamento_compartilhado: visibilidade === "departamento" ? Number(modalDepartmentSelect.value) : null,
lembrete_antecedencia: modalReminderSelect.value,
categoria: modalCategoriaSelect.value ? Number(modalCategoriaSelect.value) : null,
eh_evento: modalEhEventoCheckbox.checked,
local: modalLocalInput.value.trim(),
modalidade: modalModalidadeSelect.value,
descricao: modalDescricaoInput.value.trim(),
};
try {
if (editingEventId) {
await pidUpdateEvent(editingEventId, payload);
} else {
await pidCreateEvent(payload);
}
closeModal();
await reloadEvents();
render();
} catch (e) {
modalError.textContent = e.message;
}
});
prevBtn.addEventListener("click", () => {
viewMonth -= 1;
if (viewMonth < 0) {
viewMonth = 11;
viewYear -= 1;
}
render();
});
nextBtn.addEventListener("click", () => {
viewMonth += 1;
if (viewMonth > 11) {
viewMonth = 0;
viewYear += 1;
}
render();
});
todayBtn.addEventListener("click", () => {
viewYear = today.getFullYear();
viewMonth = today.getMonth();
render();
});
await reloadEvents();
render();
});