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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user