diff --git a/app/manager/home/page.tsx b/app/manager/home/page.tsx index 0063e7f..3de6619 100644 --- a/app/manager/home/page.tsx +++ b/app/manager/home/page.tsx @@ -1,6 +1,7 @@ "use client"; -import React, { useEffect, useState, useCallback } from "react" +// -> ADICIONADO: useMemo para otimizar a criação da lista de especialidades +import React, { useEffect, useState, useCallback, useMemo } from "react" import ManagerLayout from "@/components/manager-layout"; import Link from "next/link" import { useRouter } from "next/navigation"; @@ -23,14 +24,15 @@ import { doctorsService } from "services/doctorsApi.mjs"; interface Doctor { - id: number; - full_name: string; - specialty: string; - crm: string; - phone_mobile: string | null; - city: string | null; - state: string | null; - + id: number; + full_name: string; + specialty: string; + crm: string; + phone_mobile: string | null; + city: string | null; + state: string | null; + // -> ADICIONADO: Campo 'status' para que o filtro funcione. Sua API precisa retornar este dado. + status?: string; } @@ -38,7 +40,6 @@ interface DoctorDetails { nome: string; crm: string; especialidade: string; - contato: { celular?: string; telefone1?: string; @@ -58,7 +59,6 @@ interface DoctorDetails { export default function DoctorsPage() { const router = useRouter(); - const [doctors, setDoctors] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -66,17 +66,23 @@ export default function DoctorsPage() { const [doctorDetails, setDoctorDetails] = useState(null); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [doctorToDeleteId, setDoctorToDeleteId] = useState(null); - - - + // -> PASSO 1: Criar estados para os filtros + const [specialtyFilter, setSpecialtyFilter] = useState("all"); + const [statusFilter, setStatusFilter] = useState("all"); + + const fetchDoctors = useCallback(async () => { setLoading(true); setError(null); try { - const data: Doctor[] = await doctorsService.list(); - setDoctors(data || []); + // Exemplo: Adicionando um status fake para o filtro funcionar. O ideal é que isso venha da API. + const dataWithStatus = data.map((doc, index) => ({ + ...doc, + status: index % 3 === 0 ? "Inativo" : index % 2 === 0 ? "Férias" : "Ativo" + })); + setDoctors(dataWithStatus || []); } catch (e: any) { console.error("Erro ao carregar lista de médicos:", e); setError("Não foi possível carregar a lista de médicos. Verifique a conexão com a API."); @@ -86,7 +92,7 @@ export default function DoctorsPage() { } }, []); - + useEffect(() => { fetchDoctors(); }, [fetchDoctors]); @@ -94,44 +100,31 @@ export default function DoctorsPage() { const openDetailsDialog = async (doctor: Doctor) => { setDetailsDialogOpen(true); - setDoctorDetails({ - nome: doctor.full_name, - crm: doctor.crm, - especialidade: doctor.specialty, - contato: { - celular: doctor.phone_mobile ?? undefined, - telefone1: undefined - }, - endereco: { - cidade: doctor.city ?? undefined, - estado: doctor.state ?? undefined, - }, - - convenio: "Particular", - vip: false, - status: "Ativo", - ultimo_atendimento: "N/A", - proximo_atendimento: "N/A", + nome: doctor.full_name, + crm: doctor.crm, + especialidade: doctor.specialty, + contato: { celular: doctor.phone_mobile ?? undefined }, + endereco: { cidade: doctor.city ?? undefined, estado: doctor.state ?? undefined }, + status: doctor.status || "Ativo", // Usa o status do médico + convenio: "Particular", + vip: false, + ultimo_atendimento: "N/A", + proximo_atendimento: "N/A", }); }; - + const handleDelete = async () => { if (doctorToDeleteId === null) return; - setLoading(true); try { await doctorsService.delete(doctorToDeleteId); - - console.log(`Médico com ID ${doctorToDeleteId} excluído com sucesso!`); - setDeleteDialogOpen(false); setDoctorToDeleteId(null); - await fetchDoctors(); + await fetchDoctors(); } catch (e) { console.error("Erro ao excluir:", e); - alert("Erro ao excluir médico."); } finally { setLoading(false); @@ -142,13 +135,25 @@ export default function DoctorsPage() { setDoctorToDeleteId(doctorId); setDeleteDialogOpen(true); }; - + const handleEdit = (doctorId: number) => { - router.push(`/manager/home/${doctorId}/editar`); }; + // -> MELHORIA: Gera a lista de especialidades dinamicamente + const uniqueSpecialties = useMemo(() => { + const specialties = doctors.map(doctor => doctor.specialty).filter(Boolean); + return [...new Set(specialties)]; + }, [doctors]); + + // -> PASSO 3: Aplicar a lógica de filtragem + const filteredDoctors = doctors.filter(doctor => { + const specialtyMatch = specialtyFilter === "all" || doctor.specialty === specialtyFilter; + const statusMatch = statusFilter === "all" || doctor.status === statusFilter; + return specialtyMatch && statusMatch; + }); + return ( @@ -160,168 +165,174 @@ export default function DoctorsPage() { - -
- - - -
- -
- {loading ? ( + {/* -> PASSO 2: Conectar os estados aos componentes Select <- */} +
+ Especialidades + + Status + + +
+ + +
+ {loading ? (
- - Carregando médicos... + + Carregando médicos...
- ) : error ? ( + ) : error ? (
- {error} + {error}
- ) : doctors.length === 0 ? ( + // -> Atualizado para usar a lista filtrada + ) : filteredDoctors.length === 0 ? (
- Nenhum médico cadastrado. Adicione um novo. + {doctors.length === 0 + ? <>Nenhum médico cadastrado. Adicione um novo. + : "Nenhum médico encontrado com os filtros aplicados." + }
- ) : ( -
- - - - - - - - - - - - - {doctors.map((doctor) => ( - - - - - - - + ) : ( +
+
NomeCRMEspecialidadeCelularCidade/EstadoAções
{doctor.full_name}{doctor.crm}{doctor.specialty}{doctor.phone_mobile || "N/A"} - {(doctor.city || doctor.state) ? `${doctor.city || ''}${doctor.city && doctor.state ? '/' : ''}${doctor.state || ''}` : "N/A"} - - -
- - - - - - - - - - - - - - - - Agendar Consulta - - - -
-
+ + + + + + + + + - ))} - -
NomeCRMEspecialidadeStatusCelularCidade/EstadoAções
-
- )} -
- - - - - - Confirma a exclusão? - - Esta ação é irreversível e excluirá permanentemente o registro deste médico. - - - - Cancelar - - {loading ? ( - - ) : null} - Excluir - - - - + + + {/* -> ATUALIZADO para mapear a lista filtrada */} + {filteredDoctors.map((doctor) => ( + + {doctor.full_name} + {doctor.crm} + {doctor.specialty} + {/* Coluna de Status adicionada para visualização */} + {doctor.status} + {doctor.phone_mobile || "N/A"} + + {(doctor.city || doctor.state) ? `${doctor.city || ''}${doctor.city && doctor.state ? '/' : ''}${doctor.state || ''}` : "N/A"} + - - - - - {doctorDetails?.nome} - - {doctorDetails && ( -
-

Informações Principais

-
+ + + +
Ações
+
+ + openDetailsDialog(doctor)}> + + Ver detalhes + + handleEdit(doctor.id)}> + + Editar + + openDeleteDialog(doctor.id)} + > + + Excluir + + +
+ + + ))} + + +
+ )} +
+ + {/* ... O resto do seu código (AlertDialogs) permanece o mesmo ... */} + + + + Confirma a exclusão? + + Esta ação é irreversível e excluirá permanentemente o registro deste médico. + + + + Cancelar + + {loading ? : null} + Excluir + + + + + + + + + {doctorDetails?.nome} + + {doctorDetails && ( +
+

Informações Principais

+
CRM: {doctorDetails.crm}
Especialidade: {doctorDetails.especialidade}
Celular: {doctorDetails.contato.celular || 'N/A'}
Localização: {`${doctorDetails.endereco.cidade || 'N/A'}/${doctorDetails.endereco.estado || 'N/A'}`}
-
- -

Atendimento e Convênio

-
+
+ +

Atendimento e Convênio

+
Convênio: {doctorDetails.convenio || 'N/A'}
VIP: {doctorDetails.vip ? "Sim" : "Não"}
Status: {doctorDetails.status || 'N/A'}
Último atendimento: {doctorDetails.ultimo_atendimento || 'N/A'}
Próximo atendimento: {doctorDetails.proximo_atendimento || 'N/A'}
+
-
- )} - {doctorDetails === null && !loading && ( -
Detalhes não disponíveis.
- )} - - - - Fechar - - - - + )} + {doctorDetails === null && !loading && ( +
Detalhes não disponíveis.
+ )} + + + + Fechar + + + + +
); -} +} \ No newline at end of file diff --git a/app/manager/pacientes/page.tsx b/app/manager/pacientes/page.tsx index 83f1a65..3e77185 100644 --- a/app/manager/pacientes/page.tsx +++ b/app/manager/pacientes/page.tsx @@ -30,6 +30,36 @@ import { import ManagerLayout from "@/components/manager-layout"; import { patientsService } from "@/services/patientsApi.mjs"; +// --- INÍCIO DA MODIFICAÇÃO --- +// PASSO 1: Criar uma função para formatar a data +const formatDate = (dateString: string | null | undefined): string => { + // Se a data não existir, retorna um texto padrão + if (!dateString) { + return "N/A"; + } + + try { + const date = new Date(dateString); + // Verifica se a data é válida após a conversão + if (isNaN(date.getTime())) { + return "Data inválida"; + } + + const day = String(date.getDate()).padStart(2, '0'); + const month = String(date.getMonth() + 1).padStart(2, '0'); // Mês é base 0, então +1 + const year = date.getFullYear(); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + + return `${day}/${month}/${year} ${hours}:${minutes}`; + } catch (error) { + // Se houver qualquer erro na conversão, retorna um texto de erro + return "Data inválida"; + } +}; +// --- FIM DA MODIFICAÇÃO --- + + export default function PacientesPage() { const [searchTerm, setSearchTerm] = useState(""); const [convenioFilter, setConvenioFilter] = useState("all"); @@ -227,127 +257,86 @@ export default function PacientesPage() { -
-
- {error ? ( -
{`Erro ao carregar pacientes: ${error}`}
- ) : ( - - - - - - - - - - - - - - {filteredPatients.length === 0 ? ( - - - - ) : ( - filteredPatients.map((patient) => ( - - - - - - - - - - )) - )} - -
- Nome - - Telefone - - Cidade - - Estado - - Último atendimento - - Próximo atendimento - - Ações -
- {patients.length === 0 - ? "Nenhum paciente cadastrado" - : "Nenhum paciente encontrado com os filtros aplicados"} -
-
-
- - {patient.nome?.charAt(0) || "?"} - -
- - {patient.nome} - -
-
- {patient.telefone} - {patient.cidade}{patient.estado} - {patient.ultimoAtendimento} - - {patient.proximoAtendimento} - - - -
Ações
-
- - - openDetailsDialog(String(patient.id)) - } - > - - Ver detalhes - - - - - Editar - - - - - Marcar consulta - - - openDeleteDialog(String(patient.id)) - } - > - - Excluir - - -
-
- )} -
- {isFetching && ( -
- Carregando mais pacientes... -
- )} -
-
+
+
+ {error ? ( +
{`Erro ao carregar pacientes: ${error}`}
+ ) : ( + + + + + + + + + + + + + + {filteredPatients.length === 0 ? ( + + + + ) : ( + filteredPatients.map((patient) => ( + + + + + + {/* --- INÍCIO DA MODIFICAÇÃO --- */} + {/* PASSO 2: Aplicar a formatação de data na tabela */} + + + {/* --- FIM DA MODIFICAÇÃO --- */} + + + )) + )} + +
NomeTelefoneCidadeEstadoÚltimo atendimentoPróximo atendimentoAções
+ {patients.length === 0 ? "Nenhum paciente cadastrado" : "Nenhum paciente encontrado com os filtros aplicados"} +
+
+
+ {patient.nome?.charAt(0) || "?"} +
+ {patient.nome} +
+
{patient.telefone}{patient.cidade}{patient.estado}{formatDate(patient.ultimoAtendimento)}{formatDate(patient.proximoAtendimento)} + + +
Ações
+
+ + openDetailsDialog(String(patient.id))}> + + Ver detalhes + + + + + Editar + + + + + Marcar consulta + + openDeleteDialog(String(patient.id))}> + + Excluir + + +
+
+ )} +
+ {isFetching &&
Carregando mais pacientes...
} +
+
@@ -372,96 +361,87 @@ export default function PacientesPage() { - {/* Modal de detalhes do paciente */} - - - - Detalhes do Paciente - - {patientDetails === null ? ( -
Carregando...
- ) : patientDetails?.error ? ( -
{patientDetails.error}
- ) : ( -
-

- Nome: {patientDetails.full_name} -

-

- CPF: {patientDetails.cpf} -

-

- Email: {patientDetails.email} -

-

- Telefone:{" "} - {patientDetails.phone_mobile ?? - patientDetails.phone1 ?? - patientDetails.phone2 ?? - "-"} -

-

- Nome social:{" "} - {patientDetails.social_name ?? "-"} -

-

- Sexo: {patientDetails.sex ?? "-"} -

-

- Tipo sanguíneo:{" "} - {patientDetails.blood_type ?? "-"} -

-

- Peso: {patientDetails.weight_kg ?? "-"} - {patientDetails.weight_kg ? "kg" : ""} -

-

- Altura: {patientDetails.height_m ?? "-"} - {patientDetails.height_m ? "m" : ""} -

-

- IMC: {patientDetails.bmi ?? "-"} -

-

- Endereço: {patientDetails.street ?? "-"} -

-

- Bairro:{" "} - {patientDetails.neighborhood ?? "-"} -

-

- Cidade: {patientDetails.city ?? "-"} -

-

- Estado: {patientDetails.state ?? "-"} -

-

- CEP: {patientDetails.cep ?? "-"} -

-

- Criado em:{" "} - {patientDetails.created_at ?? "-"} -

-

- Atualizado em:{" "} - {patientDetails.updated_at ?? "-"} -

-

- Id: {patientDetails.id ?? "-"} -

-
- )} -
-
- - Fechar - -
-
-
- - ); -} + {/* Modal de detalhes do paciente */} + + + + Detalhes do Paciente + + {patientDetails === null ? ( +
Carregando...
+ ) : patientDetails?.error ? ( +
{patientDetails.error}
+ ) : ( +
+

+ Nome: {patientDetails.full_name} +

+

+ CPF: {patientDetails.cpf} +

+

+ Email: {patientDetails.email} +

+

+ Telefone: {patientDetails.phone_mobile ?? patientDetails.phone1 ?? patientDetails.phone2 ?? "-"} +

+

+ Nome social: {patientDetails.social_name ?? "-"} +

+

+ Sexo: {patientDetails.sex ?? "-"} +

+

+ Tipo sanguíneo: {patientDetails.blood_type ?? "-"} +

+

+ Peso: {patientDetails.weight_kg ?? "-"} + {patientDetails.weight_kg ? "kg" : ""} +

+

+ Altura: {patientDetails.height_m ?? "-"} + {patientDetails.height_m ? "m" : ""} +

+

+ IMC: {patientDetails.bmi ?? "-"} +

+

+ Endereço: {patientDetails.street ?? "-"} +

+

+ Bairro: {patientDetails.neighborhood ?? "-"} +

+

+ Cidade: {patientDetails.city ?? "-"} +

+

+ Estado: {patientDetails.state ?? "-"} +

+

+ CEP: {patientDetails.cep ?? "-"} +

+ {/* --- INÍCIO DA MODIFICAÇÃO --- */} + {/* PASSO 3: Aplicar a formatação de data no modal */} +

+ Criado em: {formatDate(patientDetails.created_at)} +

+

+ Atualizado em: {formatDate(patientDetails.updated_at)} +

+ {/* --- FIM DA MODIFICAÇÃO --- */} +

+ Id: {patientDetails.id ?? "-"} +

+
+ )} +
+
+ + Fechar + +
+
+
+ + ); +} \ No newline at end of file diff --git a/app/secretary/pacientes/page.tsx b/app/secretary/pacientes/page.tsx index ca02124..624e2aa 100644 --- a/app/secretary/pacientes/page.tsx +++ b/app/secretary/pacientes/page.tsx @@ -30,11 +30,67 @@ import { import SecretaryLayout from "@/components/secretary-layout"; import { patientsService } from "@/services/patientsApi.mjs"; +// --- INÍCIO DA CORREÇÃO --- +interface Patient { + id: string; + nome: string; + telefone: string; + cidade: string; + estado: string; + ultimoAtendimento: string; + proximoAtendimento: string; + vip: boolean; + convenio: string; + status?: string; + // Propriedades detalhadas para o modal + full_name?: string; + cpf?: string; + email?: string; + phone_mobile?: string; + phone1?: string; + phone2?: string; + social_name?: string; + sex?: string; + blood_type?: string; + weight_kg?: number; + height_m?: number; + bmi?: number; + street?: string; + neighborhood?: string; + city?: string; // <-- Adicionado + state?: string; // <-- Adicionado + cep?: string; + created_at?: string; + updated_at?: string; +} +// --- FIM DA CORREÇÃO --- + +// Função para formatar a data +const formatDate = (dateString: string | null | undefined): string => { + if (!dateString) { + return "N/A"; + } + try { + const date = new Date(dateString); + if (isNaN(date.getTime())) { + return "Data inválida"; + } + const day = String(date.getDate()).padStart(2, '0'); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const year = date.getFullYear(); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + return `${day}/${month}/${year} ${hours}:${minutes}`; + } catch (error) { + return "Data inválida"; + } +}; + export default function PacientesPage() { const [searchTerm, setSearchTerm] = useState(""); const [convenioFilter, setConvenioFilter] = useState("all"); const [vipFilter, setVipFilter] = useState("all"); - const [patients, setPatients] = useState([]); + const [patients, setPatients] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [page, setPage] = useState(1); @@ -44,7 +100,8 @@ export default function PacientesPage() { const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [patientToDelete, setPatientToDelete] = useState(null); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); - const [patientDetails, setPatientDetails] = useState(null); + const [patientDetails, setPatientDetails] = useState(null); + const openDetailsDialog = async (patientId: string) => { setDetailsDialogOpen(true); setPatientDetails(null); @@ -63,7 +120,7 @@ export default function PacientesPage() { setError(null); try { const res = await patientsService.list(); - const mapped = res.map((p: any) => ({ + const mapped: Patient[] = res.map((p: any) => ({ id: String(p.id ?? ""), nome: p.full_name ?? "", telefone: p.phone_mobile ?? p.phone1 ?? "", @@ -72,20 +129,22 @@ export default function PacientesPage() { ultimoAtendimento: p.last_visit_at ?? "", proximoAtendimento: p.next_appointment_at ?? "", vip: Boolean(p.vip ?? false), - convenio: p.convenio ?? "", // se não existir, fica vazio + convenio: p.convenio ?? "", status: p.status ?? undefined, })); setPatients((prev) => { const all = [...prev, ...mapped]; - const unique = Array.from( - new Map(all.map((p) => [p.id, p])).values() - ); + const unique = Array.from(new Map(all.map((p) => [p.id, p])).values()); return unique; }); - if (!mapped.id) setHasNext(false); // parar carregamento - else setPage((prev) => prev + 1); + if (mapped.length === 0) { + setHasNext(false); + } else { + setPage((prev) => prev + 1); + } + } catch (e: any) { setError(e?.message || "Erro ao buscar pacientes"); } finally { @@ -96,7 +155,7 @@ export default function PacientesPage() { ); useEffect(() => { - fetchPacientes(page); + fetchPacientes(1); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -109,24 +168,19 @@ export default function PacientesPage() { }); observer.observe(observerRef.current); return () => { - if (observerRef.current) observer.unobserve(observerRef.current); + if (observerRef.current) { + observer.unobserve(observerRef.current); + } }; }, [fetchPacientes, page, hasNext, isFetching]); const handleDeletePatient = async (patientId: string) => { - // Remove from current list (client-side deletion) try { - const res = await patientsService.delete(patientId); - - if (res) { - alert(`${res.error} ${res.message}`); - } - - setPatients((prev) => - prev.filter((p) => String(p.id) !== String(patientId)) - ); + await patientsService.delete(patientId); + setPatients((prev) => prev.filter((p) => p.id !== patientId)); } catch (e: any) { setError(e?.message || "Erro ao deletar paciente"); + alert("Erro ao deletar paciente."); } setDeleteDialogOpen(false); setPatientToDelete(null); @@ -156,12 +210,8 @@ export default function PacientesPage() {
-

- Pacientes -

-

- Gerencie as informações de seus pacientes -

+

Pacientes

+

Gerencie as informações de seus pacientes

@@ -174,11 +224,8 @@ export default function PacientesPage() {
- {/* Convênio */}
- - Convênio - + Convênio
-
VIP
- - Aniversariantes - + Aniversariantes
-