diff --git a/.env.example b/.env.example
index e62b46e..1b61f48 100644
--- a/.env.example
+++ b/.env.example
@@ -1,4 +1,5 @@
VITE_SUPABASE_URL=https://yuanqfswhberkoevtmfr.supabase.co
+VITE_API_BASE_URL=https://yuanqfswhberkoevtmfr.supabase.co/functions/v1
VITE_SUPABASE_REST_URL=https://yuanqfswhberkoevtmfr.supabase.co/rest/v1
VITE_SUPABASE_FUNCTIONS_URL=https://yuanqfswhberkoevtmfr.supabase.co/functions/v1
VITE_SUPABASE_STORAGE_URL=https://yuanqfswhberkoevtmfr.supabase.co/storage/v1
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
new file mode 100644
index 0000000..f0c24f0
--- /dev/null
+++ b/docs/ARCHITECTURE.md
@@ -0,0 +1,44 @@
+# Arquitetura e Boas Práticas do Front-end (React)
+
+Este documento estabelece as regras canônicas de arquitetura para este projeto. **Qualquer Inteligência Artificial ou desenvolvedor que atuar neste código DEVE ler e seguir estas diretrizes rigorosamente.**
+
+O objetivo desta arquitetura é manter o ecossistema React amigável para desenvolvedores com mentalidade de Backend, priorizando a Separação de Conceitos (Separation of Concerns) e a previsibilidade dos dados.
+
+## 1. A API é a Única Fonte da Verdade (Fim dos Mocks)
+- **Regra:** Não crie, não mantenha e não faça fallback para dados mockados (falsos) em produção ou na integração.
+- **Motivo:** O banco de dados (Supabase) dita as regras. Se a API falhar, o front-end deve exibir um estado de erro elegante, e não mascarar a falha com dados locais inventados.
+- **Ação:** Repositórios (`*Repository.js`) devem apenas fazer o `fetch` seguro para a API e repassar a resposta.
+
+## 2. O Padrão MVC Adaptado (Model-View-Hook)
+Para evitar que as Páginas (`*Page.jsx`) se tornem "Componentes Deuses" (fazendo requisições, filtrando dados e renderizando HTML ao mesmo tempo), adotamos o seguinte fluxo:
+
+### A. Repositório (Acesso a Dados)
+- Fica na pasta `src/repositories/`.
+- Sua ÚNICA função é bater na API, tratar erros HTTP e devolver o JSON puro (Array ou Objeto).
+- Não deve conter regras de negócio, filtragem de tela ou formatação de datas.
+
+### B. Mappers (Tradução Estrita)
+- Fica na pasta `src/mappers/`.
+- Traduz os dados do banco para o formato que a UI espera.
+- **Regra de Ouro:** O Mapper deve ser **rígido**. Se o banco retorna `full_name`, o mapper converte para `name` e todo o resto da aplicação usa apenas `name`. Não propague a bagunça do banco para a tela.
+
+### C. Custom Hooks (O Controlador)
+- Fica na pasta `src/hooks/` (ex: `useAgenda.js`).
+- Puxa os dados do repositório, passa pelo mapper, controla os estados de `loading`, `error`, e lida com a lógica de negócio (como submissão de formulários e filtragem de abas).
+- Ele encapsula todos os `useEffect` e `useState` complexos.
+
+### D. Páginas e Componentes (A View Burra)
+- Fica em `src/pages/` e `src/components/`.
+- As páginas são estritamente cascas visuais (HTML/Tailwind).
+- Elas importam o Custom Hook, pegam as variáveis prontas e apenas decidem como desenhar isso na tela.
+
+## 3. Lidar com Datas (Fuso Horário)
+- Sempre que a API enviar uma data no formato string `YYYY-MM-DD`, lembre-se que o construtor nativo do JavaScript (`new Date('YYYY-MM-DD')`) converte para o horário UTC e, dependendo do fuso do usuário, pode jogar a data para o dia anterior.
+- **Solução:** Use o helper local `parseLocalDate` ou processe os componentes da data (ano, mês, dia) manualmente antes de criar o objeto `Date`.
+
+## Exemplo de Fluxo Ideal
+1. A página `PacientesPage.jsx` chama o hook `const { pacientes, loading } = usePacientes()`.
+2. O hook `usePacientes` chama o `patientRepository.getAll()`.
+3. O repositório faz `fetch` na API.
+4. O hook pega o resultado, passa no `patientMapper.toUi()` e atualiza o estado interno.
+5. A página renderiza os dados que chegaram do hook.
diff --git a/docs/repository-api-audit.md b/docs/repository-api-audit.md
index 4025e5b..4943b59 100644
--- a/docs/repository-api-audit.md
+++ b/docs/repository-api-audit.md
@@ -1,82 +1,49 @@
-# Auditoria dos repositories contra a API
+# Auditoria de Implementacao e Mapeamento da API
-Fonte da documentacao: https://do5wegrct3.apidog.io/llms.txt
+Este documento resume o estado atual da integracao entre o front-end e os endpoints da API.
-## Endpoints documentados
+## Integrado no front
-| Grupo | Metodo | Endpoint | Status no frontend |
-| --- | --- | --- | --- |
-| Autenticacao | POST | `/auth/v1/token?grant_type=password` | Sem repository dedicado |
-| Autenticacao | POST | `/auth/v1/otp` | Sem repository dedicado |
-| Autenticacao | POST | `/auth/v1/logout` | Sem repository dedicado |
-| Autenticacao | GET | `/auth/v1/user` | Sem repository dedicado; relacionado a `profileRepository` |
-| Usuarios | POST | `/delete-user` | Sem repository dedicado |
-| Usuarios | POST | `/functions/v1/create-user` | Sem repository dedicado |
-| Usuarios | POST | `/functions/v1/create-user-with-password` | Sem repository dedicado |
-| Usuarios | POST | `/request-password-reset` | Sem repository dedicado |
-| Usuarios | POST | `/functions/v1/user-info` | Sem repository dedicado; relacionado a `profileRepository` |
-| Usuarios | POST | `/functions/v1/user-info-by-id` | Sem repository dedicado |
-| SMS | POST | `/functions/v1/send-sms` | `communicationRepository` nao implementa chamada real |
-| Pacientes | GET | `/rest/v1/patients` | Implementado em `patientRepository.getAll` |
-| Pacientes | POST | `/rest/v1/patients` | Implementado em `patientRepository.create` |
-| Pacientes | PATCH | `/rest/v1/patients?id=eq.{id}` | Implementado em `patientRepository.update` |
-| Pacientes | DELETE | `/rest/v1/patients?id=eq.{id}` | Implementado em `patientRepository.remove` |
-| Pacientes | POST | `/functions/v1/create-patient` | Implementado em `patientRepository.createWithValidation` |
-| Pacientes | POST | `/functions/v1/register-patient` | Nao implementado |
-| Medicos | GET | `/rest/v1/doctors` | `professionalRepository.getAll` usa mock |
-| Medicos | POST | `/functions/v1/create-doctor` | Nao implementado |
-| Agendamentos | GET | `/rest/v1/appointments` | `appointmentRepository.getAll` usa mock |
-| Agendamentos | POST | `/rest/v1/appointments` | Nao implementado |
-| Agendamentos | POST | `/functions/v1/get-available-slots` | Nao implementado |
-| Disponibilidade | GET | `/rest/v1/doctor_availability` | Nao implementado |
-| Disponibilidade | POST | `/rest/v1/doctor_availability` | Nao implementado |
-| Disponibilidade | PATCH | `/rest/v1/doctor_availability?id=eq.{id}` | Nao implementado |
-| Disponibilidade | DELETE | `/rest/v1/doctor_availability?id=eq.{id}` | Nao implementado |
-| Disponibilidade | GET | `/rest/v1/doctor_exceptions` | Nao implementado |
-| Disponibilidade | POST | `/rest/v1/doctor_exceptions` | Nao implementado |
-| Storage | POST | `/storage/v1/object/avatars/{path}` | Nao implementado |
-| Storage | GET | `/storage/v1/object/avatars/{path}` | Nao implementado |
-| Reports | GET | `/rest/v1/reports` | `reportRepository.getInitialReports` usa mock |
-| Reports | POST | `/rest/v1/reports` | Nao implementado |
-| Reports | PATCH | `/rest/v1/reports?id=eq.{id}` | Nao implementado |
+- **Autenticacao**
+ - Login com email e senha via Supabase Auth (`/auth/v1/token`).
+ - Solicitar reset de senha: tenta `/solicitar-reset-de-senha` e usa `/auth/v1/recover` como fallback.
+ - Dados do usuario autenticado: tenta `/informacoes-do-usuario-autenticado` e usa `/auth/v1/user` como fallback.
+ - Logout: tenta `/logout`, usa `/auth/v1/logout` como fallback e sempre limpa a sessao local.
-## Repositories ainda nao implementados
+- **Pacientes**
+ - Listar, criar, atualizar e deletar pacientes via Supabase REST.
+ - Criar paciente com validacao via Edge Function quando disponivel.
-| Repository | Metodos atuais | Endpoint equivalente | Observacao |
-| --- | --- | --- | --- |
-| `analyticsRepository` | `getDashboardData` | Nao documentado | Retorna dados estaticos de KPIs, graficos e pacientes frequentes. |
-| `appointmentRepository` | `getAll`, `getTodayTimeline`, `getPredictiveQueueSummary`, `getWeekDays` | Parcial: `GET /rest/v1/appointments`, `POST /rest/v1/appointments`, `POST /functions/v1/get-available-slots` | `getAll` deveria chamar a API; demais metodos sao derivados/visuais e nao aparecem na doc. |
-| `communicationRepository` | `getCampaigns`, `getInitialMessages`, `getInitialTemplates` | Parcial: `POST /functions/v1/send-sms` | A API so documenta envio de SMS; nao ha endpoints para campanhas, mensagens ou templates. |
-| `homeRepository` | `getDashboardOverview` | Nao documentado | Tela inicial usa agregados estaticos. |
-| `medicalRecordRepository` | `getRecordTypes`, `getInitialRecords` | Nao documentado | Nao ha endpoint de prontuarios/medical records na doc atual. |
-| `professionalRepository` | `getAll`, `getCoverageMap` | Parcial: `GET /rest/v1/doctors`, `POST /functions/v1/create-doctor` | `getAll` deveria usar doctors; `getCoverageMap` parece derivado de disponibilidade, mas nao bate direto com um endpoint. |
-| `profileRepository` | `getCurrentUserProfile` | Parcial: `GET /auth/v1/user`, `POST /functions/v1/user-info` | Retorna perfil fixo; deveria consumir dados do usuario autenticado. |
-| `reportRepository` | `getAdminUsers`, `getCurrentUser`, `getDoctors`, `getInitialReports`, `getReportTypes`, `getTemplates` | Parcial: `GET/POST/PATCH /rest/v1/reports` | Lista e metadados sao mockados; nao existem metodos de criar/atualizar usando API. |
-| `settingsRepository` | `getIntegrations`, `getSections` | Nao documentado | Configuracoes exibidas sao estaticas. |
-| `visitRepository` | `getCareQueue`, `getStages` | Nao documentado | Nao ha endpoint de atendimentos/fila/visits na doc atual. |
+- **Agendamentos**
+ - Listar agendamentos: tenta `GET /agendamentos` e usa Supabase REST `appointments` como fallback.
+ - Criar agendamento: tenta `POST /agendamentos` e usa Supabase REST `appointments` como fallback.
-## Inconsistencias encontradas
+- **Laudos Medicos**
+ - Listar relatorios: tenta `GET /reports` e usa Supabase REST `reports` como fallback.
+ - Criar relatorio: tenta `POST /reports` e usa Supabase REST `reports` como fallback.
+ - Atualizar relatorio: tenta `PATCH /reports/{id}`, depois `PATCH /reports`, e usa Supabase REST `reports` como fallback.
-- `patientRepository.getById(patientId)` nao bate com um endpoint documentado especifico. Ele chama `getAll()` e filtra em memoria; se a API aceitar filtro Supabase por id, o ideal seria usar `/rest/v1/patients?id=eq.{id}&select=*`, mas isso nao aparece como endpoint proprio na documentacao.
-- `patientRepository.getDirectoryRows()` transforma pacientes em campos de UI e preenche `insurance`, `city`, `state`, `vip`, `lastVisit` e `nextVisit` com valores fixos. Esses campos nao estao descritos na resposta de `GET /rest/v1/patients`.
-- `patientRepository.create(data)` e `createWithValidation(data)` enviam `created_by` com UUID zerado quando nao informado. A documentacao nao confirma esse fallback; isso pode gerar registro invalido se a API exigir usuario real.
-- `patientRepository.createWithValidation(data)` usa a Edge Function documentada (`/functions/v1/create-patient`), mas a API tambem possui o endpoint publico `/functions/v1/register-patient`, ainda sem metodo correspondente.
-- `appointmentRepository.getAll()` nao chama `GET /rest/v1/appointments`; usa `mockData`. Alem disso, nao existem metodos para `POST /rest/v1/appointments` nem para `POST /functions/v1/get-available-slots`.
-- `professionalRepository.getAll()` nao chama `GET /rest/v1/doctors`; usa `mockData`. Tambem falta metodo para `POST /functions/v1/create-doctor`.
-- `reportRepository.getInitialReports()` nao chama `GET /rest/v1/reports`; usa dados estaticos e nomes/status em portugues (`rascunho`, `finalizado`, `enviado`) diferentes dos status documentados (`draft`, `completed`).
-- `reportRepository` expoe templates, tipos, usuarios admin e medico atual, mas esses recursos nao aparecem na API documentada de Reports.
-- `communicationRepository` tem campanhas, mensagens e templates, mas a documentacao so possui `POST /functions/v1/send-sms`; nao ha equivalencia para listar ou gerenciar esses dados.
-- `profileRepository.getCurrentUserProfile()` retorna perfil fixo; deveria ser alinhado com `GET /auth/v1/user` ou `POST /functions/v1/user-info`.
-- `homeRepository`, `analyticsRepository`, `settingsRepository`, `visitRepository` e `medicalRecordRepository` nao possuem endpoints equivalentes na documentacao atual.
+- **Medicos / Profissionais**
+ - Listar medicos: tenta `GET /listar-medicos` e usa Supabase REST `doctors` como fallback.
-## Configuracao extraida
+- **Mensageria**
+ - Enviar SMS: tenta `POST /enviar-sms-via-twilio` e usa Edge Function `send-sms` como fallback.
+ - O formulario agora coleta telefone quando o canal selecionado e SMS.
-As variaveis reutilizaveis de API foram centralizadas em `src/config/api.js`:
+- **Storage**
+ - Upload de avatar: tenta `/upload-avatar` e usa Supabase Storage no bucket `avatars` como fallback.
+ - A tela de perfil atualiza a imagem exibida apos upload bem-sucedido.
-- `VITE_SUPABASE_URL`
-- `VITE_SUPABASE_REST_URL`
-- `VITE_SUPABASE_FUNCTIONS_URL`
-- `VITE_SUPABASE_STORAGE_URL`
-- `VITE_SUPABASE_ANON_KEY`
+## Ainda sem endpoint consolidado documentado
-O arquivo mantem os valores atuais como fallback para nao quebrar o ambiente local, mas o ideal e configurar esses valores via `.env`.
+- Dashboard / Inicio (`HomePage` / `homeRepository.js`).
+- Estatisticas e BI (`AnalyticsPage` / `analyticsRepository.js`).
+- Prontuarios especificos separados de laudos (`MedicalRecordsPage` / `medicalRecordRepository.js`).
+- Consultas isoladas fora de agendamento (`VisitsPage` / `visitRepository.js`).
+- Configuracoes gerais do tenant (`SettingsPage` / `settingsRepository.js`).
+
+## Observacoes
+
+- `VITE_API_BASE_URL` define a base dos endpoints nomeados da API. Quando nao informado, o front usa `VITE_SUPABASE_FUNCTIONS_URL`.
+- Os reposititorios aceitam formatos de resposta comuns como arrays diretos ou objetos com chaves `data`, `reports`, `agendamentos`, `medicos` etc.
+- Os fallbacks existem para manter o front funcional em ambientes onde parte das Edge Functions ainda nao foi publicada.
diff --git a/openapi.json b/openapi.json
new file mode 100644
index 0000000..2e5402d
--- /dev/null
+++ b/openapi.json
@@ -0,0 +1,4 @@
+{
+ "message": "Invalid API key",
+ "hint": "Only the `service_role` API key can be used for this endpoint."
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 164fbd4..0b60fb0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "projeto-residencia",
"version": "0.0.0",
"dependencies": {
+ "date-fns": "^4.1.0",
"react": "^19.2.4",
"react-dom": "^19.2.4"
},
@@ -1495,6 +1496,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
diff --git a/package.json b/package.json
index 8e61c32..2d18423 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "date-fns": "^4.1.0",
"react": "^19.2.4",
"react-dom": "^19.2.4"
},
diff --git a/src/App.jsx b/src/App.jsx
index 790bdc6..348b3d1 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,5 +1,7 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
+import { authRepository } from './repositories/authRepository.js'
+
import './App.css'
import { AppShell } from './components/AppShell.jsx'
import { AgendaPage } from './pages/AgendaPage.jsx'
@@ -48,11 +50,16 @@ function App() {
}, [])
const route = useMemo(() => resolveRoute(location.pathname, navigate), [location.pathname, navigate])
+ const isAuthenticated = authRepository.isAuthenticated()
if (!route.withShell) {
return route.element
}
+ if (!isAuthenticated) {
+ return
+ }
+
return (
{route.element}
@@ -119,15 +126,10 @@ function resolveRoute(pathname, navigate) {
if (pathname.startsWith('/pacientes/')) {
const patientId = pathname.split('/')[2]
- const patient = patientRepository.getById(patientId)
return {
- element: patient ? (
-
- ) : (
-
- ),
- title: patient?.name || 'Paciente nao encontrado',
+ element: ,
+ title: 'Paciente',
withShell: true,
}
}
@@ -143,7 +145,7 @@ function resolveRoute(pathname, navigate) {
if (pathname === '/laudos') {
return {
element: ,
- title: 'Laudos',
+ title: 'Relatorios medicos',
withShell: true,
}
}
@@ -195,6 +197,33 @@ function resolveRoute(pathname, navigate) {
}
}
+function PatientDetailRoute({ navigate, patientId }) {
+ const [patient, setPatient] = useState(null)
+ const [loading, setLoading] = useState(true)
+
+ useEffect(() => {
+ let active = true
+
+ patientRepository.getById(patientId)
+ .then((data) => {
+ if (active) setPatient(data)
+ })
+ .finally(() => {
+ if (active) setLoading(false)
+ })
+
+ return () => {
+ active = false
+ }
+ }, [patientId])
+
+ if (loading) {
+ return
- Organize consultas, retornos e teleatendimentos do dia.
+ {isDoctorScope
+ ? `Agenda restrita ao médico logado: ${currentProfessional?.name || viewerProfile?.name || 'Médico atual'}.`
+ : 'Visualização completa da agenda com todos os médicos.'}