diff --git a/backend/server.js b/backend/server.js index 892fe36..c997848 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1890,6 +1890,23 @@ app.get('/api/agenda/presenca', authGuard, async (req, res) => { } catch (err) { res.status(500).json({ success: false, error: err.message }); } }); +// Feed de ATIVIDADE da equipe (auditoria da clínica) — default: hoje. +app.get('/api/agenda/atividade', authGuard, async (req, res) => { + try { + const { clinicaId, desde } = req.query; + if (!clinicaId) return res.json({ success: true, atividade: [] }); + const since = desde || new Date(new Date().setHours(0, 0, 0, 0)).toISOString(); + const { rows } = await pool.query( + `SELECT a.acao, a.actor_nome, a.entidade, a.entidade_id, a.detalhes, a.at, ag.pacientenome + FROM audit_log a + LEFT JOIN agendamentos ag ON ag.id = a.entidade_id + WHERE a.clinica_id = $1 AND a.at >= $2 + ORDER BY a.at DESC LIMIT 80`, + [clinicaId, since]); + res.json({ success: true, atividade: rows }); + } catch (err) { res.status(500).json({ success: false, error: err.message }); } +}); + // Grupo familiar do paciente (para chips compactos no modal de agenda). app.get('/api/pacientes/:id/familia', authGuard, async (req, res) => { try { diff --git a/frontend/services/backend.ts b/frontend/services/backend.ts index 554e903..317c050 100644 --- a/frontend/services/backend.ts +++ b/frontend/services/backend.ts @@ -789,6 +789,15 @@ export const HybridBackend = { return { grupo: j.grupo || null, membros: j.membros || [] }; }, + // Feed de atividade da equipe (auditoria) — default: hoje. + getAtividadeAgenda: async (clinicaId?: string): Promise => { + const cid = clinicaId || HybridBackend.getActiveWorkspace()?.id || ''; + if (!cid) return []; + const res = await apiFetch(`${API_URL}/agenda/atividade?clinicaId=${cid}`, {}); + const j = await res.json().catch(() => ({} as any)); + return j.atividade || []; + }, + // Presença na agenda (Fase 2): heartbeat + leitura de quem está online / última visita. pingPresencaAgenda: async (clinicaId?: string) => { const cid = clinicaId || HybridBackend.getActiveWorkspace()?.id || ''; diff --git a/frontend/views/AgendaView.tsx b/frontend/views/AgendaView.tsx index 5d84695..fe14817 100644 --- a/frontend/views/AgendaView.tsx +++ b/frontend/views/AgendaView.tsx @@ -3,7 +3,7 @@ import FullCalendar from '@fullcalendar/react'; import dayGridPlugin from '@fullcalendar/daygrid'; import timeGridPlugin from '@fullcalendar/timegrid'; import interactionPlugin from '@fullcalendar/interaction'; -import { Calendar as CalendarIcon, Plus, X, Loader2, GripVertical, Settings as GearIcon, Edit, Trash2, CalendarRange, Clock, User, Stethoscope, AlertCircle, CheckCircle2, Bell, CalendarClock } from 'lucide-react'; +import { Calendar as CalendarIcon, Plus, X, Loader2, GripVertical, Settings as GearIcon, Edit, Trash2, CalendarRange, Clock, User, Stethoscope, AlertCircle, CheckCircle2, Bell, CalendarClock, History } from 'lucide-react'; import { AgendaSettingsModal } from '../components/AgendaSettingsModal.tsx'; import { AgendaDetailModal, FaltasBadge, FamiliaChips } from '../components/AgendaDetailModal.tsx'; import { AgendaPresence } from '../components/AgendaPresence.tsx'; @@ -357,6 +357,8 @@ export const AgendaView: React.FC<{ onNavigate?: (view: string) => void }> = ({ const [pendencias, setPendencias] = useState([]); const [showPendencias, setShowPendencias] = useState(false); const [reagendarPatient, setReagendarPatient] = useState<{ id?: string; nome?: string } | null>(null); + const [atividade, setAtividade] = useState([]); + const [showAtividade, setShowAtividade] = useState(false); const toast = useToast(); // Lista "a reagendar" (faltas/cancelamentos/remarcações pendentes desta clínica) @@ -364,6 +366,9 @@ export const AgendaView: React.FC<{ onNavigate?: (view: string) => void }> = ({ try { setPendencias(await HybridBackend.getPendenciasReagendar()); } catch { /* ignore */ } }, []); useEffect(() => { loadPendencias(); }, [loadPendencias]); + const loadAtividade = useCallback(async () => { + try { setAtividade(await HybridBackend.getAtividadeAgenda()); } catch { /* ignore */ } + }, []); // Fase 4 Parte 2 — o dentista vê os pedidos de interesse na própria agenda const meuUsuarioId = (() => { try { return JSON.parse(localStorage.getItem('SCOREODONTO_USER_DATA') || '{}').id; } catch { return undefined; } })(); @@ -545,6 +550,12 @@ export const AgendaView: React.FC<{ onNavigate?: (view: string) => void }> = ({ + {(currentRole !== 'paciente') && ( + + )} {(currentRole === 'admin' || currentRole === 'donoclinica') && ( + +
+ {atividade.length === 0 ? ( +

NENHUMA ATIVIDADE HOJE.

+ ) : atividade.map((a, i) => { + const AC: Record = { criou: 'agendou', reagendou: 'reagendou', editou: 'editou', cancelou: 'cancelou', faltou: 'marcou falta de', remarcou: 'pediu remarcação de', restaurou: 'restaurou' }; + const pac = a.pacientenome || a.detalhes?.paciente || ''; + return ( +
+
+
+

+ {a.actor_nome || 'Alguém'} {AC[a.acao] || a.acao}{pac ? {pac} : null} +

+

{new Date(a.at).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })}

+
+
+ ); + })} +
+
+ + )}