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
295 lines
8.9 KiB
JavaScript
295 lines
8.9 KiB
JavaScript
import { useState, useEffect, useMemo } from 'react'
|
|
import { isSameDay } from 'date-fns'
|
|
|
|
import { appointmentRepository } from '../repositories/appointmentRepository.js'
|
|
import { availabilityRepository } from '../repositories/availabilityRepository.js'
|
|
import { patientRepository } from '../repositories/patientRepository.js'
|
|
import { professionalRepository } from '../repositories/professionalRepository.js'
|
|
import { profileRepository } from '../repositories/profileRepository.js'
|
|
import { formatLocalDateInput, parseLocalDate, sortAppointmentsByTime } from '../utils/agendaDate.js'
|
|
|
|
export function useAgenda() {
|
|
const [patients, setPatients] = useState([])
|
|
const [professionals, setProfessionals] = useState([])
|
|
const [currentProfessional, setCurrentProfessional] = useState(null)
|
|
const [viewerProfile, setViewerProfile] = useState(null)
|
|
const [localAppointments, setLocalAppointments] = useState([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState('')
|
|
const [availableSlots, setAvailableSlots] = useState([])
|
|
const [slotsLoading, setSlotsLoading] = useState(false)
|
|
const [slotsError, setSlotsError] = useState('')
|
|
|
|
const [activeView, setActiveView] = useState('Dia')
|
|
const [baseDate, setBaseDate] = useState(new Date())
|
|
const [status, setStatus] = useState('Todos')
|
|
const [doctorFilter, setDoctorFilter] = useState('Todos')
|
|
const [doctorSearch, setDoctorSearch] = useState('')
|
|
const [unitFilter, setUnitFilter] = useState('')
|
|
const [modalOpen, setModalOpen] = useState(false)
|
|
|
|
const [form, setForm] = useState({
|
|
patientId: '',
|
|
professionalId: '',
|
|
type: 'Retorno',
|
|
time: '15:30',
|
|
mode: 'Teleconsulta',
|
|
})
|
|
const agendaScope = viewerProfile?.isDoctor ? 'doctor' : 'global'
|
|
const canCreateAppointment = agendaScope === 'doctor'
|
|
? Boolean(currentProfessional?.id)
|
|
: professionals.length > 0
|
|
|
|
useEffect(() => {
|
|
let active = true
|
|
|
|
async function loadAgendaContext() {
|
|
try {
|
|
setError('')
|
|
|
|
const [patientsData, professionalsData, currentProfile] = await Promise.all([
|
|
patientRepository.getAll(),
|
|
professionalRepository.getAll(),
|
|
profileRepository.getCurrentUserProfile(),
|
|
])
|
|
|
|
if (!active) return
|
|
|
|
const agendaScope = currentProfile?.isDoctor ? 'doctor' : 'global'
|
|
const resolvedProfessional = professionalRepository.resolveCurrentProfessional(currentProfile, professionalsData)
|
|
const initialProfessionalId =
|
|
agendaScope === 'doctor'
|
|
? resolvedProfessional?.id || ''
|
|
: professionalsData?.[0]?.id || ''
|
|
|
|
setViewerProfile(currentProfile)
|
|
setPatients(patientsData || [])
|
|
setCurrentProfessional(resolvedProfessional)
|
|
setProfessionals(professionalsData || [])
|
|
setForm((current) => ({
|
|
...current,
|
|
patientId: patientsData?.length ? patientsData[0].id : '',
|
|
professionalId: initialProfessionalId,
|
|
}))
|
|
|
|
if (agendaScope === 'doctor' && !resolvedProfessional) {
|
|
setLocalAppointments([])
|
|
setError('Não foi possível vincular o médico logado a um profissional da base.')
|
|
return
|
|
}
|
|
|
|
const appointmentsData = await appointmentRepository.getAll({
|
|
doctorId: agendaScope === 'doctor' ? resolvedProfessional?.id : undefined,
|
|
})
|
|
|
|
if (!active) return
|
|
|
|
setLocalAppointments(
|
|
agendaScope === 'doctor' && resolvedProfessional
|
|
? filterAppointmentsByProfessional(appointmentsData || [], resolvedProfessional.id)
|
|
: sortAppointmentsByTime(appointmentsData || []),
|
|
)
|
|
} catch (loadError) {
|
|
if (!active) return
|
|
|
|
console.error(loadError)
|
|
setError(loadError.message || 'Erro ao carregar agenda.')
|
|
} finally {
|
|
if (active) {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
loadAgendaContext()
|
|
|
|
return () => {
|
|
active = false
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (!modalOpen) return
|
|
|
|
const targetProfessionalId = agendaScope === 'doctor'
|
|
? currentProfessional?.id
|
|
: form.professionalId
|
|
|
|
let active = true
|
|
|
|
async function loadAvailableSlots() {
|
|
if (!targetProfessionalId) {
|
|
setAvailableSlots([])
|
|
setSlotsError('')
|
|
return
|
|
}
|
|
|
|
setSlotsLoading(true)
|
|
setSlotsError('')
|
|
|
|
try {
|
|
const slots = await availabilityRepository.getAvailableSlots({
|
|
doctorId: targetProfessionalId,
|
|
date: formatLocalDateInput(baseDate),
|
|
appointmentType: form.mode,
|
|
})
|
|
|
|
if (!active) return
|
|
|
|
const activeSlots = slots.filter((slot) => slot.available)
|
|
setAvailableSlots(activeSlots)
|
|
|
|
if (activeSlots.length) {
|
|
setForm((current) =>
|
|
activeSlots.some((slot) => slot.time === current.time)
|
|
? current
|
|
: { ...current, time: activeSlots[0].time },
|
|
)
|
|
}
|
|
} catch (loadError) {
|
|
if (!active) return
|
|
setAvailableSlots([])
|
|
setSlotsError(loadError.message || 'Não foi possível calcular horários disponíveis.')
|
|
} finally {
|
|
if (active) setSlotsLoading(false)
|
|
}
|
|
}
|
|
|
|
loadAvailableSlots()
|
|
|
|
return () => {
|
|
active = false
|
|
}
|
|
}, [agendaScope, baseDate, currentProfessional?.id, form.mode, form.professionalId, modalOpen])
|
|
|
|
const visibleAppointments = useMemo(() => {
|
|
let filtered = localAppointments
|
|
|
|
if (status !== 'Todos') {
|
|
filtered = filtered.filter((appointment) => appointment.status === status)
|
|
}
|
|
|
|
if (agendaScope !== 'doctor' && doctorFilter !== 'Todos') {
|
|
filtered = filterAppointmentsByProfessional(filtered, doctorFilter)
|
|
}
|
|
|
|
if (agendaScope !== 'doctor') {
|
|
const normalizedDoctorSearch = normalizeValue(doctorSearch)
|
|
const normalizedUnit = normalizeValue(unitFilter)
|
|
|
|
if (normalizedDoctorSearch || normalizedUnit) {
|
|
filtered = filtered.filter((appointment) => {
|
|
const professional = professionals.find(
|
|
(item) => normalizeValue(item.id) === normalizeValue(appointment.professionalId),
|
|
)
|
|
const professionalName = normalizeValue(professional?.name || appointment.professional)
|
|
const professionalUnit = normalizeValue(professional?.unit || appointment.unit)
|
|
|
|
return (
|
|
(!normalizedDoctorSearch || professionalName.includes(normalizedDoctorSearch)) &&
|
|
(!normalizedUnit || professionalUnit === normalizedUnit)
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
if (activeView === 'Dia') {
|
|
filtered = filtered.filter((appointment) => {
|
|
if (!appointment.date) return false
|
|
|
|
const appointmentDate = parseLocalDate(appointment.date)
|
|
if (!appointmentDate) return false
|
|
|
|
return isSameDay(appointmentDate, baseDate)
|
|
})
|
|
}
|
|
|
|
return sortAppointmentsByTime(filtered)
|
|
}, [localAppointments, status, agendaScope, doctorFilter, doctorSearch, unitFilter, professionals, activeView, baseDate])
|
|
|
|
function updateForm(field, value) {
|
|
setForm((current) => ({ ...current, [field]: value }))
|
|
}
|
|
|
|
async function handleCreate(event) {
|
|
event.preventDefault()
|
|
|
|
if (!form.patientId) {
|
|
alert('Selecione um paciente para criar o agendamento.')
|
|
return
|
|
}
|
|
|
|
const targetProfessionalId = agendaScope === 'doctor'
|
|
? currentProfessional?.id
|
|
: form.professionalId
|
|
|
|
if (!targetProfessionalId) {
|
|
alert('Não foi possível identificar o profissional da consulta.')
|
|
return
|
|
}
|
|
|
|
const dateStr = formatLocalDateInput(baseDate)
|
|
|
|
try {
|
|
const created = await appointmentRepository.create({
|
|
patientId: form.patientId,
|
|
date: dateStr,
|
|
time: form.time,
|
|
type: form.type,
|
|
mode: form.mode,
|
|
room: form.mode === 'Teleconsulta' ? 'Virtual' : 'Consultório 1',
|
|
professionalId: targetProfessionalId,
|
|
})
|
|
|
|
setLocalAppointments((current) => sortAppointmentsByTime([...current, created]))
|
|
setModalOpen(false)
|
|
} catch (createError) {
|
|
alert(createError.message || 'Erro ao criar agendamento.')
|
|
}
|
|
}
|
|
|
|
return {
|
|
patients,
|
|
professionals,
|
|
currentProfessional,
|
|
viewerProfile,
|
|
agendaScope,
|
|
loading,
|
|
error,
|
|
canCreateAppointment,
|
|
activeView,
|
|
setActiveView,
|
|
baseDate,
|
|
setBaseDate,
|
|
status,
|
|
setStatus,
|
|
doctorFilter,
|
|
setDoctorFilter,
|
|
doctorSearch,
|
|
setDoctorSearch,
|
|
unitFilter,
|
|
setUnitFilter,
|
|
modalOpen,
|
|
setModalOpen,
|
|
form,
|
|
updateForm,
|
|
handleCreate,
|
|
visibleAppointments,
|
|
availableSlots,
|
|
slotsLoading,
|
|
slotsError,
|
|
}
|
|
}
|
|
|
|
function filterAppointmentsByProfessional(appointments, professionalId) {
|
|
const normalizedProfessionalId = normalizeValue(professionalId)
|
|
|
|
return sortAppointmentsByTime(
|
|
appointments.filter((appointment) => normalizeValue(appointment.professionalId) === normalizedProfessionalId),
|
|
)
|
|
}
|
|
|
|
function normalizeValue(value) {
|
|
return String(value || '').trim().toLowerCase()
|
|
}
|