feat(admin, patient): implementa criação condicional e corrige layouts

Refatora o formulário de criação de usuários no painel do manager para lidar com a lógica de múltiplos endpoints, diferenciando a criação de médicos das demais roles.

- Adiciona campos condicionais para CRM e especialidade na UI.
- Implementa a chamada ao endpoint `/functions/v1/create-doctor` para a role "medico".
- Ajusta o payload para o endpoint `/create-user-with-password` para as outras roles.

fix(patient): corrige renderização duplicada do layout nas páginas de agendamento e consultas, removendo o wrapper redundante do `PatientLayout`.

refactor(services): ajusta os serviços `doctorsApi` e `usersApi` para alinhar com os schemas de dados corretos da API.
This commit is contained in:
Gabriel Lira Figueira 2025-11-05 01:35:44 -03:00
parent 50fd9141ce
commit f8f5f8214a
6 changed files with 106 additions and 24 deletions

View File

@ -1,3 +1,5 @@
// /app/manager/usuario/novo/page.tsx
"use client";
import { useState } from "react";
@ -9,7 +11,8 @@ import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Save, Loader2, Pause } from "lucide-react";
import ManagerLayout from "@/components/manager-layout";
import { usersService } from "services/usersApi.mjs";
import { usersService } from "@/services/usersApi.mjs";
import { doctorsService } from "@/services/doctorsApi.mjs"; // Importação adicionada
import { login } from "services/api.mjs";
interface UserFormData {
@ -20,6 +23,10 @@ interface UserFormData {
senha: string;
confirmarSenha: string;
cpf: string;
// Novos campos para Médico
crm: string;
crm_uf: string;
specialty: string;
}
const defaultFormData: UserFormData = {
@ -30,6 +37,10 @@ const defaultFormData: UserFormData = {
senha: "",
confirmarSenha: "",
cpf: "",
// Valores iniciais para campos de Médico
crm: "",
crm_uf: "",
specialty: "",
};
const cleanNumber = (value: string): string => value.replace(/\D/g, "");
@ -47,7 +58,13 @@ export default function NovoUsuarioPage() {
const [error, setError] = useState<string | null>(null);
const handleInputChange = (key: keyof UserFormData, value: string) => {
const updatedValue = key === "telefone" ? formatPhone(value) : value;
let updatedValue = value;
if (key === "telefone") {
updatedValue = formatPhone(value);
} else if (key === "crm_uf") {
// Converte UF para maiúsculas
updatedValue = value.toUpperCase();
}
setFormData((prev) => ({ ...prev, [key]: updatedValue }));
};
@ -65,22 +82,56 @@ export default function NovoUsuarioPage() {
return;
}
// Validação adicional para Médico
if (formData.papel === "medico") {
if (!formData.crm || !formData.crm_uf) {
setError("Para a função 'Médico', o CRM e a UF do CRM são obrigatórios.");
return;
}
}
setIsSaving(true);
try {
const payload = {
full_name: formData.nomeCompleto,
email: formData.email.trim().toLowerCase(),
phone: formData.telefone || null,
role: formData.papel,
password: formData.senha,
cpf: formData.cpf,
};
if (formData.papel === "medico") {
// Lógica para criação de Médico
const doctorPayload = {
email: formData.email.trim().toLowerCase(),
full_name: formData.nomeCompleto,
cpf: formData.cpf,
crm: formData.crm,
crm_uf: formData.crm_uf,
specialty: formData.specialty || null,
phone_mobile: formData.telefone || null, // Usando phone_mobile conforme o schema
};
console.log("📤 Enviando payload:");
console.log(payload);
console.log("📤 Enviando payload para Médico:");
console.log(doctorPayload);
await usersService.create_user(payload);
// Chamada ao endpoint específico para criação de médico
await doctorsService.create(doctorPayload);
} else {
// Lógica para criação de Outras Roles
const isPatient = formData.papel === "paciente";
const userPayload = {
email: formData.email.trim().toLowerCase(),
password: formData.senha,
full_name: formData.nomeCompleto,
phone: formData.telefone || null,
role: formData.papel,
cpf: formData.cpf,
create_patient_record: isPatient, // true se a role for 'paciente'
phone_mobile: isPatient ? formData.telefone || null : undefined, // Enviar phone_mobile se for paciente
};
console.log("📤 Enviando payload para Usuário Comum:");
console.log(userPayload);
// Chamada ao endpoint padrão para criação de usuário
await usersService.create_user(userPayload);
}
router.push("/manager/usuario");
} catch (e: any) {
@ -91,6 +142,8 @@ export default function NovoUsuarioPage() {
}
};
const isMedico = formData.papel === "medico";
return (
<ManagerLayout>
<div className="w-full h-full p-4 md:p-8 flex justify-center items-start">
@ -140,6 +193,27 @@ export default function NovoUsuarioPage() {
</Select>
</div>
{/* Campos Condicionais para Médico */}
{isMedico && (
<>
<div className="space-y-2">
<Label htmlFor="crm">CRM *</Label>
<Input id="crm" value={formData.crm} onChange={(e) => handleInputChange("crm", e.target.value)} placeholder="Número do CRM" required />
</div>
<div className="space-y-2">
<Label htmlFor="crm_uf">UF do CRM *</Label>
<Input id="crm_uf" value={formData.crm_uf} onChange={(e) => handleInputChange("crm_uf", e.target.value)} placeholder="Ex: SP" maxLength={2} required />
</div>
<div className="space-y-2 md:col-span-2">
<Label htmlFor="specialty">Especialidade (opcional)</Label>
<Input id="specialty" value={formData.specialty} onChange={(e) => handleInputChange("specialty", e.target.value)} placeholder="Ex: Cardiologia" />
</div>
</>
)}
{/* Fim dos Campos Condicionais */}
<div className="space-y-2">
<Label htmlFor="senha">Senha *</Label>
<Input id="senha" type="password" value={formData.senha} onChange={(e) => handleInputChange("senha", e.target.value)} placeholder="Mínimo 8 caracteres" minLength={8} required />

View File

@ -182,8 +182,8 @@ export default function PatientAppointments() {
<div className="space-y-6">
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-bold text-gray-900">Minhas Consultas</h1>
<p className="text-gray-600">Veja, reagende ou cancele suas consultas</p>
<h1 className="text-3xl font-bold text-foreground">Minhas Consultas</h1>
<p className="text-muted-foreground">Veja, reagende ou cancele suas consultas</p>
</div>
</div>
@ -244,7 +244,13 @@ export default function PatientAppointments() {
</Card>
))
) : (
<p className="text-gray-600">Você ainda não possui consultas agendadas.</p>
<Card className="p-6 text-center">
<CalendarDays className="mx-auto h-12 w-12 text-muted-foreground mb-4" />
<CardTitle className="text-xl">Nenhuma Consulta Encontrada</CardTitle>
<CardDescription className="mt-2">
Você ainda não possui consultas agendadas. Use o menu "Agendar Consulta" para começar.
</CardDescription>
</Card>
)}
</div>
</div>

View File

@ -134,8 +134,8 @@ export default function ScheduleAppointment() {
{/* Médico */}
<div className="space-y-2">
<Label htmlFor="doctor">Médico</Label>
<Select value={selectedDoctor} onValueChange={setSelectedDoctor}>
<SelectTrigger>
<Select value={selectedDoctor} onValueChange={setSelectedDoctor} disabled={loading}>
<SelectTrigger id="doctor">
<SelectValue placeholder="Selecione um médico" />
</SelectTrigger>
<SelectContent>
@ -168,7 +168,7 @@ export default function ScheduleAppointment() {
<div className="space-y-2">
<Label htmlFor="time">Horário</Label>
<Select value={selectedTime} onValueChange={setSelectedTime}>
<SelectTrigger>
<SelectTrigger id="time">
<SelectValue placeholder="Selecione um horário" />
</SelectTrigger>
<SelectContent>

View File

@ -223,7 +223,7 @@ export default function PatientLayout({ children }: PatientLayoutProps) {
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
<Input
placeholder="Buscar paciente"
className="pl-10 bg-background border-border"
className="pl-10 bg-background border-input"
/>
</div>
</div>

View File

@ -3,7 +3,10 @@ import { api } from "./api.mjs";
export const doctorsService = {
list: () => api.get("/rest/v1/doctors"),
getById: (id) => api.get(`/rest/v1/doctors?id=eq.${id}`).then(data => data[0]),
create: (data) => api.post("/functions/v1/create-doctor", data),
async create(data) {
// Esta é a função usada no page.tsx para criar médicos
return await api.post("/functions/v1/create-doctor", data);
},
update: (id, data) => api.patch(`/rest/v1/doctors?id=eq.${id}`, data),
delete: (id) => api.delete(`/rest/v1/doctors?id=eq.${id}`),
};
};

View File

@ -1,5 +1,3 @@
// SUBSTITUA O OBJETO INTEIRO EM services/usersApi.mjs
import { api } from "./api.mjs";
export const usersService = {
@ -19,6 +17,7 @@ export const usersService = {
},
async create_user(data) {
// Esta é a função usada no page.tsx para criar usuários que não são médicos
return await api.post(`/functions/v1/create-user-with-password`, data);
},