modo-claro

modified:   src/hooks/useAgenda.js
modified:   src/index.css
modified:   src/main.jsx
modified:   src/mappers/reportMapper.js
modified:   src/pages/AgendaPage.jsx
modified:   src/pages/AuthPages.jsx
modified:   src/pages/MedicalRecordsPage.jsx
modified:   src/pages/PatientsPage.jsx
modified:   src/pages/ReportsPage.jsx
modified:   src/pages/SettingsPage.jsx
modified:   src/repositories/analyticsRepository.js
modified:   src/repositories/authRepository.js
modified:   src/repositories/patientRepository.js
modified:   src/repositories/reportRepository.js
modified:   src/repositories/repositoryUtils.js
new file:   src/utils/theme.js
new file:   vercel.json
This commit is contained in:
2026-05-07 05:51:07 -03:00
parent 64d9527318
commit db7a2fe8f5
17 changed files with 669 additions and 121 deletions

View File

@@ -1,9 +1,7 @@
import { useEffect, useMemo, useState } from 'react'
import { hasCapability, normalizeRole } from '../config/permissions.js'
import { hasCapability } from '../config/permissions.js'
import { patientRepository } from '../repositories/patientRepository.js'
import { professionalRepository } from '../repositories/professionalRepository.js'
import { profileRepository } from '../repositories/profileRepository.js'
const ITEMS_PER_PAGE = 25
const darkInput =
@@ -70,11 +68,11 @@ export function PatientsPage({ navigate, role }) {
const [page, setPage] = useState(1)
useEffect(() => {
buildPatientRows(role)
buildPatientRows()
.then((data) => setRows(data))
.catch((err) => setError(err.message))
.finally(() => setLoading(false))
}, [role])
}, [])
const editingPatient = rows.find((patient) => patient.id === editingId)
const hasAdvancedFilters = city || state || ageMin || ageMax || lastVisitSince
@@ -180,13 +178,13 @@ export function PatientsPage({ navigate, role }) {
try {
if (isNew) {
const [created] = await patientRepository.create(patient)
const created = normalizeCreatedPatient(await patientRepository.create(patient))
const newRow = {
...patient,
id: created.id,
detailId: created.id,
name: created.full_name || patient.name,
phone: created.phone_mobile || patient.phone,
id: created?.id || patient.id,
detailId: created?.id || patient.detailId || patient.id,
name: created?.full_name || created?.name || patient.name,
phone: created?.phone_mobile || created?.phone || patient.phone,
}
setRows((currentRows) => [newRow, ...currentRows])
} else {
@@ -368,14 +366,14 @@ export function PatientsPage({ navigate, role }) {
{patient.name}
</span>
<span className="mt-0.5 block whitespace-normal break-words text-xs text-[#a3a3a3]">
{patient.insurance || 'Sem convenio'} {patient.vip ? ' | VIP' : ''}
{patient.insurance || missingValue('Convênio')} {patient.vip ? ' | VIP' : ''}
</span>
</span>
</button>
</td>
<td className="px-6 py-4 align-top whitespace-normal break-words text-[#a3a3a3]">{patient.phone}</td>
<td className="px-6 py-4 align-top whitespace-normal break-words text-[#a3a3a3]">{patient.city}</td>
<td className="px-6 py-4 align-top text-[#a3a3a3]">{patient.state}</td>
<td className="px-6 py-4 align-top whitespace-normal break-words text-[#a3a3a3]">{patient.phone || missingValue('Telefone')}</td>
<td className="px-6 py-4 align-top whitespace-normal break-words text-[#a3a3a3]">{patient.city || missingValue('Cidade')}</td>
<td className="px-6 py-4 align-top text-[#a3a3a3]">{patient.state || missingValue('Estado')}</td>
<td className="px-6 py-4 align-top whitespace-normal break-words text-[#a3a3a3]">{patient.lastVisit || 'Ainda não houve atendimento'}</td>
<td className="px-6 py-4 align-top whitespace-normal break-words text-[#a3a3a3]">{patient.nextVisit || 'Nenhum atendimento agendado'}</td>
<td className="relative sticky right-0 bg-[#262626] px-6 py-4 text-right shadow-[-10px_0_12px_-12px_rgba(0,0,0,0.75)]">
@@ -398,7 +396,7 @@ export function PatientsPage({ navigate, role }) {
onClick={() => setOpenMenuId(null)}
type="button"
/>
<div className="absolute right-4 top-12 z-50 w-48 rounded-lg border border-[#404040] bg-[#303030] py-1 text-left shadow-lg">
<div className="absolute right-4 top-12 z-50 w-48 rounded-md border border-[#404040] bg-[#262626] p-1 text-left shadow-lg">
<ActionItem icon="file" label="Ver detalhes" onClick={() => openDetail(patient)} />
{canEditPatients ? <ActionItem icon="edit" label="Editar" onClick={() => openForm(patient.id)} /> : null}
<ActionItem
@@ -870,8 +868,8 @@ export function PatientDetailPage({ navigate, patient, role }) {
</header>
<section className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<SummaryTile label="Idade" value={`${localPatient.age} anos`} />
<SummaryTile label="Risco" value={localPatient.risk} tone={riskColor(localPatient.risk)} />
<SummaryTile label="Idade" value={localPatient.age ? `${localPatient.age} anos` : missingValue('Idade')} />
<SummaryTile label="Risco" value={localPatient.risk || missingValue('Risco')} tone={localPatient.risk ? riskColor(localPatient.risk) : null} />
<SummaryTile label="Última consulta" value={localPatient.lastVisit || 'Ainda não houve atendimento'} />
<SummaryTile label="Próxima consulta" value={localPatient.nextVisit || 'Nenhum atendimento agendado'} />
</section>
@@ -1197,11 +1195,15 @@ function InfoRow({ label, value }) {
return (
<div>
<dt className="font-semibold text-[#737373]">{label}</dt>
<dd className="mt-1 text-[#e5e5e5]">{value || 'Não informado'}</dd>
<dd className="mt-1 text-[#e5e5e5]">{value || missingValue(label)}</dd>
</div>
)
}
function missingValue(label) {
return `${label} não informado`
}
function formatDisplayDate(value) {
if (!value) return ''
const [year, month, day] = String(value).split('-')
@@ -1296,8 +1298,8 @@ function PageButton({ children, disabled, onClick }) {
function ActionItem({ danger = false, icon, label, onClick }) {
return (
<button
className={`flex w-full items-center gap-2 px-4 py-2 text-sm transition ${
danger ? 'text-[#ef4444] hover:bg-[#ef4444]/10' : 'text-[#e5e5e5] hover:bg-[#333333]'
className={`flex w-full items-center gap-2 rounded-sm px-3 py-2 text-left text-sm font-medium transition ${
danger ? 'text-[#f87171] hover:bg-[#303030]' : 'text-[#e5e5e5] hover:bg-[#303030]'
}`}
onClick={onClick}
type="button"
@@ -1620,22 +1622,13 @@ function PatientIcon({ className = 'size-4', name }) {
)
}
async function buildPatientRows(role) {
if (normalizeRole(role) !== 'medico') {
return patientRepository.getDirectoryRows()
}
async function buildPatientRows() {
return patientRepository.getDirectoryRows()
}
const [profile, professionals] = await Promise.all([
profileRepository.getCurrentUserProfile(),
professionalRepository.getAll(),
])
const currentProfessional = professionalRepository.resolveCurrentProfessional(profile, professionals)
if (!currentProfessional?.id) {
throw new Error('Não foi possível vincular o médico logado a um profissional da base.')
}
return patientRepository.getDirectoryRows({ doctorId: currentProfessional.id })
function normalizeCreatedPatient(payload) {
if (Array.isArray(payload)) return payload[0] || null
return payload?.patient || payload?.data || payload?.created || payload || null
}
function uniqueSlug(value, existingIds) {