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