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:
@@ -2,6 +2,7 @@ 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'
|
||||
@@ -15,6 +16,9 @@ export function useAgenda() {
|
||||
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())
|
||||
@@ -28,6 +32,10 @@ export function useAgenda() {
|
||||
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
|
||||
@@ -63,7 +71,7 @@ export function useAgenda() {
|
||||
|
||||
if (agendaScope === 'doctor' && !resolvedProfessional) {
|
||||
setLocalAppointments([])
|
||||
setError('Nao foi possivel vincular o medico logado a um profissional da base.')
|
||||
setError('Não foi possível vincular o médico logado a um profissional da base.')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -97,6 +105,59 @@ export function useAgenda() {
|
||||
}
|
||||
}, [])
|
||||
|
||||
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),
|
||||
})
|
||||
|
||||
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.professionalId, modalOpen])
|
||||
|
||||
const visibleAppointments = useMemo(() => {
|
||||
let filtered = localAppointments
|
||||
|
||||
@@ -118,11 +179,6 @@ export function useAgenda() {
|
||||
return sortAppointmentsByTime(filtered)
|
||||
}, [localAppointments, status, activeView, baseDate])
|
||||
|
||||
const agendaScope = viewerProfile?.isDoctor ? 'doctor' : 'global'
|
||||
const canCreateAppointment = agendaScope === 'doctor'
|
||||
? Boolean(currentProfessional?.id)
|
||||
: professionals.length > 0
|
||||
|
||||
function updateForm(field, value) {
|
||||
setForm((current) => ({ ...current, [field]: value }))
|
||||
}
|
||||
@@ -135,7 +191,7 @@ export function useAgenda() {
|
||||
: form.professionalId
|
||||
|
||||
if (!targetProfessionalId) {
|
||||
alert('Nao foi possivel identificar o profissional da consulta.')
|
||||
alert('Não foi possível identificar o profissional da consulta.')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -180,6 +236,9 @@ export function useAgenda() {
|
||||
updateForm,
|
||||
handleCreate,
|
||||
visibleAppointments,
|
||||
availableSlots,
|
||||
slotsLoading,
|
||||
slotsError,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { getAuthSession, saveAuthSession } from '../config/api.js'
|
||||
import { AUTH_SESSION_CHANGED_EVENT, getAuthSession, saveAuthSession } from '../config/api.js'
|
||||
import { normalizeRole } from '../config/permissions.js'
|
||||
import { authRepository } from '../repositories/authRepository.js'
|
||||
|
||||
export function useAuth() {
|
||||
const [state, setState] = useState(() => {
|
||||
const session = getAuthSession()
|
||||
return {
|
||||
user: session?.user ?? null,
|
||||
role: session?.role ?? null,
|
||||
profile: session?.profile ?? null,
|
||||
isAuthenticated: !!session?.access_token,
|
||||
loading: !!session?.access_token && !session?.role,
|
||||
}
|
||||
})
|
||||
const [state, setState] = useState(() => getStateFromSession(getAuthSession()))
|
||||
|
||||
useEffect(() => {
|
||||
function syncSession() {
|
||||
setState(getStateFromSession(getAuthSession()))
|
||||
}
|
||||
|
||||
window.addEventListener(AUTH_SESSION_CHANGED_EVENT, syncSession)
|
||||
return () => window.removeEventListener(AUTH_SESSION_CHANGED_EVENT, syncSession)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
// Se não está autenticado ou já tem role salvo, não busca
|
||||
if (!state.isAuthenticated || state.role) {
|
||||
setState((s) => ({ ...s, loading: false }))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -29,22 +28,16 @@ export function useAuth() {
|
||||
.then((data) => {
|
||||
if (cancelled || !data) return
|
||||
|
||||
// Suporta diferentes formatos de resposta da API
|
||||
const role =
|
||||
Array.isArray(data.roles) ? data.roles[0]
|
||||
: (data.role ?? data.user_metadata?.role ?? data.app_metadata?.role ?? null)
|
||||
|
||||
const profile = data.profile ?? null
|
||||
const user = data.user ?? data ?? null
|
||||
|
||||
// Persiste na sessão para evitar nova busca a cada reload
|
||||
const profile = data.profile ?? data.perfil ?? null
|
||||
const user = data.user ?? data.usuario ?? data ?? null
|
||||
const role = resolveRole(data)
|
||||
const session = getAuthSession()
|
||||
saveAuthSession({ ...session, role, profile, user: user || session?.user })
|
||||
|
||||
setState((s) => ({ ...s, role, profile, user: user || s.user, loading: false }))
|
||||
saveAuthSession({ ...session, role, profile, user: user || session?.user })
|
||||
setState((current) => ({ ...current, role, profile, user: user || current.user, loading: false }))
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setState((s) => ({ ...s, loading: false }))
|
||||
if (!cancelled) setState((current) => ({ ...current, loading: false }))
|
||||
})
|
||||
|
||||
return () => {
|
||||
@@ -53,4 +46,56 @@ export function useAuth() {
|
||||
}, [state.isAuthenticated, state.role])
|
||||
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function getStateFromSession(session) {
|
||||
const role = normalizeRole(session?.role)
|
||||
|
||||
return {
|
||||
user: session?.user ?? null,
|
||||
role,
|
||||
profile: session?.profile ?? null,
|
||||
isAuthenticated: !!session?.access_token,
|
||||
loading: !!session?.access_token && !role,
|
||||
}
|
||||
}
|
||||
|
||||
function resolveRole(data) {
|
||||
const user = data?.user ?? data?.usuario ?? {}
|
||||
const profile = data?.profile ?? data?.perfil ?? {}
|
||||
const metadata = {
|
||||
...user?.user_metadata,
|
||||
...user?.app_metadata,
|
||||
...user?.metadata,
|
||||
...data?.user_metadata,
|
||||
...data?.app_metadata,
|
||||
...data?.metadata,
|
||||
}
|
||||
|
||||
const candidates = [
|
||||
...(Array.isArray(data?.roles) ? data.roles : []),
|
||||
...(Array.isArray(user?.roles) ? user.roles : []),
|
||||
data?.role,
|
||||
data?.cargo,
|
||||
profile?.role,
|
||||
profile?.cargo,
|
||||
user?.role,
|
||||
user?.cargo,
|
||||
metadata.role,
|
||||
metadata.cargo,
|
||||
]
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const role = normalizeRole(candidate)
|
||||
if (role) return role
|
||||
}
|
||||
|
||||
const permissions = data?.permissions ?? data?.permissoes ?? {}
|
||||
if (permissions.isAdmin) return 'admin'
|
||||
if (permissions.isManager) return 'gestor'
|
||||
if (permissions.isDoctor) return 'medico'
|
||||
if (permissions.isSecretary) return 'secretaria'
|
||||
if (permissions.isPatient) return 'paciente'
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user