new file: public/favicon.svg
deleted: src/assets/hero.png modified: src/components/AppShell.jsx modified: src/components/calendar/AgendaDailyView.jsx modified: src/components/calendar/AgendaMonthlyView.jsx modified: src/components/calendar/AgendaWeeklyView.jsx modified: src/hooks/useAgenda.js modified: src/index.css modified: src/mappers/appointmentMapper.js modified: src/mappers/reportMapper.js modified: src/pages/AgendaPage.jsx modified: src/pages/AuthPages.jsx modified: src/pages/HomePage.jsx modified: src/pages/MessagesPage.jsx modified: src/pages/PatientsPage.jsx modified: src/pages/ProfilePage.jsx modified: src/pages/ReportsPage.jsx modified: src/pages/SettingsPage.jsx modified: src/repositories/appointmentRepository.js modified: src/repositories/settingsRepository.js
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { isSameDay } from 'date-fns'
|
||||
|
||||
import { appointmentRepository } from '../repositories/appointmentRepository.js'
|
||||
@@ -8,6 +8,16 @@ import { professionalRepository } from '../repositories/professionalRepository.j
|
||||
import { profileRepository } from '../repositories/profileRepository.js'
|
||||
import { formatLocalDateInput, parseLocalDate, sortAppointmentsByTime } from '../utils/agendaDate.js'
|
||||
|
||||
const initialForm = {
|
||||
patientId: '',
|
||||
professionalId: '',
|
||||
type: 'Retorno',
|
||||
time: '15:30',
|
||||
mode: 'Teleconsulta',
|
||||
status: 'Aguardando',
|
||||
notes: '',
|
||||
}
|
||||
|
||||
export function useAgenda() {
|
||||
const [patients, setPatients] = useState([])
|
||||
const [professionals, setProfessionals] = useState([])
|
||||
@@ -27,14 +37,9 @@ export function useAgenda() {
|
||||
const [doctorSearch, setDoctorSearch] = useState('')
|
||||
const [unitFilter, setUnitFilter] = useState('')
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const [editingAppointment, setEditingAppointment] = useState(null)
|
||||
const [form, setForm] = useState(initialForm)
|
||||
|
||||
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)
|
||||
@@ -55,10 +60,10 @@ export function useAgenda() {
|
||||
|
||||
if (!active) return
|
||||
|
||||
const agendaScope = currentProfile?.isDoctor ? 'doctor' : 'global'
|
||||
const currentScope = currentProfile?.isDoctor ? 'doctor' : 'global'
|
||||
const resolvedProfessional = professionalRepository.resolveCurrentProfessional(currentProfile, professionalsData)
|
||||
const initialProfessionalId =
|
||||
agendaScope === 'doctor'
|
||||
currentScope === 'doctor'
|
||||
? resolvedProfessional?.id || ''
|
||||
: professionalsData?.[0]?.id || ''
|
||||
|
||||
@@ -72,20 +77,20 @@ export function useAgenda() {
|
||||
professionalId: initialProfessionalId,
|
||||
}))
|
||||
|
||||
if (agendaScope === 'doctor' && !resolvedProfessional) {
|
||||
if (currentScope === '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,
|
||||
doctorId: currentScope === 'doctor' ? resolvedProfessional?.id : undefined,
|
||||
})
|
||||
|
||||
if (!active) return
|
||||
|
||||
setLocalAppointments(
|
||||
agendaScope === 'doctor' && resolvedProfessional
|
||||
currentScope === 'doctor' && resolvedProfessional
|
||||
? filterAppointmentsByProfessional(appointmentsData || [], resolvedProfessional.id)
|
||||
: sortAppointmentsByTime(appointmentsData || []),
|
||||
)
|
||||
@@ -95,9 +100,7 @@ export function useAgenda() {
|
||||
console.error(loadError)
|
||||
setError(loadError.message || 'Erro ao carregar agenda.')
|
||||
} finally {
|
||||
if (active) {
|
||||
setLoading(false)
|
||||
}
|
||||
if (active) setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +112,7 @@ export function useAgenda() {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!modalOpen) return
|
||||
if (!modalOpen || editingAppointment) return
|
||||
|
||||
const targetProfessionalId = agendaScope === 'doctor'
|
||||
? currentProfessional?.id
|
||||
@@ -160,7 +163,7 @@ export function useAgenda() {
|
||||
return () => {
|
||||
active = false
|
||||
}
|
||||
}, [agendaScope, baseDate, currentProfessional?.id, form.mode, form.professionalId, modalOpen])
|
||||
}, [agendaScope, baseDate, currentProfessional?.id, editingAppointment, form.mode, form.professionalId, modalOpen])
|
||||
|
||||
const visibleAppointments = useMemo(() => {
|
||||
let filtered = localAppointments
|
||||
@@ -205,46 +208,154 @@ export function useAgenda() {
|
||||
}
|
||||
|
||||
return sortAppointmentsByTime(filtered)
|
||||
}, [localAppointments, status, agendaScope, doctorFilter, doctorSearch, unitFilter, professionals, activeView, baseDate])
|
||||
}, [activeView, agendaScope, baseDate, doctorFilter, doctorSearch, localAppointments, professionals, status, unitFilter])
|
||||
|
||||
function updateForm(field, value) {
|
||||
setForm((current) => ({ ...current, [field]: value }))
|
||||
}
|
||||
|
||||
async function handleCreate(event) {
|
||||
function openCreateModal({ date, time } = {}) {
|
||||
if (date) {
|
||||
const parsedDate = parseLocalDate(date)
|
||||
if (parsedDate) setBaseDate(parsedDate)
|
||||
}
|
||||
|
||||
setEditingAppointment(null)
|
||||
setAvailableSlots([])
|
||||
setSlotsError('')
|
||||
setForm((current) => ({
|
||||
...initialForm,
|
||||
patientId: current.patientId || patients[0]?.id || '',
|
||||
professionalId:
|
||||
agendaScope === 'doctor'
|
||||
? currentProfessional?.id || ''
|
||||
: current.professionalId || professionals[0]?.id || '',
|
||||
time: time || current.time || initialForm.time,
|
||||
}))
|
||||
setModalOpen(true)
|
||||
}
|
||||
|
||||
function openAppointmentModal(appointment) {
|
||||
const parsedDate = parseLocalDate(appointment.date)
|
||||
if (parsedDate) setBaseDate(parsedDate)
|
||||
|
||||
setEditingAppointment(appointment)
|
||||
setAvailableSlots([])
|
||||
setSlotsError('')
|
||||
setForm({
|
||||
patientId: appointment.patientId || '',
|
||||
professionalId: appointment.professionalId || '',
|
||||
type: appointment.type || initialForm.type,
|
||||
time: appointment.time || initialForm.time,
|
||||
mode: appointment.mode || initialForm.mode,
|
||||
status: appointment.status || initialForm.status,
|
||||
notes: appointment.notes || '',
|
||||
})
|
||||
setModalOpen(true)
|
||||
}
|
||||
|
||||
function closeAppointmentModal() {
|
||||
setModalOpen(false)
|
||||
setEditingAppointment(null)
|
||||
}
|
||||
|
||||
async function handleSubmitAppointment(event) {
|
||||
event.preventDefault()
|
||||
|
||||
if (!form.patientId) {
|
||||
alert('Selecione um paciente para criar o agendamento.')
|
||||
if (editingAppointment) {
|
||||
await updateAppointment()
|
||||
return
|
||||
}
|
||||
|
||||
await createAppointment()
|
||||
}
|
||||
|
||||
async function createAppointment() {
|
||||
const payload = buildPayload()
|
||||
if (!payload) return
|
||||
|
||||
try {
|
||||
const created = await appointmentRepository.create(payload)
|
||||
setLocalAppointments((current) => sortAppointmentsByTime([...current, enrichAppointment(created, payload, patients, professionals)]))
|
||||
closeAppointmentModal()
|
||||
} catch (createError) {
|
||||
alert(createError.message || 'Erro ao criar agendamento.')
|
||||
}
|
||||
}
|
||||
|
||||
async function updateAppointment() {
|
||||
if (!editingAppointment) return
|
||||
|
||||
const payload = buildPayload()
|
||||
if (!payload) return
|
||||
|
||||
try {
|
||||
const updated = await appointmentRepository.update(editingAppointment.id, payload)
|
||||
setLocalAppointments((current) =>
|
||||
sortAppointmentsByTime(
|
||||
current.map((appointment) =>
|
||||
appointment.id === editingAppointment.id
|
||||
? enrichAppointment(updated, payload, patients, professionals)
|
||||
: appointment,
|
||||
),
|
||||
),
|
||||
)
|
||||
closeAppointmentModal()
|
||||
} catch (updateError) {
|
||||
alert(updateError.message || 'Erro ao atualizar agendamento.')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCancelAppointment() {
|
||||
if (!editingAppointment) return
|
||||
if (!window.confirm('Tem certeza que deseja cancelar este agendamento?')) return
|
||||
|
||||
const payload = buildPayload({ status: 'Cancelada' })
|
||||
if (!payload) return
|
||||
|
||||
try {
|
||||
const cancelled = await appointmentRepository.cancel(editingAppointment.id, payload)
|
||||
setLocalAppointments((current) =>
|
||||
sortAppointmentsByTime(
|
||||
current.map((appointment) =>
|
||||
appointment.id === editingAppointment.id
|
||||
? enrichAppointment(cancelled, payload, patients, professionals)
|
||||
: appointment,
|
||||
),
|
||||
),
|
||||
)
|
||||
closeAppointmentModal()
|
||||
} catch (cancelError) {
|
||||
alert(cancelError.message || 'Erro ao cancelar agendamento.')
|
||||
}
|
||||
}
|
||||
|
||||
function buildPayload(overrides = {}) {
|
||||
if (!form.patientId) {
|
||||
alert('Selecione um paciente para salvar o agendamento.')
|
||||
return null
|
||||
}
|
||||
|
||||
const targetProfessionalId = agendaScope === 'doctor'
|
||||
? currentProfessional?.id
|
||||
: form.professionalId
|
||||
|
||||
if (!targetProfessionalId) {
|
||||
alert('Não foi possível identificar o profissional da consulta.')
|
||||
return
|
||||
return null
|
||||
}
|
||||
|
||||
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 {
|
||||
patientId: form.patientId,
|
||||
date: formatLocalDateInput(baseDate),
|
||||
time: form.time,
|
||||
type: form.type,
|
||||
mode: form.mode,
|
||||
status: form.status,
|
||||
notes: form.notes,
|
||||
room: form.mode === 'Teleconsulta' ? 'Virtual' : 'Consultório 1',
|
||||
professionalId: targetProfessionalId,
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,10 +381,14 @@ export function useAgenda() {
|
||||
unitFilter,
|
||||
setUnitFilter,
|
||||
modalOpen,
|
||||
setModalOpen,
|
||||
editingAppointment,
|
||||
form,
|
||||
updateForm,
|
||||
handleCreate,
|
||||
openCreateModal,
|
||||
openAppointmentModal,
|
||||
closeAppointmentModal,
|
||||
handleSubmitAppointment,
|
||||
handleCancelAppointment,
|
||||
visibleAppointments,
|
||||
availableSlots,
|
||||
slotsLoading,
|
||||
@@ -289,6 +404,26 @@ function filterAppointmentsByProfessional(appointments, professionalId) {
|
||||
)
|
||||
}
|
||||
|
||||
function enrichAppointment(appointment, payload, patients, professionals) {
|
||||
const patient = patients.find((item) => String(item.id) === String(payload.patientId))
|
||||
const professional = professionals.find((item) => String(item.id) === String(payload.professionalId))
|
||||
|
||||
return {
|
||||
...appointment,
|
||||
patientId: payload.patientId,
|
||||
professionalId: payload.professionalId,
|
||||
patient: patient?.name || patient?.full_name || patient?.nome || appointment.patient,
|
||||
professional: professional?.name || professional?.full_name || professional?.nome || appointment.professional,
|
||||
date: payload.date,
|
||||
time: payload.time,
|
||||
type: payload.type,
|
||||
mode: payload.mode,
|
||||
status: payload.status,
|
||||
notes: payload.notes,
|
||||
room: payload.room,
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeValue(value) {
|
||||
return String(value || '').trim().toLowerCase()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user