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 (

Exceções (Bloqueios / Liberações)

Clique numa data para adicionar exceções por médico
{/* Calendário */}
{loading ? (

Carregando calendário…

) : err ? (

Erro: {err}

) : ( )}
{/* Lista de exceções */}
Lista de Exceções
{exceptions.length} registro(s)
{loading ? (

Carregando lista…

) : err ? (

Erro: {err}

) : exceptions.length === 0 ? (

Nenhuma exceção encontrada.

) : (
{exceptions.map((ex) => ( ))}
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.

); }