modified: index.html

modified:   src/App.jsx
modified:   src/components/AppShell.jsx
modified:   src/components/featureStateStyles.js
modified:   src/config/permissions.js
modified:   src/hooks/useAgenda.js
modified:   src/mappers/reportMapper.js
modified:   src/pages/AgendaPage.jsx
modified:   src/pages/AnalyticsPage.jsx
modified:   src/pages/AuthPages.jsx
modified:   src/pages/HomePage.jsx
modified:   src/pages/MedicalRecordsPage.jsx
modified:   src/pages/MessagesPage.jsx
modified:   src/pages/PatientsPage.jsx
modified:   src/pages/ReportsPage.jsx
modified:   src/pages/SettingsPage.jsx
deleted:    src/pages/TeamPage.jsx
modified:   src/pages/UsersPage.jsx
modified:   src/repositories/availabilityRepository.js
modified:   src/repositories/patientRepository.js
modified:   src/repositories/professionalRepository.js
modified:   src/repositories/reportRepository.js
modified:   src/repositories/settingsRepository.js
This commit is contained in:
2026-05-07 01:11:10 -03:00
parent 9335e974eb
commit efb942d5aa
23 changed files with 1461 additions and 591 deletions

View File

@@ -10,11 +10,13 @@ import {
startOfWeek,
} from 'date-fns'
import { ptBR } from 'date-fns/locale'
import { useState } from 'react'
import { AgendaDailyView } from '../components/calendar/AgendaDailyView.jsx'
import { AgendaWeeklyView } from '../components/calendar/AgendaWeeklyView.jsx'
import { AgendaMonthlyView } from '../components/calendar/AgendaMonthlyView.jsx'
import { useAgenda } from '../hooks/useAgenda.js'
import { formatLocalDateInput, parseLocalDate } from '../utils/agendaDate.js'
const statusFilters = [
{ label: 'Todos', value: 'Todos' },
@@ -30,6 +32,8 @@ const viewFilters = [
]
export function AgendaPage({ navigate }) {
const [modalPatientSearch, setModalPatientSearch] = useState('')
const [modalDoctorSearch, setModalDoctorSearch] = useState('')
const {
patients,
professionals,
@@ -45,6 +49,11 @@ export function AgendaPage({ navigate }) {
setBaseDate,
status,
setStatus,
setDoctorFilter,
doctorSearch,
setDoctorSearch,
unitFilter,
setUnitFilter,
modalOpen,
setModalOpen,
form,
@@ -67,6 +76,37 @@ export function AgendaPage({ navigate }) {
const weekStart = startOfWeek(baseDate, { weekStartsOn: 0 })
const weekEnd = endOfWeek(baseDate, { weekStartsOn: 0 })
const isDoctorScope = agendaScope === 'doctor'
const unitOptions = [
...new Set(
professionals
.map((professional) => professional.unit)
.filter(Boolean),
),
].sort((a, b) => a.localeCompare(b, 'pt-BR'))
const filteredPatients = (() => {
const query = modalPatientSearch.trim().toLowerCase()
if (!query) return patients
return patients.filter((patient) =>
[patient.name, patient.full_name, patient.nome, patient.cpf, patient.email]
.filter(Boolean)
.join(' ')
.toLowerCase()
.includes(query),
)
})()
const filteredProfessionals = (() => {
const query = modalDoctorSearch.trim().toLowerCase()
if (!query) return professionals
return professionals.filter((professional) =>
[professional.name, professional.email, professional.unit]
.filter(Boolean)
.join(' ')
.toLowerCase()
.includes(query),
)
})()
return (
<div className="mx-auto flex max-w-[1180px] flex-col gap-8 text-[#e5e5e5]">
@@ -76,9 +116,7 @@ export function AgendaPage({ navigate }) {
Agenda
</h1>
<p className="mt-2 text-sm leading-5 text-[#a3a3a3]">
{isDoctorScope
? `Agenda restrita ao médico logado: ${currentProfessional?.name || viewerProfile?.name || 'Médico atual'}.`
: 'Visualização completa da agenda com todos os médicos.'}
Perfil atual: {viewerProfile?.role || (isDoctorScope ? 'Médico' : 'Usuário')}
</p>
</div>
@@ -126,7 +164,7 @@ export function AgendaPage({ navigate }) {
onClick={() => setModalOpen(true)}
type="button"
>
+ Nova consulta
+ Novo agendamento
</button>
</div>
</section>
@@ -174,26 +212,61 @@ export function AgendaPage({ navigate }) {
</div>
</div>
<div className="mt-5 flex flex-wrap gap-2">
{statusFilters.map((filter) => (
<button
className={`h-8 rounded-sm border px-3 text-sm font-semibold transition ${
status === filter.value
? 'border-[#3b82f6] bg-[#3b82f6]/10 text-[#3b82f6]'
: 'border-[#404040] bg-[#303030] text-[#a3a3a3] hover:text-[#e5e5e5]'
}`}
key={filter.value}
onClick={() => setStatus(filter.value)}
type="button"
>
{filter.label}
</button>
))}
<div className="mt-5 flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
<div className="flex flex-wrap gap-2">
{statusFilters.map((filter) => (
<button
className={`h-8 rounded-sm border px-3 text-sm font-semibold transition ${
status === filter.value
? 'border-[#3b82f6] bg-[#3b82f6]/10 text-[#3b82f6]'
: 'border-[#404040] bg-[#303030] text-[#a3a3a3] hover:text-[#e5e5e5]'
}`}
key={filter.value}
onClick={() => setStatus(filter.value)}
type="button"
>
{filter.label}
</button>
))}
</div>
{!isDoctorScope ? (
<div className="grid gap-3 sm:min-w-[32rem] sm:grid-cols-2">
<label className="grid gap-1.5 text-xs font-semibold text-[#a3a3a3]">
<span>Médico</span>
<input
className="h-9 rounded-sm border border-[#404040] bg-[#303030] px-3 text-sm font-medium text-[#e5e5e5] outline-none transition placeholder:text-[#737373] focus:border-[#3b82f6]"
onChange={(event) => {
setDoctorFilter('Todos')
setDoctorSearch(event.target.value)
}}
placeholder="Pesquisar médico pelo nome"
type="search"
value={doctorSearch}
/>
</label>
<label className="grid gap-1.5 text-xs font-semibold text-[#a3a3a3]">
<span>Unidade</span>
<select
className="h-9 rounded-sm border border-[#404040] bg-[#303030] px-3 text-sm font-medium text-[#e5e5e5] outline-none transition focus:border-[#3b82f6]"
onChange={(event) => setUnitFilter(event.target.value)}
value={unitFilter}
>
<option value="">Todas as unidades</option>
{unitOptions.map((unit) => (
<option key={unit} value={unit}>
{unit}
</option>
))}
</select>
</label>
</div>
) : null}
</div>
{!isDoctorScope && (
<div className="mt-4 rounded-xl border border-[#404040] bg-[#1f1f1f] px-4 py-3 text-sm text-[#a3a3a3]">
Perfil atual: {viewerProfile?.role || 'Administrador'} | agendamentos exibidos para todos os profissionais.
Perfil atual: {viewerProfile?.role || 'Administrador'}
</div>
)}
@@ -229,15 +302,34 @@ export function AgendaPage({ navigate }) {
</section>
)}
<DarkModal onClose={() => setModalOpen(false)} open={modalOpen} title="Nova consulta">
<DarkModal onClose={() => setModalOpen(false)} open={modalOpen} title="Novo agendamento">
<form className="grid gap-4" onSubmit={handleCreate}>
<DarkField label="Dia do agendamento">
<input
className="h-11 rounded-md border border-[#404040] bg-[#303030] px-3 text-sm text-[#e5e5e5] outline-none [color-scheme:dark] focus:border-[#3b82f6]"
onChange={(event) => {
const parsedDate = parseLocalDate(event.target.value)
if (parsedDate) setBaseDate(parsedDate)
}}
type="date"
value={formatLocalDateInput(baseDate)}
/>
</DarkField>
<DarkField label="Paciente">
<input
className="mb-2 h-10 rounded-md border border-[#404040] bg-[#303030] px-3 text-sm text-[#e5e5e5] outline-none transition placeholder:text-[#737373] focus:border-[#3b82f6]"
onChange={(event) => setModalPatientSearch(event.target.value)}
placeholder="Pesquisar paciente"
type="search"
value={modalPatientSearch}
/>
<select
className="h-11 rounded-md border border-[#404040] bg-[#303030] px-3 text-sm text-[#e5e5e5] outline-none focus:border-[#3b82f6]"
onChange={(event) => updateForm('patientId', event.target.value)}
value={form.patientId}
>
{patients.map((patient) => (
{filteredPatients.map((patient) => (
<option key={patient.id} value={patient.id}>
{patient.name || patient.full_name || patient.nome}
</option>
@@ -295,17 +387,26 @@ export function AgendaPage({ navigate }) {
value={currentProfessional?.name || 'Médico não vinculado'}
/>
) : (
<select
className="h-11 rounded-md border border-[#404040] bg-[#303030] px-3 text-sm text-[#e5e5e5] outline-none focus:border-[#3b82f6]"
onChange={(event) => updateForm('professionalId', event.target.value)}
value={form.professionalId}
>
{professionals.map((professional) => (
<option key={professional.id} value={professional.id}>
{professional.name}
</option>
))}
</select>
<>
<input
className="mb-2 h-10 rounded-md border border-[#404040] bg-[#303030] px-3 text-sm text-[#e5e5e5] outline-none transition placeholder:text-[#737373] focus:border-[#3b82f6]"
onChange={(event) => setModalDoctorSearch(event.target.value)}
placeholder="Pesquisar médico"
type="search"
value={modalDoctorSearch}
/>
<select
className="h-11 rounded-md border border-[#404040] bg-[#303030] px-3 text-sm text-[#e5e5e5] outline-none focus:border-[#3b82f6]"
onChange={(event) => updateForm('professionalId', event.target.value)}
value={form.professionalId}
>
{filteredProfessionals.map((professional) => (
<option key={professional.id} value={professional.id}>
{professional.name}
</option>
))}
</select>
</>
)}
</DarkField>
@@ -330,7 +431,7 @@ export function AgendaPage({ navigate }) {
disabled={!canCreateAppointment}
type="submit"
>
Salvar consulta
Salvar
</button>
</div>
</form>