develop #83

Merged
M-Gabrielly merged 426 commits from develop into main 2025-12-04 04:13:15 +00:00
3 changed files with 596 additions and 0 deletions
Showing only changes of commit a9bbd2f872 - Show all commits

View File

@ -10,6 +10,8 @@ import { Label } from "@/components/ui/label";
import { MoreHorizontal, Plus, Search, Edit, Trash2, ArrowLeft, Eye, Users } from "lucide-react"; import { MoreHorizontal, Plus, Search, Edit, Trash2, ArrowLeft, Eye, Users } from "lucide-react";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { DoctorRegistrationForm } from "@/components/forms/doctor-registration-form"; import { DoctorRegistrationForm } from "@/components/forms/doctor-registration-form";
import AvailabilityForm from '@/components/forms/availability-form'
import { listarDisponibilidades, DoctorAvailability, deletarDisponibilidade } from '@/lib/api'
import { listarMedicos, excluirMedico, buscarMedicos, buscarMedicoPorId, buscarPacientesPorIds, Medico } from "@/lib/api"; import { listarMedicos, excluirMedico, buscarMedicos, buscarMedicoPorId, buscarPacientesPorIds, Medico } from "@/lib/api";
@ -57,6 +59,28 @@ function normalizeMedico(m: any): Medico {
}; };
} }
function translateWeekday(w?: string) {
if (!w) return '';
const key = w.toString().toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, '').replace(/[^a-z0-9]/g, '');
const map: Record<string, string> = {
'segunda': 'Segunda',
'terca': 'Terça',
'quarta': 'Quarta',
'quinta': 'Quinta',
'sexta': 'Sexta',
'sabado': 'Sábado',
'domingo': 'Domingo',
'monday': 'Segunda',
'tuesday': 'Terça',
'wednesday': 'Quarta',
'thursday': 'Quinta',
'friday': 'Sexta',
'saturday': 'Sábado',
'sunday': 'Domingo',
};
return map[key] ?? w;
}
export default function DoutoresPage() { export default function DoutoresPage() {
const [doctors, setDoctors] = useState<Medico[]>([]); const [doctors, setDoctors] = useState<Medico[]>([]);
@ -69,6 +93,11 @@ export default function DoutoresPage() {
const [assignedPatients, setAssignedPatients] = useState<any[]>([]); const [assignedPatients, setAssignedPatients] = useState<any[]>([]);
const [assignedLoading, setAssignedLoading] = useState(false); const [assignedLoading, setAssignedLoading] = useState(false);
const [assignedDoctor, setAssignedDoctor] = useState<Medico | null>(null); const [assignedDoctor, setAssignedDoctor] = useState<Medico | null>(null);
const [availabilityOpenFor, setAvailabilityOpenFor] = useState<Medico | null>(null);
const [availabilityViewingFor, setAvailabilityViewingFor] = useState<Medico | null>(null);
const [availabilities, setAvailabilities] = useState<DoctorAvailability[]>([]);
const [availLoading, setAvailLoading] = useState(false);
const [editingAvailability, setEditingAvailability] = useState<DoctorAvailability | null>(null);
const [searchResults, setSearchResults] = useState<Medico[]>([]); const [searchResults, setSearchResults] = useState<Medico[]>([]);
const [searchMode, setSearchMode] = useState(false); const [searchMode, setSearchMode] = useState(false);
const [searchTimeout, setSearchTimeout] = useState<NodeJS.Timeout | null>(null); const [searchTimeout, setSearchTimeout] = useState<NodeJS.Timeout | null>(null);
@ -280,6 +309,19 @@ export default function DoutoresPage() {
} }
} }
async function reloadAvailabilities(doctorId?: string) {
if (!doctorId) return;
setAvailLoading(true);
try {
const list = await listarDisponibilidades({ doctorId, active: true });
setAvailabilities(list || []);
} catch (e) {
console.warn('Erro ao recarregar disponibilidades:', e);
} finally {
setAvailLoading(false);
}
}
async function handleDelete(id: string) { async function handleDelete(id: string) {
if (!confirm("Excluir este médico?")) return; if (!confirm("Excluir este médico?")) return;
@ -433,6 +475,27 @@ export default function DoutoresPage() {
Ver pacientes atribuídos Ver pacientes atribuídos
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => setAvailabilityOpenFor(doctor)}>
<Plus className="mr-2 h-4 w-4" />
Criar disponibilidade
</DropdownMenuItem>
<DropdownMenuItem onClick={async () => {
setAvailLoading(true);
try {
const list = await listarDisponibilidades({ doctorId: doctor.id, active: true });
setAvailabilities(list || []);
setAvailabilityViewingFor(doctor);
} catch (e) {
console.warn('Erro ao listar disponibilidades:', e);
} finally {
setAvailLoading(false);
}
}}>
<Users className="mr-2 h-4 w-4" />
Ver disponibilidades
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleEdit(String(doctor.id))}> <DropdownMenuItem onClick={() => handleEdit(String(doctor.id))}>
<Edit className="mr-2 h-4 w-4" /> <Edit className="mr-2 h-4 w-4" />
Editar Editar
@ -497,6 +560,79 @@ export default function DoutoresPage() {
</Dialog> </Dialog>
)} )}
{/* Availability modal */}
{availabilityOpenFor && (
<AvailabilityForm
open={!!availabilityOpenFor}
onOpenChange={(open) => { if (!open) setAvailabilityOpenFor(null); }}
doctorId={availabilityOpenFor?.id}
onSaved={(saved) => { console.log('Disponibilidade salva', saved); setAvailabilityOpenFor(null); /* optionally reload list */ reloadAvailabilities(availabilityOpenFor?.id); }}
/>
)}
{/* Edit availability modal */}
{editingAvailability && (
<AvailabilityForm
open={!!editingAvailability}
onOpenChange={(open) => { if (!open) setEditingAvailability(null); }}
doctorId={editingAvailability?.doctor_id ?? availabilityViewingFor?.id}
availability={editingAvailability}
mode="edit"
onSaved={(saved) => { console.log('Disponibilidade atualizada', saved); setEditingAvailability(null); reloadAvailabilities(editingAvailability?.doctor_id ?? availabilityViewingFor?.id); }}
/>
)}
{/* Ver disponibilidades dialog */}
{availabilityViewingFor && (
<Dialog open={!!availabilityViewingFor} onOpenChange={(open) => { if (!open) { setAvailabilityViewingFor(null); setAvailabilities([]); } }}>
<DialogContent>
<DialogHeader>
<DialogTitle>Disponibilidades - {availabilityViewingFor.full_name}</DialogTitle>
<DialogDescription>
Lista de disponibilidades públicas do médico selecionado.
</DialogDescription>
</DialogHeader>
<div className="py-4">
{availLoading ? (
<div>Carregando disponibilidades</div>
) : availabilities && availabilities.length ? (
<div className="space-y-2">
{availabilities.map((a) => (
<div key={String(a.id)} className="p-2 border rounded flex justify-between items-start">
<div>
<div className="font-medium">{translateWeekday(a.weekday)} {a.start_time} {a.end_time}</div>
<div className="text-xs text-muted-foreground">Duração: {a.slot_minutes} min Tipo: {a.appointment_type || '—'} {a.active ? 'Ativa' : 'Inativa'}</div>
</div>
<div className="flex gap-2">
<Button size="sm" variant="outline" onClick={() => setEditingAvailability(a)}>Editar</Button>
<Button size="sm" variant="destructive" onClick={async () => {
if (!confirm('Excluir esta disponibilidade?')) return;
try {
await deletarDisponibilidade(String(a.id));
// reload
reloadAvailabilities(availabilityViewingFor?.id ?? a.doctor_id);
} catch (e) {
console.warn('Erro ao deletar disponibilidade:', e);
alert((e as any)?.message || 'Erro ao deletar disponibilidade');
}
}}>Excluir</Button>
</div>
</div>
))}
</div>
) : (
<div>Nenhuma disponibilidade encontrada.</div>
)}
</div>
<DialogFooter>
<Button onClick={() => { setAvailabilityViewingFor(null); setAvailabilities([]); }}>Fechar</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)}
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
Mostrando {displayedDoctors.length} {searchMode ? 'resultado(s) da busca' : `de ${doctors.length}`} Mostrando {displayedDoctors.length} {searchMode ? 'resultado(s) da busca' : `de ${doctors.length}`}
</div> </div>

View File

@ -0,0 +1,181 @@
"use client"
import { useState, useEffect } from 'react'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Label } from '@/components/ui/label'
import { Input } from '@/components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { criarDisponibilidade, atualizarDisponibilidade, DoctorAvailabilityCreate, DoctorAvailability, DoctorAvailabilityUpdate } from '@/lib/api'
import { useToast } from '@/hooks/use-toast'
export interface AvailabilityFormProps {
open: boolean
onOpenChange: (open: boolean) => void
doctorId?: string | null
onSaved?: (saved: any) => void
// when editing, pass the existing availability and set mode to 'edit'
availability?: DoctorAvailability | null
mode?: 'create' | 'edit'
}
export function AvailabilityForm({ open, onOpenChange, doctorId = null, onSaved, availability = null, mode = 'create' }: AvailabilityFormProps) {
const [weekday, setWeekday] = useState<string>('segunda')
const [startTime, setStartTime] = useState<string>('09:00')
const [endTime, setEndTime] = useState<string>('17:00')
const [slotMinutes, setSlotMinutes] = useState<number>(30)
const [appointmentType, setAppointmentType] = useState<'presencial'|'telemedicina'>('presencial')
const [active, setActive] = useState<boolean>(true)
const [submitting, setSubmitting] = useState(false)
const { toast } = useToast()
// When editing, populate state from availability prop
useEffect(() => {
if (mode === 'edit' && availability) {
// weekday may be 'monday' or 'segunda' — keep original string
setWeekday(String(availability.weekday ?? 'segunda'))
// strip seconds for time inputs (HH:MM)
const st = String(availability.start_time ?? '09:00:00').replace(/:00$/,'')
const et = String(availability.end_time ?? '17:00:00').replace(/:00$/,'')
setStartTime(st)
setEndTime(et)
setSlotMinutes(Number(availability.slot_minutes ?? 30))
setAppointmentType((availability.appointment_type ?? 'presencial') as any)
setActive(!!availability.active)
}
}, [mode, availability])
async function handleSubmit(e?: React.FormEvent) {
e?.preventDefault()
if (!doctorId) {
toast({ title: 'Erro', description: 'ID do médico não informado', variant: 'destructive' })
return
}
setSubmitting(true)
try {
if (mode === 'create') {
const payload: DoctorAvailabilityCreate = {
doctor_id: String(doctorId),
weekday: weekday as any,
start_time: `${startTime}:00`,
end_time: `${endTime}:00`,
slot_minutes: slotMinutes,
appointment_type: appointmentType,
active,
}
const saved = await criarDisponibilidade(payload)
const labelMap: Record<string,string> = {
'segunda':'Segunda','terca':'Terça','quarta':'Quarta','quinta':'Quinta','sexta':'Sexta','sabado':'Sábado','domingo':'Domingo',
'monday':'Segunda','tuesday':'Terça','wednesday':'Quarta','thursday':'Quinta','friday':'Sexta','saturday':'Sábado','sunday':'Domingo'
}
const label = labelMap[weekday as string] ?? String(weekday)
toast({ title: 'Disponibilidade criada', description: `${label} ${startTime}${endTime}`, variant: 'default' })
onSaved?.(saved)
onOpenChange(false)
} else {
// edit mode: update existing availability
if (!availability || !availability.id) {
throw new Error('Disponibilidade inválida para edição')
}
const payload: DoctorAvailabilityUpdate = {
weekday: weekday as any,
start_time: `${startTime}:00`,
end_time: `${endTime}:00`,
slot_minutes: slotMinutes,
appointment_type: appointmentType,
active,
}
const updated = await atualizarDisponibilidade(String(availability.id), payload)
const labelMap: Record<string,string> = {
'segunda':'Segunda','terca':'Terça','quarta':'Quarta','quinta':'Quinta','sexta':'Sexta','sabado':'Sábado','domingo':'Domingo',
'monday':'Segunda','tuesday':'Terça','wednesday':'Quarta','thursday':'Quinta','friday':'Sexta','saturday':'Sábado','sunday':'Domingo'
}
const label = labelMap[weekday as string] ?? String(weekday)
toast({ title: 'Disponibilidade atualizada', description: `${label} ${startTime}${endTime}`, variant: 'default' })
onSaved?.(updated)
onOpenChange(false)
}
} catch (err: any) {
console.error('Erro ao criar disponibilidade:', err)
toast({ title: 'Erro', description: err?.message || String(err), variant: 'destructive' })
} finally {
setSubmitting(false)
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Criar disponibilidade</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4 py-4">
<div className="grid grid-cols-2 gap-4">
<div>
<Label>Dia da semana</Label>
<Select value={weekday} onValueChange={(v) => setWeekday(v)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="segunda">Segunda</SelectItem>
<SelectItem value="terca">Terça</SelectItem>
<SelectItem value="quarta">Quarta</SelectItem>
<SelectItem value="quinta">Quinta</SelectItem>
<SelectItem value="sexta">Sexta</SelectItem>
<SelectItem value="sabado">Sábado</SelectItem>
<SelectItem value="domingo">Domingo</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label>Tipo</Label>
<Select value={appointmentType} onValueChange={(v) => setAppointmentType(v as any)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="presencial">Presencial</SelectItem>
<SelectItem value="telemedicina">Telemedicina</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<Label>Início</Label>
<Input type="time" value={startTime} onChange={(e) => setStartTime(e.target.value)} />
</div>
<div>
<Label>Fim</Label>
<Input type="time" value={endTime} onChange={(e) => setEndTime(e.target.value)} />
</div>
<div>
<Label>Minutos por slot</Label>
<Input type="number" value={String(slotMinutes)} onChange={(e) => setSlotMinutes(Number(e.target.value || 30))} />
</div>
</div>
<div className="flex items-center gap-4">
<label className="flex items-center gap-2">
<input type="checkbox" checked={active} onChange={(e) => setActive(e.target.checked)} />
<span>Ativo</span>
</label>
</div>
<DialogFooter>
<Button variant="ghost" onClick={() => onOpenChange(false)} disabled={submitting}>Cancelar</Button>
<Button type="submit" disabled={submitting}>{submitting ? 'Salvando...' : 'Criar disponibilidade'}</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
)
}
export default AvailabilityForm

View File

@ -1,6 +1,7 @@
// lib/api.ts // lib/api.ts
import { ENV_CONFIG } from '@/lib/env-config'; import { ENV_CONFIG } from '@/lib/env-config';
import { AUTH_STORAGE_KEYS } from '@/types/auth'
// Use ENV_CONFIG for SUPABASE URL and anon key in frontend // Use ENV_CONFIG for SUPABASE URL and anon key in frontend
export type ApiOk<T = any> = { export type ApiOk<T = any> = {
@ -146,6 +147,284 @@ export type MedicoInput = {
updated_by?: string | null; updated_by?: string | null;
}; };
// ===== DISPONIBILIDADE (Doctor Availability) =====
export type DoctorAvailabilityCreate = {
doctor_id: string;
weekday: 'segunda' | 'terca' | 'quarta' | 'quinta' | 'sexta' | 'sabado' | 'domingo';
start_time: string; // 'HH:MM:SS'
end_time: string; // 'HH:MM:SS'
slot_minutes?: number;
appointment_type?: 'presencial' | 'telemedicina';
active?: boolean;
};
export type DoctorAvailability = DoctorAvailabilityCreate & {
id: string;
created_at?: string;
updated_at?: string;
created_by?: string;
updated_by?: string | null;
};
export type DoctorAvailabilityUpdate = Partial<{
weekday: 'segunda' | 'terca' | 'quarta' | 'quinta' | 'sexta' | 'sabado' | 'domingo' | string;
start_time: string;
end_time: string;
slot_minutes: number;
appointment_type: 'presencial' | 'telemedicina' | string;
active: boolean;
}>;
/**
* Cria uma disponibilidade de médico (POST /rest/v1/doctor_availability)
*/
export async function criarDisponibilidade(input: DoctorAvailabilityCreate): Promise<DoctorAvailability> {
// Apply sensible defaults
// Map weekday to the server-expected enum value if necessary. Some deployments use
// English weekday names (monday..sunday) as the enum values; the UI uses
// Portuguese values (segunda..domingo). Normalize and convert here so POST
// doesn't fail with Postgres `invalid input value for enum weekday`.
const mapWeekdayForServer = (w?: string) => {
if (!w) return w;
const key = w.toString().toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, '').replace(/[^a-z0-9]/g, '');
const map: Record<string, string> = {
'segunda': 'monday',
'terca': 'tuesday',
'quarta': 'wednesday',
'quinta': 'thursday',
'sexta': 'friday',
'sabado': 'saturday',
'domingo': 'sunday',
// allow common english names through
'monday': 'monday',
'tuesday': 'tuesday',
'wednesday': 'wednesday',
'thursday': 'thursday',
'friday': 'friday',
'saturday': 'saturday',
'sunday': 'sunday',
};
return map[key] ?? w;
};
// Determine created_by: try localStorage first, then fall back to calling user-info
let createdBy: string | null = null;
if (typeof window !== 'undefined') {
try {
const raw = localStorage.getItem(AUTH_STORAGE_KEYS.USER);
if (raw) {
const parsed = JSON.parse(raw);
createdBy = parsed?.id ?? parsed?.user?.id ?? null;
}
} catch (e) {
// ignore parse errors
}
}
if (!createdBy) {
try {
const info = await getUserInfo();
createdBy = info?.user?.id ?? null;
} catch (e) {
// ignore
}
}
if (!createdBy) {
throw new Error('Não foi possível determinar o usuário atual (created_by). Faça login novamente antes de criar uma disponibilidade.');
}
const payload: any = {
slot_minutes: input.slot_minutes ?? 30,
appointment_type: input.appointment_type ?? 'presencial',
active: typeof input.active === 'undefined' ? true : input.active,
doctor_id: input.doctor_id,
weekday: mapWeekdayForServer(input.weekday),
start_time: input.start_time,
end_time: input.end_time,
};
const url = `${REST}/doctor_availability`;
// Try several payload permutations to tolerate different server enum/time formats.
const attempts = [] as Array<{ weekdayVal: string | undefined; withSeconds: boolean }>;
const mappedWeekday = mapWeekdayForServer(input.weekday);
const originalWeekday = input.weekday;
attempts.push({ weekdayVal: mappedWeekday, withSeconds: true });
attempts.push({ weekdayVal: originalWeekday, withSeconds: true });
attempts.push({ weekdayVal: mappedWeekday, withSeconds: false });
attempts.push({ weekdayVal: originalWeekday, withSeconds: false });
let lastRes: Response | null = null;
for (const at of attempts) {
const start = at.withSeconds ? input.start_time : String(input.start_time).replace(/:00$/,'');
const end = at.withSeconds ? input.end_time : String(input.end_time).replace(/:00$/,'');
const tryPayload: any = {
slot_minutes: input.slot_minutes ?? 30,
appointment_type: input.appointment_type ?? 'presencial',
active: typeof input.active === 'undefined' ? true : input.active,
doctor_id: input.doctor_id,
weekday: at.weekdayVal,
start_time: start,
end_time: end,
created_by: createdBy,
};
try {
const res = await fetch(url, {
method: 'POST',
headers: withPrefer({ ...baseHeaders(), 'Content-Type': 'application/json' }, 'return=representation'),
body: JSON.stringify(tryPayload),
});
lastRes = res;
if (res.ok) {
const arr = await parse<DoctorAvailability[] | DoctorAvailability>(res);
return Array.isArray(arr) ? arr[0] : (arr as DoctorAvailability);
}
// If server returned a 4xx, try next permutation; for 5xx, bail out and throw the error from parse()
if (res.status >= 500) {
// Let parse produce the error with friendly messaging
return await parse<DoctorAvailability>(res);
}
// Log a warning and continue to next attempt
const raw = await res.clone().text().catch(() => '');
console.warn('[criarDisponibilidade] tentativa falhou', { status: res.status, weekday: at.weekdayVal, withSeconds: at.withSeconds, raw });
// continue to next attempt
} catch (e) {
console.warn('[criarDisponibilidade] fetch erro na tentativa', e);
// continue to next attempt
}
}
// All attempts failed — throw using the last response to get friendly message from parse()
if (lastRes) {
return await parse<DoctorAvailability>(lastRes);
}
throw new Error('Falha ao criar disponibilidade: nenhuma resposta do servidor.');
}
/**
* Lista disponibilidades. Se doctorId for passado, filtra por médico.
*/
export async function listarDisponibilidades(params?: { doctorId?: string; active?: boolean }): Promise<DoctorAvailability[]> {
const qs = new URLSearchParams();
if (params?.doctorId) qs.set('doctor_id', `eq.${encodeURIComponent(String(params.doctorId))}`);
if (typeof params?.active !== 'undefined') qs.set('active', `eq.${params.active ? 'true' : 'false'}`);
const url = `${REST}/doctor_availability${qs.toString() ? `?${qs.toString()}` : ''}`;
const res = await fetch(url, { method: 'GET', headers: baseHeaders() });
return await parse<DoctorAvailability[]>(res);
}
/**
* Atualiza uma disponibilidade existente (PATCH /rest/v1/doctor_availability?id=eq.<id>)
*/
export async function atualizarDisponibilidade(id: string, input: DoctorAvailabilityUpdate): Promise<DoctorAvailability> {
if (!id) throw new Error('ID da disponibilidade é obrigatório');
const mapWeekdayForServer = (w?: string) => {
if (!w) return w;
const key = w.toString().toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, '').replace(/[^a-z0-9]/g, '');
const map: Record<string, string> = {
'segunda': 'monday',
'terca': 'tuesday',
'quarta': 'wednesday',
'quinta': 'thursday',
'sexta': 'friday',
'sabado': 'saturday',
'domingo': 'sunday',
'monday': 'monday',
'tuesday': 'tuesday',
'wednesday': 'wednesday',
'thursday': 'thursday',
'friday': 'friday',
'saturday': 'saturday',
'sunday': 'sunday',
};
return map[key] ?? w;
};
// determine updated_by
let updatedBy: string | null = null;
if (typeof window !== 'undefined') {
try {
const raw = localStorage.getItem(AUTH_STORAGE_KEYS.USER);
if (raw) {
const parsed = JSON.parse(raw);
updatedBy = parsed?.id ?? parsed?.user?.id ?? null;
}
} catch (e) {}
}
if (!updatedBy) {
try {
const info = await getUserInfo();
updatedBy = info?.user?.id ?? null;
} catch (e) {}
}
const restUrl = `${REST}/doctor_availability?id=eq.${encodeURIComponent(String(id))}`;
// Build candidate payloads (weekday mapped/original, with/without seconds)
const candidates: Array<DoctorAvailabilityUpdate> = [];
const wk = input.weekday ? mapWeekdayForServer(String(input.weekday)) : undefined;
// preferred candidate
candidates.push({ ...input, weekday: wk });
// original weekday if different
if (input.weekday && String(input.weekday) !== wk) candidates.push({ ...input, weekday: input.weekday });
// times without seconds
const stripSeconds = (t?: string) => t ? String(t).replace(/:00$/,'') : t;
candidates.push({ ...input, start_time: stripSeconds(input.start_time), end_time: stripSeconds(input.end_time), weekday: wk });
let lastRes: Response | null = null;
for (const cand of candidates) {
const payload: any = { ...cand };
if (updatedBy) payload.updated_by = updatedBy;
try {
const res = await fetch(restUrl, {
method: 'PATCH',
headers: withPrefer({ ...baseHeaders(), 'Content-Type': 'application/json' }, 'return=representation'),
body: JSON.stringify(payload),
});
lastRes = res;
if (res.ok) {
const arr = await parse<DoctorAvailability[] | DoctorAvailability>(res);
return Array.isArray(arr) ? arr[0] : (arr as DoctorAvailability);
}
if (res.status >= 500) return await parse<DoctorAvailability>(res);
const raw = await res.clone().text().catch(() => '');
console.warn('[atualizarDisponibilidade] tentativa falhou', { status: res.status, payload, raw });
} catch (e) {
console.warn('[atualizarDisponibilidade] erro fetch', e);
}
}
if (lastRes) return await parse<DoctorAvailability>(lastRes);
throw new Error('Falha ao atualizar disponibilidade: sem resposta do servidor');
}
/**
* Deleta uma disponibilidade por ID (DELETE /rest/v1/doctor_availability?id=eq.<id>)
*/
export async function deletarDisponibilidade(id: string): Promise<void> {
if (!id) throw new Error('ID da disponibilidade é obrigatório');
const url = `${REST}/doctor_availability?id=eq.${encodeURIComponent(String(id))}`;
// Request minimal return to get a 204 No Content when the delete succeeds.
const res = await fetch(url, {
method: 'DELETE',
headers: withPrefer({ ...baseHeaders() }, 'return=minimal'),
});
if (res.status === 204) return;
// Some deployments may return 200 with a representation — accept that too
if (res.status === 200) return;
// Otherwise surface a friendly error using parse()
await parse(res as Response);
}
// ===== CONFIG ===== // ===== CONFIG =====