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,7 +1,8 @@
import { useMemo, useState } from 'react'
import { useEffect, useMemo, useState } from 'react'
import { FeatureCallout } from '../components/FeatureState.jsx'
import { medicalRecordRepository } from '../repositories/medicalRecordRepository.js'
import { patientRepository } from '../repositories/patientRepository.js'
const inputClass =
@@ -12,9 +13,27 @@ const cardClass = 'rounded-2xl border border-[#404040] bg-[#262626] shadow-sm'
export function MedicalRecordsPage() {
const recordTypes = medicalRecordRepository.getRecordTypes()
const [records, setRecords] = useState(() => medicalRecordRepository.getInitialRecords())
const [patients, setPatients] = useState([])
const [search, setSearch] = useState('')
const [editorOpen, setEditorOpen] = useState(false)
useEffect(() => {
let active = true
patientRepository
.getDirectoryRows()
.then((data) => {
if (active) setPatients(data || [])
})
.catch(() => {
if (active) setPatients([])
})
return () => {
active = false
}
}, [])
const filteredRecords = useMemo(() => {
return records.filter((record) => {
const matchesSearch = [record.patient, record.cid, record.doctor]
@@ -81,6 +100,7 @@ export function MedicalRecordsPage() {
<RecordEditorModal
onClose={() => setEditorOpen(false)}
onSave={handleCreateRecord}
patients={patients}
recordTypes={recordTypes}
/>
) : null}
@@ -149,8 +169,10 @@ function IconButton({ label, name }) {
)
}
function RecordEditorModal({ onClose, onSave, recordTypes }) {
function RecordEditorModal({ onClose, onSave, patients, recordTypes }) {
const [patientSearch, setPatientSearch] = useState('')
const [formData, setFormData] = useState({
patientId: '',
patient: '',
date: '',
type: 'Primeira Consulta',
@@ -168,6 +190,31 @@ function RecordEditorModal({ onClose, onSave, recordTypes }) {
setFormData((currentData) => ({ ...currentData, [name]: value }))
}
const filteredPatients = (() => {
const query = normalizeSearch(patientSearch)
if (!query) return patients
return patients.filter((patient) =>
[patient.name, patient.full_name, patient.nome, patient.cpf, patient.document, patient.email]
.filter(Boolean)
.join(' ')
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.includes(query),
)
})()
function selectPatient(patient) {
const name = getPatientName(patient)
setFormData((currentData) => ({
...currentData,
patientId: patient.id,
patient: name,
}))
setPatientSearch(name)
}
function handleSubmit(event) {
event.preventDefault()
const submitter = event.nativeEvent.submitter
@@ -199,11 +246,36 @@ function RecordEditorModal({ onClose, onSave, recordTypes }) {
<DarkField label="Paciente">
<input
className={inputClass}
name="patient"
onChange={updateField}
onChange={(event) => {
setPatientSearch(event.target.value)
setFormData((currentData) => ({ ...currentData, patientId: '', patient: '' }))
}}
placeholder="Buscar paciente..."
value={formData.patient}
type="search"
value={patientSearch || formData.patient}
/>
<div className="mt-2 max-h-44 overflow-y-auto rounded-lg border border-[#404040] bg-[#1a1a1a]">
{filteredPatients.length ? (
filteredPatients.slice(0, 6).map((patient) => {
const selected = String(patient.id) === String(formData.patientId)
return (
<button
className={`block w-full px-3 py-2 text-left text-sm transition ${
selected ? 'bg-[#3b82f6]/20 text-[#e5e5e5]' : 'text-[#a3a3a3] hover:bg-[#2a2a2a] hover:text-[#e5e5e5]'
}`}
key={patient.id}
onClick={() => selectPatient(patient)}
type="button"
>
<span className="block font-semibold">{getPatientName(patient)}</span>
<span className="mt-0.5 block text-xs text-[#737373]">{patient.cpf || patient.document || patient.email || 'Sem documento'}</span>
</button>
)
})
) : (
<p className="px-3 py-2 text-xs text-[#737373]">Nenhum paciente encontrado.</p>
)}
</div>
</DarkField>
<DarkField label="Data da Consulta">
<input
@@ -330,6 +402,18 @@ function formatDate(value) {
return `${day}/${month}/${year}`
}
function getPatientName(patient) {
return patient?.name || patient?.full_name || patient?.nome || ''
}
function normalizeSearch(value) {
return String(value || '')
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.trim()
.toLowerCase()
}
function RecordIcon({ className = 'size-4', name }) {
const common = {
className,