modified: src/App.jsx
modified: src/components/AppShell.jsx modified: src/config/api.js modified: src/config/permissions.js modified: src/data/mockData.js modified: src/hooks/useAgenda.js modified: src/hooks/useAuth.js modified: src/mappers/appointmentMapper.js modified: src/pages/AgendaPage.jsx modified: src/pages/AuthPages.jsx modified: src/pages/HomePage.jsx modified: src/pages/MedicalRecordsPage.jsx modified: src/pages/MessagesPage.jsx modified: src/pages/NotFoundPage.jsx modified: src/pages/PatientsPage.jsx modified: src/pages/ReportsPage.jsx modified: src/pages/TeamPage.jsx modified: src/pages/UsersPage.jsx modified: src/pages/VisitsPage.jsx modified: src/repositories/authRepository.js new file: src/repositories/availabilityRepository.js modified: src/repositories/communicationRepository.js modified: src/repositories/patientRepository.js modified: src/repositories/professionalRepository.js modified: src/repositories/profileRepository.js modified: src/repositories/reportRepository.js modified: src/repositories/repositoryUtils.js modified: src/repositories/settingsRepository.js modified: src/repositories/userRepository.js modified: src/repositories/visitRepository.js
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { hasCapability } from '../config/permissions.js'
|
||||
import { patientRepository } from '../repositories/patientRepository.js'
|
||||
const ITEMS_PER_PAGE = 25
|
||||
|
||||
@@ -14,7 +15,7 @@ const patientTabs = [
|
||||
{ label: 'Documentos', value: 'documentos' },
|
||||
]
|
||||
|
||||
export function PatientsPage({ navigate }) {
|
||||
export function PatientsPage({ navigate, role }) {
|
||||
const [rows, setRows] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
@@ -45,6 +46,8 @@ export function PatientsPage({ navigate }) {
|
||||
const insuranceOptions = useMemo(() => [...new Set(rows.map((patient) => patient.insurance).filter(Boolean))], [rows])
|
||||
const stateOptions = useMemo(() => [...new Set(rows.map((patient) => patient.state).filter(Boolean))], [rows])
|
||||
const hasAdvancedFilters = city || state || ageMin || ageMax || lastVisitSince
|
||||
const canEditPatients = hasCapability(role, 'canEditPatients')
|
||||
const canHardDeletePatients = hasCapability(role, 'hardDeletePatients')
|
||||
|
||||
const filteredPatients = useMemo(() => {
|
||||
return rows.filter((patient) => {
|
||||
@@ -65,7 +68,7 @@ export function PatientsPage({ navigate }) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (vip === 'Nao' && patient.vip) {
|
||||
if (vip === 'Não' && patient.vip) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -117,12 +120,18 @@ export function PatientsPage({ navigate }) {
|
||||
}
|
||||
|
||||
function openForm(patientId = null) {
|
||||
if (!canEditPatients) return
|
||||
setEditingId(patientId)
|
||||
setOpenMenuId(null)
|
||||
setView('form')
|
||||
}
|
||||
|
||||
async function savePatient(patient) {
|
||||
if (!canEditPatients) {
|
||||
window.alert('Você não tem permissão para salvar pacientes.')
|
||||
return
|
||||
}
|
||||
|
||||
const isNew = !rows.some((item) => item.id === patient.id)
|
||||
setSaving(true)
|
||||
|
||||
@@ -156,6 +165,11 @@ export function PatientsPage({ navigate }) {
|
||||
}
|
||||
|
||||
async function deletePatient(patientId) {
|
||||
if (!canHardDeletePatients) {
|
||||
window.alert('Você não tem permissão para excluir pacientes.')
|
||||
return
|
||||
}
|
||||
|
||||
if (window.confirm('Tem certeza que deseja excluir este paciente?')) {
|
||||
try {
|
||||
await patientRepository.remove(patientId)
|
||||
@@ -206,16 +220,18 @@ async function deletePatient(patientId) {
|
||||
<div className="flex flex-col items-start justify-between gap-4 md:flex-row md:items-center">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight text-[#e5e5e5]">Pacientes</h1>
|
||||
<p className="mt-1 text-sm text-[#a3a3a3]">Gerencie as informacoes de seus pacientes</p>
|
||||
<p className="mt-1 text-sm text-[#a3a3a3]">Gerencie as informações de seus pacientes</p>
|
||||
</div>
|
||||
<button
|
||||
className="inline-flex h-10 w-full items-center justify-center gap-2 rounded-lg bg-[#3b82f6] px-4 text-sm font-medium text-white shadow-sm transition hover:bg-[#2563eb] md:w-auto"
|
||||
onClick={() => openForm()}
|
||||
type="button"
|
||||
>
|
||||
<PatientIcon name="user-plus" />
|
||||
Adicionar
|
||||
</button>
|
||||
{canEditPatients ? (
|
||||
<button
|
||||
className="inline-flex h-10 w-full items-center justify-center gap-2 rounded-lg bg-[#3b82f6] px-4 text-sm font-medium text-white shadow-sm transition hover:bg-[#2563eb] md:w-auto"
|
||||
onClick={() => openForm()}
|
||||
type="button"
|
||||
>
|
||||
<PatientIcon name="user-plus" />
|
||||
Adicionar
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<section className="rounded-2xl border border-[#404040] bg-[#262626] px-6 py-8 shadow-sm xl:py-14">
|
||||
@@ -253,7 +269,7 @@ async function deletePatient(patientId) {
|
||||
setVip(value)
|
||||
setPage(1)
|
||||
}}
|
||||
options={['Sim', 'Nao']}
|
||||
options={['Sim', 'Não']}
|
||||
value={vip}
|
||||
/>
|
||||
|
||||
@@ -310,7 +326,7 @@ async function deletePatient(patientId) {
|
||||
<th className="w-[8%] px-6 py-4">Estado</th>
|
||||
<th className="w-[16%] px-6 py-4">Ultimo atendimento</th>
|
||||
<th className="w-[18%] px-6 py-4">Proximo atendimento</th>
|
||||
<th className="sticky right-0 w-[8.5rem] bg-[#171717] px-6 py-4 text-right">Acoes</th>
|
||||
<th className="sticky right-0 w-[8.5rem] bg-[#171717] px-6 py-4 text-right">Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-[#404040] bg-[#262626]">
|
||||
@@ -335,11 +351,11 @@ async function deletePatient(patientId) {
|
||||
<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.lastVisit || 'Ainda nao houve atendimento'}</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)]">
|
||||
<button
|
||||
aria-label={`Acoes de ${patient.name}`}
|
||||
aria-label={`Ações de ${patient.name}`}
|
||||
className="rounded p-1 text-[#a3a3a3] transition hover:bg-[#333333] hover:text-[#e5e5e5]"
|
||||
onClick={() => setOpenMenuId(openMenuId === patient.id ? null : patient.id)}
|
||||
type="button"
|
||||
@@ -356,7 +372,7 @@ async function deletePatient(patientId) {
|
||||
/>
|
||||
<div className="absolute right-8 top-10 z-20 w-48 rounded-lg border border-[#404040] bg-[#303030] py-1 text-left shadow-lg">
|
||||
<ActionItem icon="file" label="Ver detalhes" onClick={() => openDetail(patient)} />
|
||||
<ActionItem icon="edit" label="Editar" onClick={() => openForm(patient.id)} />
|
||||
{canEditPatients ? <ActionItem icon="edit" label="Editar" onClick={() => openForm(patient.id)} /> : null}
|
||||
<ActionItem
|
||||
icon="calendar"
|
||||
label="Marcar consulta"
|
||||
@@ -365,7 +381,9 @@ async function deletePatient(patientId) {
|
||||
navigate('/agenda')
|
||||
}}
|
||||
/>
|
||||
<ActionItem danger icon="trash" label="Excluir" onClick={() => deletePatient(patient.id)} />
|
||||
{canHardDeletePatients ? (
|
||||
<ActionItem danger icon="trash" label="Excluir" onClick={() => deletePatient(patient.id)} />
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
@@ -488,12 +506,12 @@ function PatientEditor({ existingIds, onCancel, onSave, patient, saving }) {
|
||||
id: formData.id || uniqueSlug(formData.name, existingIds),
|
||||
age: Number(formData.age) || 0,
|
||||
birthday: formData.birthday || '07/04',
|
||||
city: formData.city || 'Cidade nao informada',
|
||||
document: formData.cpf ? `CPF ${formData.cpf}` : 'CPF nao informado',
|
||||
city: formData.city || 'Cidade não informada',
|
||||
document: formData.cpf ? `CPF ${formData.cpf}` : 'CPF não informado',
|
||||
insurance: formData.insurance || 'Particular',
|
||||
lastVisit: formData.lastVisit || 'Ainda nao houve atendimento',
|
||||
lastVisit: formData.lastVisit || 'Ainda não houve atendimento',
|
||||
nextVisit: formData.nextVisit || null,
|
||||
phone: formData.phone || 'Telefone nao informado',
|
||||
phone: formData.phone || 'Telefone não informado',
|
||||
plan: formData.insurance || formData.plan || 'Particular',
|
||||
state: formData.state || 'UF',
|
||||
})
|
||||
@@ -512,7 +530,7 @@ function PatientEditor({ existingIds, onCancel, onSave, patient, saving }) {
|
||||
</button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight text-[#e5e5e5]">Paciente</h1>
|
||||
<p className="mt-1 text-sm text-[#a3a3a3]">Gerencie as informacoes de seus pacientes</p>
|
||||
<p className="mt-1 text-sm text-[#a3a3a3]">Gerencie as informações de seus pacientes</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -551,8 +569,8 @@ function PatientEditor({ existingIds, onCancel, onSave, patient, saving }) {
|
||||
<DarkField className="md:col-span-3" label="Etnia">
|
||||
<select className={darkInput} defaultValue="">
|
||||
<option value="">Selecione</option>
|
||||
<option>Indigena</option>
|
||||
<option>Nao Indigena</option>
|
||||
<option>Indígena</option>
|
||||
<option>Não Indígena</option>
|
||||
</select>
|
||||
</DarkField>
|
||||
<DarkField className="md:col-span-3" label="Estado civil">
|
||||
@@ -605,12 +623,12 @@ function PatientEditor({ existingIds, onCancel, onSave, patient, saving }) {
|
||||
</section>
|
||||
|
||||
<section className={darkCard}>
|
||||
<h2 className="mb-6 text-lg font-semibold text-[#e5e5e5]">Endereco</h2>
|
||||
<h2 className="mb-6 text-lg font-semibold text-[#e5e5e5]">Endereço</h2>
|
||||
<div className="grid grid-cols-1 gap-x-6 gap-y-6 md:grid-cols-12">
|
||||
<DarkField className="md:col-span-3" label="CEP">
|
||||
<input className={darkInput} maxLength={9} onChange={maskCEPInput} placeholder="_____-___" />
|
||||
</DarkField>
|
||||
<DarkField className="md:col-span-5" label="Endereco">
|
||||
<DarkField className="md:col-span-5" label="Endereço">
|
||||
<input className={darkInput} />
|
||||
</DarkField>
|
||||
<DarkField className="md:col-span-4" label="Cidade">
|
||||
@@ -621,7 +639,7 @@ function PatientEditor({ existingIds, onCancel, onSave, patient, saving }) {
|
||||
<option value="">Selecione</option>
|
||||
<option value="PE">Pernambuco</option>
|
||||
<option value="SE">Sergipe</option>
|
||||
<option value="SP">Sao Paulo</option>
|
||||
<option value="SP">São Paulo</option>
|
||||
<option value="RJ">Rio de Janeiro</option>
|
||||
</select>
|
||||
</DarkField>
|
||||
@@ -629,7 +647,7 @@ function PatientEditor({ existingIds, onCancel, onSave, patient, saving }) {
|
||||
</section>
|
||||
|
||||
<section className={darkCard}>
|
||||
<h2 className="mb-6 text-lg font-semibold text-[#e5e5e5]">Informacoes de convenio</h2>
|
||||
<h2 className="mb-6 text-lg font-semibold text-[#e5e5e5]">Informações de convenio</h2>
|
||||
<div className="grid grid-cols-1 gap-x-6 gap-y-6 md:grid-cols-12">
|
||||
<DarkField className="md:col-span-6" label="Convenio">
|
||||
<select className={darkInput} name="insurance" onChange={handleChange} value={formData.insurance}>
|
||||
|
||||
Reference in New Issue
Block a user