fix(main-routes): security, layout, and form formatting

- Removed sensitive logs from the console
- Added sidebar to the Schedule, Procedure, and Financial pages
- Standardized spacing between labels and inputs in all forms
- Added automatic formatting for ID, date of birth, and phone number in patient registration
- Removed duplicate "Cell Phone" field in doctor registration
- Adjusted page layout to follow standard
This commit is contained in:
M-Gabrielly 2025-10-29 23:26:09 -03:00
parent b478a1f8d3
commit 26d4077784
9 changed files with 126 additions and 183 deletions

View File

@ -99,16 +99,16 @@ export default function NovoAgendamentoPage() {
};
return (
<div className="min-h-screen flex flex-col bg-background">
<div className="flex flex-col h-full bg-background">
<HeaderAgenda />
<main className="flex-1 mx-auto w-full max-w-7xl px-8 py-8">
<CalendarRegistrationForm
formData={formData as any}
onFormChange={handleFormChange as any}
createMode
/>
<main className="flex-1 mx-auto w-full max-w-7xl px-8 py-8 overflow-auto">
<CalendarRegistrationForm
formData={formData as any}
onFormChange={handleFormChange as any}
createMode
/>
</main>
<FooterAgenda onSave={handleSave} onCancel={handleCancel} />
</div>
);
}
}

View File

@ -1,27 +1,16 @@
"use client";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Search, ChevronDown, Calculator, DollarSign } from "lucide-react";
import { Plus } from "lucide-react";
import { Calculator, DollarSign } from "lucide-react";
import HeaderAgenda from "@/components/agenda/HeaderAgenda";
import FooterAgenda from "@/components/agenda/FooterAgenda";
export default function FinanceiroPage() {
const pathname = usePathname();
const router = useRouter();
const [bloqueio, setBloqueio] = useState(false);
const [formaTipo, setFormaTipo] = useState("");
const [parcelas, setParcelas] = useState("1");
const isAg = pathname?.startsWith("/agendamento");
const isPr = pathname?.startsWith("/procedimento");
const isFi = pathname?.startsWith("/financeiro");
const handleSave = () => {
// Lógica de salvar será implementada
@ -33,12 +22,11 @@ export default function FinanceiroPage() {
};
return (
<div className="w-full min-h-screen flex flex-col bg-background">
{/* HEADER */}
<div className="flex flex-col h-full bg-background">
<HeaderAgenda />
{/* CORPO */}
<main className="mx-auto w-full max-w-7xl px-8 py-6 space-y-6 flex-grow">
<main className="mx-auto w-full max-w-7xl px-8 py-6 space-y-6 flex-1 overflow-auto">
{/* INFORMAÇÕES FINANCEIRAS */}
<section className="space-y-6">
{/* Selo Financeiro */}
@ -58,7 +46,7 @@ export default function FinanceiroPage() {
Valor do Atendimento
</Label>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<div className="space-y-1">
<Label className="text-xs text-muted-foreground">Valor Particular</Label>
<div className="relative">
<DollarSign className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
@ -68,7 +56,7 @@ export default function FinanceiroPage() {
/>
</div>
</div>
<div className="space-y-2">
<div className="space-y-1">
<Label className="text-xs text-muted-foreground">Valor Convênio</Label>
<div className="relative">
<DollarSign className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
@ -90,7 +78,7 @@ export default function FinanceiroPage() {
Forma de Pagamento
</Label>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-2">
<div className="space-y-1">
<Label className="text-xs text-muted-foreground">Tipo</Label>
<select value={formaTipo} onChange={(e) => setFormaTipo(e.target.value)} className="h-10 w-full rounded-md border border-gray-300 dark:border-input bg-background text-foreground pr-8 pl-3 text-[13px] appearance-none transition-colors hover:bg-muted/30 hover:border-gray-400">
<option value="">Selecionar</option>
@ -100,7 +88,7 @@ export default function FinanceiroPage() {
<option value="convenio">Convênio</option>
</select>
</div>
<div className="space-y-2">
<div className="space-y-1">
<Label className="text-xs text-muted-foreground">Parcelas</Label>
<select className="h-10 w-full rounded-md border border-gray-300 dark:border-input bg-background text-foreground pr-8 pl-3 text-[13px] appearance-none transition-colors hover:bg-muted/30 hover:border-gray-400">
<option value="1">1x</option>
@ -111,7 +99,7 @@ export default function FinanceiroPage() {
<option value="6">6x</option>
</select>
</div>
<div className="space-y-2">
<div className="space-y-1">
<Label className="text-xs text-muted-foreground">Desconto</Label>
<div className="relative">
<Calculator className="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
@ -156,4 +144,4 @@ export default function FinanceiroPage() {
<FooterAgenda onSave={handleSave} onCancel={handleCancel} />
</div>
);
}
}

View File

@ -1,25 +1,17 @@
"use client";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Search, ChevronDown, RotateCcw } from "lucide-react";
import { Search, ChevronDown } from "lucide-react";
import { Plus } from "lucide-react";
import HeaderAgenda from "@/components/agenda/HeaderAgenda";
import FooterAgenda from "@/components/agenda/FooterAgenda";
export default function ProcedimentoPage() {
const pathname = usePathname();
const router = useRouter();
const [bloqueio, setBloqueio] = useState(false);
const isAg = pathname?.startsWith("/agendamento");
const isPr = pathname?.startsWith("/procedimento");
const isFi = pathname?.startsWith("/financeiro");
const handleSave = () => {
// Lógica de salvar será implementada
@ -30,20 +22,12 @@ export default function ProcedimentoPage() {
router.push("/calendar");
};
const tab = (active: boolean, extra = "") =>
`px-4 py-1.5 text-[13px] border ${
active
? "border-sky-500 bg-sky-50 dark:bg-sky-900/30 text-sky-700 dark:text-sky-300 font-medium"
: "text-muted-foreground hover:bg-muted border-border"
} ${extra}`;
return (
<div className="w-full min-h-screen flex flex-col bg-background">
{/* HEADER */}
<div className="flex flex-col h-full bg-background">
<HeaderAgenda />
{/* CORPO */}
<main className="mx-auto w-full max-w-7xl px-8 py-6 space-y-6 flex-grow">
<main className="mx-auto w-full max-w-7xl px-8 py-6 space-y-6 flex-1 overflow-auto">
{/* ATENDIMENTOS */}
<section className="space-y-6">
{/* Selo Atendimento com + dentro da bolinha */}

View File

@ -1080,8 +1080,8 @@ export function CalendarRegistrationForm({ formData, onFormChange, createMode =
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-3 gap-3 mt-3">
<div>
<div className="grid grid-cols-3 gap-3">
<div className="space-y-1">
<Label className="text-[13px]">Status</Label>
<select name="status" className="h-11 w-full rounded-md border border-gray-300 dark:border-input bg-background text-foreground pr-3 text-[13px]" value={formData.status || ''} onChange={handleChange}>
<option value="">Selecione</option>
@ -1094,50 +1094,50 @@ export function CalendarRegistrationForm({ formData, onFormChange, createMode =
<option value="no_show">Não compareceu</option>
</select>
</div>
<div>
<div className="space-y-1">
<Label className="text-[13px]">Duração (min)</Label>
<Input name="duration_minutes" type="number" min={1} className="h-11 w-full rounded-md" value={formData.duration_minutes ?? ''} onChange={handleChange} readOnly={lockedDurationFromSlot} disabled={lockedDurationFromSlot} />
</div>
<div>
<div className="space-y-1">
<Label className="text-[13px]">Convênio</Label>
<Input name="insurance_provider" placeholder="Operadora" className="h-11 w-full rounded-md" value={formData.insurance_provider || ''} onChange={handleChange} />
</div>
</div>
</div>
<div className="space-y-2">
<div className="space-y-2 mt-4">
<div className="flex items-center justify-between">
<Label className="text-[13px]">Observações</Label>
</div>
<Textarea name="notes" rows={4} className="text-[13px] min-h-[80px] resize-none rounded-md transition-colors hover:bg-muted/30" value={formData.notes || ''} onChange={handleChange} />
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-3">
<div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<div className="space-y-1">
<Label className="text-[13px]">Queixa principal</Label>
<Textarea name="chief_complaint" rows={3} className="text-[13px] rounded-md" value={formData.chief_complaint || ''} onChange={handleChange} />
</div>
<div>
<div className="space-y-1">
<Label className="text-[13px]">Notas do paciente</Label>
<Textarea name="patient_notes" rows={3} className="text-[13px] rounded-md" value={formData.patient_notes || ''} onChange={handleChange} />
</div>
</div>
<div className="grid grid-cols-3 gap-3 mt-3">
<div>
<div className="grid grid-cols-3 gap-3 mt-4">
<div className="space-y-1">
<Label className="text-[13px]">Horário de check-in</Label>
<Input name="checked_in_at" type="datetime-local" className="h-11 w-full rounded-md" value={isoToDatetimeLocal(formData.checked_in_at as any)} onChange={handleChange} />
</div>
<div>
<div className="space-y-1">
<Label className="text-[13px]">Concluído em</Label>
<Input name="completed_at" type="datetime-local" className="h-11 w-full rounded-md" value={isoToDatetimeLocal(formData.completed_at as any)} onChange={handleChange} />
</div>
<div>
<div className="space-y-1">
<Label className="text-[13px]">Cancelado em</Label>
<Input name="cancelled_at" type="datetime-local" className="h-11 w-full rounded-md" value={isoToDatetimeLocal(formData.cancelled_at as any)} onChange={handleChange} />
</div>
</div>
<div className="mt-3">
<div className="mt-4 space-y-1">
<Label className="text-[13px]">Motivo do cancelamento</Label>
<Input name="cancellation_reason" className="h-11 w-full rounded-md" value={formData.cancellation_reason || ''} onChange={handleChange} />
</div>

View File

@ -935,14 +935,6 @@ async function handleSubmit(ev: React.FormEvent) {
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Celular</Label>
<Input
value={form.celular}
onChange={(e) => setField("celular", formatPhone(e.target.value))}
placeholder="(XX) XXXXX-XXXX"
/>
</div>
<div className="space-y-2">
<Label>Contato de Emergência</Label>
<Input

View File

@ -98,6 +98,30 @@ export function PatientRegistrationForm({
const [form, setForm] = useState<FormData>(initial);
const [errors, setErrors] = useState<Record<string, string>>({});
const [expanded, setExpanded] = useState({ dados: true, contato: false, endereco: false, obs: false });
// Funções de formatação
const formatRG = (value: string) => {
const cleaned = value.replace(/\D/g, '');
if (cleaned.length <= 9) {
return cleaned.replace(/(\d{2})(\d{3})(\d{3})(\d{1})/, '$1.$2.$3-$4');
}
return cleaned.slice(0, 9);
};
const formatTelefone = (value: string) => {
const cleaned = value.replace(/\D/g, '');
if (cleaned.length <= 10) {
return cleaned.replace(/(\d{2})(\d{4})(\d{4})/, '($1) $2-$3');
}
return cleaned.replace(/(\d{2})(\d{5})(\d{4})/, '($1) $2-$3');
};
const formatDataNascimento = (value: string) => {
const cleaned = value.replace(/\D/g, '');
if (cleaned.length <= 2) return cleaned;
if (cleaned.length <= 4) return `${cleaned.slice(0, 2)}/${cleaned.slice(2)}`;
return `${cleaned.slice(0, 2)}/${cleaned.slice(2, 4)}/${cleaned.slice(4, 8)}`;
};
const [isSubmitting, setSubmitting] = useState(false);
const [isUploadingPhoto, setUploadingPhoto] = useState(false);
const [isSearchingCEP, setSearchingCEP] = useState(false);
@ -362,7 +386,7 @@ export function PatientRegistrationForm({
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2"><Label>CPF *</Label><Input value={form.cpf} onChange={(e) => handleCPFChange(e.target.value)} placeholder="000.000.000-00" maxLength={14} className={errors.cpf ? "border-destructive" : ""} />{errors.cpf && <p className="text-sm text-destructive">{errors.cpf}</p>}</div>
<div className="space-y-2"><Label>RG</Label><Input value={form.rg} onChange={(e) => setField("rg", e.target.value)} /></div>
<div className="space-y-2"><Label>RG</Label><Input value={form.rg} onChange={(e) => setField("rg", formatRG(e.target.value))} placeholder="00.000.000-0" maxLength={12} /></div>
</div>
<div className="grid grid-cols-2 gap-4">
@ -372,7 +396,7 @@ export function PatientRegistrationForm({
<SelectContent><SelectItem value="masculino">Masculino</SelectItem><SelectItem value="feminino">Feminino</SelectItem><SelectItem value="outro">Outro</SelectItem></SelectContent>
</Select>
</div>
<div className="space-y-2"><Label>Data de Nascimento</Label><Input placeholder="dd/mm/aaaa" value={form.birth_date} onChange={(e) => { const v = e.target.value.replace(/[^0-9\/]*/g, "").slice(0, 10); setField("birth_date", v); }} onBlur={() => { const raw = form.birth_date; const parts = raw.split(/\D+/).filter(Boolean); if (parts.length === 3) { const d = `${parts[0].padStart(2,'0')}/${parts[1].padStart(2,'0')}/${parts[2].padStart(4,'0')}`; setField("birth_date", d); } }} /></div>
<div className="space-y-2"><Label>Data de Nascimento</Label><Input placeholder="dd/mm/aaaa" value={form.birth_date} onChange={(e) => setField("birth_date", formatDataNascimento(e.target.value))} maxLength={10} /></div>
</div>
</CardContent>
</CollapsibleContent>
@ -388,7 +412,7 @@ export function PatientRegistrationForm({
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2"><Label>E-mail</Label><Input value={form.email} onChange={(e) => setField("email", e.target.value)} />{errors.email && <p className="text-sm text-destructive">{errors.email}</p>}</div>
<div className="space-y-2"><Label>Telefone</Label><Input value={form.telefone} onChange={(e) => setField("telefone", e.target.value)} />{errors.telefone && <p className="text-sm text-destructive">{errors.telefone}</p>}</div>
<div className="space-y-2"><Label>Telefone</Label><Input value={form.telefone} onChange={(e) => setField("telefone", formatTelefone(e.target.value))} placeholder="(00) 00000-0000" maxLength={15} />{errors.telefone && <p className="text-sm text-destructive">{errors.telefone}</p>}</div>
</div>
</CardContent>
</CollapsibleContent>

View File

@ -652,28 +652,26 @@ function withPrefer(h: Record<string, string>, prefer: string) {
// Helper: fetch seguro que tenta urls alternativas caso a requisição primária falhe
async function fetchWithFallback<T = any>(url: string, headers: Record<string, string>, altUrls?: string[]): Promise<T | null> {
try {
console.debug('[fetchWithFallback] tentando URL:', url);
// Log removido por segurança
const res = await fetch(url, { method: 'GET', headers });
if (res.ok) {
return await parse<T>(res);
}
const raw = await res.clone().text().catch(() => '');
console.warn('[fetchWithFallback] falha na URL primária:', url, 'status:', res.status, 'raw:', raw);
// Log removido por segurança
if (!altUrls || !altUrls.length) return null;
for (const alt of altUrls) {
try {
console.debug('[fetchWithFallback] tentando fallback URL:', alt);
// Log removido por segurança
const r2 = await fetch(alt, { method: 'GET', headers });
if (r2.ok) return await parse<T>(r2);
const raw2 = await r2.clone().text().catch(() => '');
console.warn('[fetchWithFallback] fallback falhou:', alt, 'status:', r2.status, 'raw:', raw2);
// Log removido por segurança
} catch (e) {
console.warn('[fetchWithFallback] erro no fallback:', alt, e);
// Log removido por segurança
}
}
return null;
} catch (e) {
console.warn('[fetchWithFallback] erro fetch primario:', url, e);
// Log removido por segurança
if (!altUrls || !altUrls.length) return null;
for (const alt of altUrls) {
try {
@ -724,22 +722,17 @@ async function parse<T>(res: Response): Promise<T> {
// Special-case authentication/authorization errors to reduce noisy logs
if (res.status === 401) {
// If the server returned an empty body, avoid dumping raw text to console.error
if (!rawText && !json) {
console.warn('[API AUTH] 401 Unauthorized for', res.url, '- no auth token or token expired.');
} else {
console.warn('[API AUTH] 401 Unauthorized for', res.url, 'response:', json ?? rawText);
}
// Log removido por segurança - não expor URL da Supabase
throw new Error('Você não está autenticado. Faça login novamente.');
}
if (res.status === 403) {
console.warn('[API AUTH] 403 Forbidden for', res.url, (json ?? rawText) ? 'response: ' + (json ?? rawText) : '');
// Log removido por segurança - não expor URL da Supabase
throw new Error('Você não tem permissão para executar esta ação.');
}
// For other errors, log a concise error and try to produce a friendly message
console.error('[API ERROR]', res.url, res.status, json ? json : 'no-json', rawText ? 'raw body present' : 'no raw body');
console.error('[API ERROR] Status:', res.status, json ? 'JSON response' : 'no-json', rawText ? 'raw body present' : 'no raw body');
// Mensagens amigáveis para erros comuns
let friendlyMessage = msg;
@ -877,9 +870,7 @@ export async function buscarPacientes(termo: string): Promise<Paciente[]> {
params.set('limit', '10');
const url = `${REST}/patients?${params.toString()}`;
const headers = baseHeaders();
const masked = (headers['Authorization'] as string | undefined) ? `${String(headers['Authorization']).slice(0,6)}...${String(headers['Authorization']).slice(-6)}` : null;
console.debug('[buscarPacientes] URL:', url);
console.debug('[buscarPacientes] Headers (masked):', { ...headers, Authorization: masked ? '<<masked>>' : undefined });
// Logs removidos por segurança
const res = await fetch(url, { method: "GET", headers });
const arr = await parse<Paciente[]>(res);
@ -908,7 +899,7 @@ export async function buscarPacientePorUserId(userId?: string | null): Promise<P
try {
const url = `${REST}/patients?user_id=eq.${encodeURIComponent(String(userId))}&limit=1`;
const headers = baseHeaders();
console.debug('[buscarPacientePorUserId] URL:', url);
// Log removido por segurança
const arr = await fetchWithFallback<Paciente[]>(url, headers).catch(() => []);
if (arr && arr.length) return arr[0];
return null;
@ -925,7 +916,7 @@ export async function buscarPacientePorId(id: string | number): Promise<Paciente
// Tenta buscar por id (UUID ou string) primeiro
try {
const url = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/patients?id=eq.${encodeURIComponent(idParam)}`;
console.debug('[buscarPacientePorId] tentando por id URL:', url);
// Log removido por segurança
const arr = await fetchWithFallback<Paciente[]>(url, headers);
if (arr && arr.length) return arr[0];
} catch (e) {
@ -943,7 +934,7 @@ export async function buscarPacientePorId(id: string | number): Promise<Paciente
altParams.set('social_name', `ilike.*${String(id)}*`);
altParams.set('limit', '5');
const alt = `${REST}/patients?${altParams.toString()}`;
console.debug('[buscarPacientePorId] tentando por nome URL:', url);
// Log removido por segurança
const arr2 = await fetchWithFallback<Paciente[]>(url, headers, [alt]);
if (arr2 && arr2.length) return arr2[0];
}
@ -1348,31 +1339,31 @@ export async function buscarRelatorioPorId(id: string | number): Promise<Report>
// 1) tenta por id (UUID ou campo id)
try {
const urlById = `${REST}/reports?id=eq.${encodeURIComponent(sId)}`;
console.debug('[buscarRelatorioPorId] tentando por id URL:', urlById);
// Log removido por segurança
const arr = await fetchWithFallback<Report[]>(urlById, headers);
if (arr && arr.length) return arr[0];
} catch (e) {
console.warn('[buscarRelatorioPorId] falha ao buscar por id:', e);
// Falha silenciosa - tenta próxima estratégia
}
// 2) tenta por order_number (caso o usuário cole um código legível)
try {
const urlByOrder = `${REST}/reports?order_number=eq.${encodeURIComponent(sId)}`;
console.debug('[buscarRelatorioPorId] tentando por order_number URL:', urlByOrder);
// Log removido por segurança
const arr2 = await fetchWithFallback<Report[]>(urlByOrder, headers);
if (arr2 && arr2.length) return arr2[0];
} catch (e) {
console.warn('[buscarRelatorioPorId] falha ao buscar por order_number:', e);
// Falha silenciosa - tenta próxima estratégia
}
// 3) tenta por patient_id (caso o usuário passe um patient_id em vez do report id)
try {
const urlByPatient = `${REST}/reports?patient_id=eq.${encodeURIComponent(sId)}`;
console.debug('[buscarRelatorioPorId] tentando por patient_id URL:', urlByPatient);
// Log removido por segurança
const arr3 = await fetchWithFallback<Report[]>(urlByPatient, headers);
if (arr3 && arr3.length) return arr3[0];
} catch (e) {
console.warn('[buscarRelatorioPorId] falha ao buscar por patient_id:', e);
// Falha silenciosa - não encontrado
}
// Não encontrado
@ -1424,7 +1415,7 @@ export async function buscarPacientesPorIds(ids: Array<string | number>): Promis
altParams.set('limit', '100');
const alt = `${REST}/patients?${altParams.toString()}`;
const headers = baseHeaders();
console.debug('[buscarPacientesPorIds] URL (patient by name):', url);
// Log removido por segurança
const arr = await fetchWithFallback<Paciente[]>(url, headers, [alt]);
if (arr && arr.length) results.push(...arr);
} catch (e) {
@ -1567,7 +1558,7 @@ export async function criarPaciente(input: PacienteInput): Promise<Paciente> {
const a = maskedHeaders.Authorization as string;
maskedHeaders.Authorization = `${a.slice(0,6)}...${a.slice(-6)}`;
}
console.debug('[criarPaciente] POST', u, 'headers(masked):', maskedHeaders, 'payloadKeys:', Object.keys(payload));
// Log removido por segurança
const res = await fetch(u, {
method: 'POST',
headers,
@ -1766,8 +1757,7 @@ export async function buscarMedicos(termo: string): Promise<Medico[]> {
queries.push(`specialty=ilike.*${q}*`);
}
// debug: mostrar queries construídas
console.debug('[buscarMedicos] queries construídas:', queries);
// Debug removido por segurança
const results: Medico[] = [];
const seenIds = new Set<string>();
@ -1783,10 +1773,7 @@ export async function buscarMedicos(termo: string): Promise<Medico[]> {
params.set('limit', '10');
const url = `${REST}/doctors?${params.toString()}`;
const headers = baseHeaders();
const masked = (headers['Authorization'] as string | undefined) ? `${String(headers['Authorization']).slice(0,6)}...${String(headers['Authorization']).slice(-6)}` : null;
console.debug('[buscarMedicos] URL params:', params.toString());
console.debug('[buscarMedicos] URL:', url);
console.debug('[buscarMedicos] Headers (masked):', { ...headers, Authorization: masked ? '<<masked>>' : undefined });
// Logs removidos por segurança
const res = await fetch(url, { method: 'GET', headers });
const arr = await parse<Medico[]>(res);
@ -1819,7 +1806,7 @@ export async function buscarMedicoPorId(id: string | number): Promise<Medico | n
// 1) Se parece UUID, busca por id direto
if (isString && uuidRegex.test(sId)) {
const url = `${REST}/doctors?id=eq.${encodeURIComponent(sId)}`;
console.debug('[buscarMedicoPorId] tentando por id URL:', url);
// Log removido por segurança
const arr = await fetchWithFallback<Medico[]>(url, baseHeaders());
if (arr && arr.length > 0) return arr[0];
}
@ -1868,18 +1855,16 @@ export async function buscarMedicoPorId(id: string | number): Promise<Medico | n
// Se não encontrar no Supabase, tenta o mock API
try {
const mockUrl = `https://yuanqog.com/m1/1053378-0-default/rest/v1/doctors/${encodeURIComponent(String(id))}`;
console.debug('[buscarMedicoPorId] tentando mock API URL:', mockUrl);
// Log removido por segurança
try {
const medico = await fetchWithFallback<any>(mockUrl, { Accept: 'application/json' });
if (medico) {
console.log('✅ Médico encontrado no Mock API:', medico);
return medico as Medico;
}
// fetchWithFallback returned null -> not found
console.warn('[buscarMedicoPorId] mock API returned no result for id:', id);
return null;
} catch (fetchErr) {
console.warn('[buscarMedicoPorId] mock API fetch failed or returned no result:', fetchErr);
// Falha silenciosa
return null;
}
} catch (error) {
@ -1928,7 +1913,7 @@ export async function buscarMedicosPorIds(ids: Array<string | number>): Promise<
altParams.set('limit', '200');
const alt = `${REST}/doctors?${altParams.toString()}`;
const headers = baseHeaders();
console.debug('[buscarMedicosPorIds] URL (doctor by name):', url);
// Log removido por segurança
const socialAltParams = new URLSearchParams();
socialAltParams.set('social_name', `ilike.*${name}*`);
socialAltParams.set('limit', '200');
@ -2047,7 +2032,7 @@ export async function criarMedico(input: MedicoInput): Promise<Medico> {
const a = maskedHeaders.Authorization as string;
maskedHeaders.Authorization = `${a.slice(0,6)}...${a.slice(-6)}`;
}
console.debug('[criarMedico] POST', u, 'headers(masked):', maskedHeaders, 'payloadKeys:', Object.keys(payload));
// Log removido por segurança
const res = await fetch(u, {
method: 'POST',
@ -2106,12 +2091,7 @@ export async function criarMedico(input: MedicoInput): Promise<Medico> {
const url = `${API_BASE}/functions/v1/create-doctor`;
const headers = { ...baseHeaders(), 'Content-Type': 'application/json' } as Record<string, string>;
const maskedHeaders = { ...headers } as Record<string, string>;
if (maskedHeaders.Authorization) {
const a = maskedHeaders.Authorization as string;
maskedHeaders.Authorization = `${a.slice(0,6)}...${a.slice(-6)}`;
}
console.debug('[criarMedico fallback] POST', url, 'headers(masked):', maskedHeaders, 'body:', JSON.stringify(fallbackPayload));
// Logs removidos por segurança
const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify(fallbackPayload) });
const parsed = await parse<any>(res as Response);
@ -2198,19 +2178,14 @@ export async function vincularUserIdPaciente(pacienteId: string | number, userId
const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
const looksLikeUuid = uuidRegex.test(idStr);
// Allow non-UUID ids (legacy) but log a debug warning when it's not UUID
if (!looksLikeUuid) console.warn('[vincularUserIdPaciente] pacienteId does not look like a UUID:', idStr);
// Log removido por segurança
const url = `${REST}/patients?id=eq.${encodeURIComponent(idStr)}`;
const payload = { user_id: String(userId) };
// Debug-friendly masked headers
const headers = withPrefer({ ...baseHeaders(), 'Content-Type': 'application/json' }, 'return=representation');
const maskedHeaders = { ...headers } as Record<string, string>;
if (maskedHeaders.Authorization) {
const a = maskedHeaders.Authorization as string;
maskedHeaders.Authorization = a.slice(0,6) + '...' + a.slice(-6);
}
console.debug('[vincularUserIdPaciente] PATCH', url, 'payload:', { ...payload }, 'headers(masked):', maskedHeaders);
// Logs removidos por segurança
const res = await fetch(url, {
method: 'PATCH',
@ -2223,7 +2198,7 @@ export async function vincularUserIdPaciente(pacienteId: string | number, userId
const arr = await parse<Paciente[] | Paciente>(res);
return Array.isArray(arr) ? arr[0] : (arr as Paciente);
} catch (err) {
console.error('[vincularUserIdPaciente] erro ao vincular:', { pacienteId: idStr, userId, url });
console.error('[vincularUserIdPaciente] erro ao vincular - falha na requisição');
throw err;
}
}
@ -2232,8 +2207,7 @@ export async function vincularUserIdPaciente(pacienteId: string | number, userId
export async function atualizarMedico(id: string | number, input: MedicoInput): Promise<Medico> {
console.log(`Tentando atualizar médico ID: ${id}`);
console.log(`Payload original:`, input);
// Logs removidos por segurança
// Criar um payload limpo apenas com campos básicos que sabemos que existem
const cleanPayload = {
@ -2280,7 +2254,7 @@ export async function atualizarMedico(id: string | number, input: MedicoInput):
// Atualizar apenas no Supabase (dados reais)
try {
const url = `${REST}/doctors?id=eq.${id}`;
console.log(`URL de atualização: ${url}`);
// Log removido por segurança
const res = await fetch(url, {
method: "PATCH",
@ -2288,12 +2262,12 @@ export async function atualizarMedico(id: string | number, input: MedicoInput):
body: JSON.stringify(cleanPayload),
});
console.log(`Resposta do servidor: ${res.status} ${res.statusText}`);
// Log removido por segurança
if (res.ok) {
const arr = await parse<Medico[] | Medico>(res);
const result = Array.isArray(arr) ? arr[0] : (arr as Medico);
console.log('Médico atualizado no Supabase:', result);
// Log removido por segurança
return result;
} else {
// Vamos tentar ver o erro detalhado

View File

@ -139,15 +139,12 @@ export async function listAssignmentsForPatient(patientId: string): Promise<Pati
* Útil para obter os patient_id dos pacientes atribuídos ao usuário.
*/
export async function listAssignmentsForUser(userId: string): Promise<PatientAssignment[]> {
console.log(`🔍 [ASSIGNMENT] Listando atribuições para o usuário: ${userId}`);
// Log removido por segurança
const url = `${ASSIGNMENTS_URL}?user_id=eq.${encodeURIComponent(userId)}`;
try {
const headers = getHeaders();
console.debug('[ASSIGNMENT] GET', url, 'headers(masked)=', {
...headers,
Authorization: headers.Authorization ? '<<masked>>' : undefined,
});
// Logs removidos por segurança
const response = await fetch(url, {
method: 'GET',
headers,
@ -156,7 +153,7 @@ export async function listAssignmentsForUser(userId: string): Promise<PatientAss
// dump raw text for debugging when content-type isn't JSON or when empty
const contentType = response.headers.get('content-type') || '';
const txt = await response.clone().text().catch(() => '');
console.debug('[ASSIGNMENT] response status=', response.status, response.statusText, 'content-type=', contentType, 'bodyPreview=', txt ? (txt.length > 1000 ? txt.slice(0,1000) + '...[truncated]' : txt) : '<empty>');
// Log removido por segurança
if (!response.ok) {
const errorBody = txt || '';

View File

@ -140,22 +140,14 @@ export async function listarRelatorios(filtros?: { patient_id?: string; status?:
cabecalhos['Authorization'] = `Bearer ${token}`;
}
// Logs de depuração (mask token)
const masked = token ? `${token.slice(0, 6)}...${token.slice(-6)}` : null;
console.log('[listarRelatorios] URL:', url);
console.log('[listarRelatorios] Authorization (masked):', masked);
console.log('[listarRelatorios] Headers (masked):', {
...cabecalhos,
Authorization: cabecalhos['Authorization'] ? '<<masked>>' : undefined,
});
// Logs removidos por segurança
const resposta = await fetch(url, {
method: 'GET',
headers: cabecalhos,
});
console.log('[listarRelatorios] Status:', resposta.status, resposta.statusText);
// Logs removidos por segurança
const dados = await resposta.json().catch(() => null);
console.log('[listarRelatorios] Payload:', dados);
if (!resposta.ok) throw new Error('Erro ao buscar relatórios');
if (Array.isArray(dados)) return dados;
if (dados && Array.isArray(dados.data)) return dados.data;
@ -170,14 +162,14 @@ export async function listarRelatorios(filtros?: { patient_id?: string; status?:
*/
export async function buscarRelatorioPorId(id: string): Promise<Report> {
try {
console.log('🔍 [API RELATÓRIOS] Buscando relatório ID:', id);
// Log removido por segurança
const resposta = await fetch(`${BASE_API_RELATORIOS}?id=eq.${id}`, {
method: 'GET',
headers: obterCabecalhos(),
});
const resultado = await tratarRespostaApi<Report[]>(resposta);
const relatorio = Array.isArray(resultado) && resultado.length > 0 ? resultado[0] : null;
console.log('✅ [API RELATÓRIOS] Relatório encontrado:', relatorio);
// Log removido por segurança
if (!relatorio) throw new Error('Relatório não encontrado');
return relatorio;
} catch (erro) {
@ -191,16 +183,14 @@ export async function buscarRelatorioPorId(id: string): Promise<Report> {
*/
export async function criarRelatorio(dadosRelatorio: CreateReportData, token?: string): Promise<Report> {
const headers = obterCabecalhos(token);
const masked = (headers as any)['Authorization'] ? String((headers as any)['Authorization']).replace(/Bearer\s+(.+)/, 'Bearer <token_masked>') : null;
console.log('[criarRelatorio] POST', BASE_API_RELATORIOS);
console.log('[criarRelatorio] Headers (masked):', { ...headers, Authorization: masked });
// Logs removidos por segurança
const resposta = await fetch(BASE_API_RELATORIOS, {
method: 'POST',
headers,
body: JSON.stringify(dadosRelatorio),
});
console.log('[criarRelatorio] Status:', resposta.status, resposta.statusText);
// Log removido por segurança
if (!resposta.ok) {
let mensagemErro = `HTTP ${resposta.status}: ${resposta.statusText}`;
try {
@ -229,8 +219,7 @@ export async function criarRelatorio(dadosRelatorio: CreateReportData, token?: s
*/
export async function atualizarRelatorio(id: string, dadosRelatorio: UpdateReportData): Promise<Report> {
try {
console.log('📝 [API RELATÓRIOS] Atualizando relatório ID:', id);
console.log('📤 [API RELATÓRIOS] Dados:', dadosRelatorio);
// Logs removidos por segurança
const resposta = await fetch(`${BASE_API_RELATORIOS}?id=eq.${id}`, {
method: 'PATCH',
headers: obterCabecalhos(),
@ -238,7 +227,7 @@ export async function atualizarRelatorio(id: string, dadosRelatorio: UpdateRepor
});
const resultado = await tratarRespostaApi<Report[]>(resposta);
const relatorio = Array.isArray(resultado) && resultado.length > 0 ? resultado[0] : null;
console.log('✅ [API RELATÓRIOS] Relatório atualizado:', relatorio);
// Log removido por segurança
if (!relatorio) throw new Error('Relatório não encontrado');
return relatorio;
} catch (erro) {
@ -252,13 +241,13 @@ export async function atualizarRelatorio(id: string, dadosRelatorio: UpdateRepor
*/
export async function deletarRelatorio(id: string): Promise<void> {
try {
console.log('🗑️ [API RELATÓRIOS] Deletando relatório ID:', id);
// Log removido por segurança
const resposta = await fetch(`${BASE_API_RELATORIOS}/${id}`, {
method: 'DELETE',
headers: obterCabecalhos(),
});
await tratarRespostaApi<void>(resposta);
console.log('✅ [API RELATÓRIOS] Relatório deletado com sucesso');
// Log removido por segurança
} catch (erro) {
console.error('❌ [API RELATÓRIOS] Erro ao deletar relatório:', erro);
throw erro;
@ -270,20 +259,19 @@ export async function deletarRelatorio(id: string): Promise<void> {
*/
export async function listarRelatoriosPorPaciente(idPaciente: string): Promise<Report[]> {
try {
console.log('👤 [API RELATÓRIOS] Buscando relatórios do paciente:', idPaciente);
// Logs removidos por segurança
// Try a strict eq lookup first (encode the id)
const encodedId = encodeURIComponent(String(idPaciente));
let url = `${BASE_API_RELATORIOS}?patient_id=eq.${encodedId}`;
const headers = obterCabecalhos();
const masked = (headers as any)['Authorization'] ? `${String((headers as any)['Authorization']).slice(0,6)}...${String((headers as any)['Authorization']).slice(-6)}` : null;
console.debug('[listarRelatoriosPorPaciente] URL:', url);
console.debug('[listarRelatoriosPorPaciente] Headers (masked):', { ...headers, Authorization: masked ? '<<masked>>' : undefined });
// Logs removidos por segurança
const resposta = await fetch(url, {
method: 'GET',
headers,
});
const resultado = await tratarRespostaApi<Report[]>(resposta);
console.log('✅ [API RELATÓRIOS] Relatórios do paciente encontrados (eq):', resultado.length);
// Log removido por segurança
// If eq returned results, return them. Otherwise retry using `in.(id)` which some setups prefer.
if (Array.isArray(resultado) && resultado.length) return resultado;
@ -291,13 +279,13 @@ export async function listarRelatoriosPorPaciente(idPaciente: string): Promise<R
try {
const inClause = encodeURIComponent(`(${String(idPaciente)})`);
const urlIn = `${BASE_API_RELATORIOS}?patient_id=in.${inClause}`;
console.debug('[listarRelatoriosPorPaciente] retrying with IN clause URL:', urlIn);
// Log removido por segurança
const resp2 = await fetch(urlIn, { method: 'GET', headers });
const res2 = await tratarRespostaApi<Report[]>(resp2);
console.log('✅ [API RELATÓRIOS] Relatórios do paciente encontrados (in):', Array.isArray(res2) ? res2.length : 0);
// Log removido por segurança
return Array.isArray(res2) ? res2 : [];
} catch (e) {
console.warn('[listarRelatoriosPorPaciente] fallback in.() failed', e);
// Log removido por segurança
}
return [];
@ -315,15 +303,13 @@ export async function listarRelatoriosPorMedico(idMedico: string): Promise<Repor
console.log('👨‍⚕️ [API RELATÓRIOS] Buscando relatórios do médico:', idMedico);
const url = `${BASE_API_RELATORIOS}?requested_by=eq.${idMedico}`;
const headers = obterCabecalhos();
const masked = (headers as any)['Authorization'] ? `${String((headers as any)['Authorization']).slice(0,6)}...${String((headers as any)['Authorization']).slice(-6)}` : null;
console.debug('[listarRelatoriosPorMedico] URL:', url);
console.debug('[listarRelatoriosPorMedico] Headers (masked):', { ...headers, Authorization: masked ? '<<masked>>' : undefined });
// Logs removidos por segurança
const resposta = await fetch(url, {
method: 'GET',
headers: obterCabecalhos(),
});
const resultado = await tratarRespostaApi<Report[]>(resposta);
console.log('✅ [API RELATÓRIOS] Relatórios do médico encontrados:', resultado.length);
// Log removido por segurança
return resultado;
} catch (erro) {
console.error('❌ [API RELATÓRIOS] Erro ao buscar relatórios do médico:', erro);
@ -346,13 +332,11 @@ export async function listarRelatoriosPorPacientes(ids: string[]): Promise<Repor
const inClause = cleaned.join(',');
const url = `${BASE_API_RELATORIOS}?patient_id=in.(${inClause})`;
const headers = obterCabecalhos();
const masked = (headers as any)['Authorization'] ? '<<masked>>' : undefined;
console.debug('[listarRelatoriosPorPacientes] URL:', url);
console.debug('[listarRelatoriosPorPacientes] Headers (masked):', { ...headers, Authorization: masked ? '<<masked>>' : undefined });
// Logs removidos por segurança
const resposta = await fetch(url, { method: 'GET', headers });
const resultado = await tratarRespostaApi<Report[]>(resposta);
console.log('✅ [API RELATÓRIOS] Relatórios encontrados para pacientes:', resultado.length);
// Log removido por segurança
return resultado;
} catch (erro) {
console.error('❌ [API RELATÓRIOS] Erro ao buscar relatórios para vários pacientes:', erro);
@ -368,26 +352,26 @@ export async function listarRelatoriosPorPacientes(ids: string[]): Promise<Repor
export async function listarRelatoriosParaMedicoAtribuido(userId?: string): Promise<Report[]> {
try {
if (!userId) {
console.warn('[listarRelatoriosParaMedicoAtribuido] userId ausente, retornando array vazio');
// Log removido por segurança
return [];
}
console.log('[listarRelatoriosParaMedicoAtribuido] buscando assignments para user:', userId);
// Log removido por segurança
// importe dinamicamente para evitar possíveis ciclos
const assignmentMod = await import('./assignment');
const assigns = await assignmentMod.listAssignmentsForUser(String(userId));
if (!assigns || !Array.isArray(assigns) || assigns.length === 0) {
console.log('[listarRelatoriosParaMedicoAtribuido] nenhum paciente atribuído encontrado para user:', userId);
// Log removido por segurança
return [];
}
const patientIds = Array.from(new Set(assigns.map((a: any) => String(a.patient_id)).filter(Boolean)));
if (!patientIds.length) {
console.log('[listarRelatoriosParaMedicoAtribuido] nenhuma patient_id válida encontrada nas atribuições');
// Log removido por segurança
return [];
}
console.log('[listarRelatoriosParaMedicoAtribuido] carregando relatórios para pacientes:', patientIds);
// Log removido por segurança
const rels = await listarRelatoriosPorPacientes(patientIds);
return rels || [];
} catch (err) {