{appointment.time || '--:--'}
++ {appointment.mode} +
++ {appointment.type} com {appointment.professional} +
+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/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/components/AppShell.jsx b/src/components/AppShell.jsx index ba2f89d..8726804 100644 --- a/src/components/AppShell.jsx +++ b/src/components/AppShell.jsx @@ -1,5 +1,6 @@ -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' +import { profileRepository } from '../repositories/profileRepository.js' import { BrandLogo } from './Brand.jsx' import { FeatureLegend } from './FeatureState.jsx' @@ -7,16 +8,16 @@ const navItems = [ { href: '/inicio', label: 'Painel', icon: 'pulse', activePaths: ['/inicio', '/home', '/dashboard'] }, { href: '/agenda', label: 'Agenda', icon: 'calendar' }, { href: '/pacientes', label: 'Pacientes', icon: 'users', exact: true }, - { href: '/prontuario', label: 'Prontuário', icon: 'file' }, + { href: '/prontuario', label: 'Prontuario', icon: 'file' }, { href: '/laudos', label: 'Laudos', icon: 'clipboard' }, { href: '/camunicacao', - label: 'Comunicação', + label: 'Comunicacao', icon: 'message', activePaths: ['/camunicacao', '/comunicacao', '/mensagens'], }, - { href: '/relatorios', label: 'Relatórios', icon: 'chart' }, - { href: '/configuracoes', label: 'Configurações', icon: 'settings', activePaths: ['/configuracoes', '/config'] }, + { href: '/relatorios', label: 'Relatorios', icon: 'chart' }, + { href: '/configuracoes', label: 'Configuracoes', icon: 'settings', activePaths: ['/configuracoes', '/config'] }, ] const titles = { @@ -27,20 +28,21 @@ const titles = { '/consultas': 'Consultas', '/laudos': 'Laudos', '/pacientes': 'Pacientes', - '/prontuario': 'Prontuário', - '/camunicacao': 'Comunicação', - '/comunicacao': 'Comunicação', - '/mensagens': 'Comunicação', - '/relatorios': 'Relatórios', + '/prontuario': 'Prontuario', + '/camunicacao': 'Comunicacao', + '/comunicacao': 'Comunicacao', + '/mensagens': 'Comunicacao', + '/relatorios': 'Relatorios', '/profissionais': 'Profissionais', '/perfil': 'Perfil', - '/configuracoes': 'Configurações', - '/config': 'Configurações', + '/configuracoes': 'Configuracoes', + '/config': 'Configuracoes', } export function AppShell({ children, currentPath, navigate, routeTitle }) { const [menuOpen, setMenuOpen] = useState(false) const [quickSearch, setQuickSearch] = useState('') + const [viewerProfile, setViewerProfile] = useState({ name: 'Usuario', role: 'Usuario do Sistema' }) const pageTitle = useMemo(() => { if (currentPath.startsWith('/pacientes/') && routeTitle) { @@ -50,6 +52,25 @@ export function AppShell({ children, currentPath, navigate, routeTitle }) { return routeTitle || titles[currentPath] || 'MediConnect' }, [currentPath, routeTitle]) + useEffect(() => { + let active = true + + profileRepository.getCurrentUserProfile() + .then((profile) => { + if (!active || !profile) return + + setViewerProfile({ + name: profile.name || 'Usuario', + role: profile.role || 'Usuario do Sistema', + }) + }) + .catch(() => {}) + + return () => { + active = false + } + }, []) + function goTo(path) { setMenuOpen(false) navigate(path) @@ -96,8 +117,8 @@ export function AppShell({ children, currentPath, navigate, routeTitle }) { onClick={() => goTo('/perfil')} type="button" > -
Dr. Henrique Cardoso
-Médico Clínico Geral
+{viewerProfile.name}
+{viewerProfile.role}
@@ -129,7 +150,7 @@ export function AppShell({ children, currentPath, navigate, routeTitle }) { aria-label="Busca rapida" className="h-[38px] w-full rounded-sm border border-[#404040] bg-[#303030] py-2 pl-10 pr-4 text-sm text-[#e5e5e5] outline-none transition placeholder:text-[#a3a3a3] focus:border-[#3b82f6] focus:ring-2 focus:ring-[#3b82f6]/20" onChange={(event) => setQuickSearch(event.target.value)} - placeholder="Buscar paciente, prontuário..." + placeholder="Buscar paciente, prontuario..." value={quickSearch} /> @@ -155,14 +176,14 @@ export function AppShell({ children, currentPath, navigate, routeTitle }) { type="button" > - HC + {getInitials(viewerProfile.name)} - Dr. Henrique Cardoso + {viewerProfile.name} - Médico(a) + {viewerProfile.role}+ Ajuste o filtro ou altere o período no calendário. +
+{appointment.time || '--:--'}
++ {appointment.mode} +
++ {appointment.type} com {appointment.professional} +
+Carregando agenda...
+- 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.'}
- Visualização: {activeView.toLowerCase()} | {visibleAppointments.length} registros no filtro -
-{error}
++ Enquanto esse vinculo nao existir na API, a tela fica bloqueada para evitar exibir consultas de outro medico. +
- Ajuste o filtro ou crie uma consulta mockada para este período.
+
+ ) : (
+
+ Visualização: {activeView.toLowerCase()} | {visibleAppointments.length} registros visíveis
+ {format(baseDate, "EEEE, dd 'de' MMMM", { locale: ptBR })}
+
+ Linha do tempo
- Resumo preditivo
-