import React, { useEffect, useMemo, useState } from "react";
import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin from "@fullcalendar/interaction";
import ptBrLocale from "@fullcalendar/core/locales/pt-br";
import Swal from "sweetalert2";
import { getAccessToken } from "../../utils/auth.js";
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY;
const API_ROOT = `${supabaseUrl}/rest/v1`;
const API_URL = `${API_ROOT}/doctor_exceptions`;
const API_DOCTORS = `${API_ROOT}/doctors?select=id,full_name`;
const API_KEY = supabaseAK;
export default function Doctorexceçao() {
const token = getAccessToken();
const [exceptions, setExceptions] = useState([]);
const [doctors, setDoctors] = useState([]);
const [loading, setLoading] = useState(true);
const [err, setErr] = useState("");
// ---------- CONFIGURAÇÕES COMUNS ----------
const commonHeaders = {
apikey: API_KEY,
Authorization: `Bearer ${token}`,
};
// ---------- CARREGAR DADOS ----------
const loadExceptions = async () => {
try {
setLoading(true);
setErr("");
const res = await fetch(`${API_URL}?select=*`, { headers: commonHeaders });
if (!res.ok) throw new Error(await res.text());
const data = await res.json();
setExceptions(Array.isArray(data) ? data : []);
} catch (e) {
setErr(e.message || "Erro ao carregar exceções");
} finally {
setLoading(false);
}
};
const loadDoctors = async () => {
try {
const res = await fetch(API_DOCTORS, { headers: commonHeaders });
if (!res.ok) throw new Error(await res.text());
const data = await res.json();
setDoctors(Array.isArray(data) ? data : []);
} catch {
setDoctors([]);
}
};
useEffect(() => {
loadDoctors();
loadExceptions();
}, [token]);
// ---------- CRIAR EXCEÇÃO ----------
const createException = async (payload) => {
try {
const body = {
...payload,
created_by: payload.created_by || payload.doctor_id,
};
const res = await fetch(API_URL, {
method: "POST",
headers: {
...commonHeaders,
"Content-Type": "application/json",
Prefer: "return=representation",
},
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(await res.text());
await res.json();
await loadExceptions();
Swal.fire("Sucesso!", "Exceção criada com sucesso.", "success");
} catch (e) {
Swal.fire("Erro ao criar", e.message || "Falha ao criar exceção", "error");
}
};
// ---------- DELETAR EXCEÇÃO ----------
const deleteException = async (id) => {
const confirm = await Swal.fire({
title: "Excluir exceção?",
text: "Essa ação não pode ser desfeita.",
icon: "warning",
showCancelButton: true,
confirmButtonText: "Sim, excluir",
cancelButtonText: "Cancelar",
});
if (!confirm.isConfirmed) return;
try {
const res = await fetch(`${API_URL}?id=eq.${id}`, {
method: "DELETE",
headers: commonHeaders,
});
if (!res.ok) throw new Error(await res.text());
await loadExceptions();
Swal.fire("Removida!", "Exceção excluída com sucesso.", "success");
} catch (e) {
Swal.fire("Erro ao excluir", e.message || "Falha ao excluir", "error");
}
};
// ---------- EVENTOS DO CALENDÁRIO ----------
const events = useMemo(() => {
return exceptions.map((ex) => {
const isBlock = ex.kind === "bloqueio";
return {
id: ex.id,
title: isBlock ? "Bloqueio" : "Liberação",
start: ex.date,
allDay: true,
backgroundColor: isBlock ? "#ef4444" : "#22c55e",
borderColor: isBlock ? "#b91c1c" : "#15803d",
textColor: "#fff",
};
});
}, [exceptions]);
// ---------- HANDLERS ----------
const handleDateClick = async (info) => {
if (!doctors.length) {
Swal.fire("Sem médicos", "Cadastre médicos antes de criar exceções.", "info");
return;
}
// 1️⃣ Selecionar médico
const doctorOptions = doctors.reduce((acc, d) => {
acc[d.id] = d.full_name || d.id;
return acc;
}, {});
const s1 = await Swal.fire({
title: `Nova exceção — ${info.dateStr}`,
input: "select",
inputOptions: doctorOptions,
inputPlaceholder: "Selecione o médico",
showCancelButton: true,
confirmButtonText: "Continuar",
didOpen: (popup) => {
popup.style.position = "fixed";
popup.style.top = "230px";
}
});
if (!s1.isConfirmed || !s1.value) return;
const doctor_id = s1.value;
// 2️⃣ Tipo da exceção
const s2 = await Swal.fire({
title: "Tipo de exceção",
input: "select",
inputOptions: {
bloqueio: "Bloqueio (remover horários)",
liberacao: "Liberação (adicionar horários extras)",
},
inputPlaceholder: "Selecione o tipo",
showCancelButton: true,
confirmButtonText: "Continuar",
didOpen: (popup) => {
popup.style.position = "fixed";
popup.style.top = "230px";
}
});
if (!s2.isConfirmed || !s2.value) return;
const kind = s2.value;
// 3️⃣ Motivo
const form = await Swal.fire({
title: "Motivo (opcional)",
input: "text",
inputPlaceholder: "Ex: Congresso, folga, manutenção...",
showCancelButton: true,
confirmButtonText: "Criar exceção",
didOpen: (popup) => {
popup.style.position = "fixed";
popup.style.top = "230px";
}
});
if (!form.isConfirmed) return;
const payload = {
doctor_id,
created_by: doctor_id,
date: info.dateStr,
kind,
reason: form.value || null,
};
await createException(payload);
};
const handleEventClick = async (info) => {
const e = exceptions.find((x) => x.id === info.event.id);
if (!e) return;
await Swal.fire({
title: e.kind === "bloqueio" ? "Bloqueio" : "Liberação",
html: `Médico: ${
doctors.find((d) => d.id === e.doctor_id)?.full_name || e.doctor_id
}
Data: ${e.date}
Motivo: ${e.reason || "-"}`,
icon: "info",
showCancelButton: true,
confirmButtonText: "Excluir",
cancelButtonText: "Fechar",
}).then((r) => {
if (r.isConfirmed) deleteException(e.id);
});
};
// ---------- UI ----------
return (
Carregando calendário…
) : err ? (Erro: {err}
) : (Carregando lista…
) : err ? (Erro: {err}
) : exceptions.length === 0 ? (Nenhuma exceção encontrada.
) : (| Médico | Data | Tipo | Motivo | Ações |
|---|---|---|---|---|
| {doctors.find((d) => d.id === ex.doctor_id)?.full_name || ex.doctor_id} | {ex.date} | {ex.kind === "bloqueio" ? ( Bloqueio ) : ( Liberação )} | {ex.reason || "-"} |
* Vermelho = Bloqueio, Verde = Liberação.