fix(agenda): fuso automático (America/Sao_Paulo) + dentista configura seus horários
- TZ=America/Sao_Paulo no backend (compose): a agenda deixa de rodar em UTC — horários/slots passam a ser Brasília automaticamente (corrige deslocamento de 3h). - Acesso do DENTISTA: novo botão "Meus Horários" na agenda do dentista, abrindo a config direto nos horários/folgas DELE (HorariosConfig com initialTab/soDentista, restrito ao próprio registro). Antes só admin/donoclinica alcançavam a config. - Config da clínica: gate passa a incluir donoconsultorio (dono de consultório). Obs.: AgendaView.tsx também traz trabalho acumulado da agenda que estava no working tree. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,7 @@ services:
|
|||||||
- "./backend/.env"
|
- "./backend/.env"
|
||||||
environment:
|
environment:
|
||||||
- BAILEYS_ENGINE=infinite
|
- BAILEYS_ENGINE=infinite
|
||||||
|
- TZ=America/Sao_Paulo
|
||||||
- PORT=8018
|
- PORT=8018
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
# Senha do banco vem do ambiente (.env raiz em DEV; provisionado fora do git em PROD).
|
# Senha do banco vem do ambiente (.env raiz em DEV; provisionado fora do git em PROD).
|
||||||
|
|||||||
@@ -62,8 +62,9 @@ const GradeSemanal: React.FC<{ faixas: Faixa[]; onChange: (f: Faixa[]) => void;
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HorariosConfig: React.FC<{ isOpen: boolean; onClose: () => void; clinicaId?: string }> = ({ isOpen, onClose, clinicaId }) => {
|
export const HorariosConfig: React.FC<{ isOpen: boolean; onClose: () => void; clinicaId?: string; initialTab?: 'clinica' | 'dentistas'; soDentista?: boolean }> = ({ isOpen, onClose, clinicaId, initialTab, soDentista }) => {
|
||||||
const [aba, setAba] = useState<'clinica' | 'dentistas'>('clinica');
|
const [aba, setAba] = useState<'clinica' | 'dentistas'>(initialTab || 'clinica');
|
||||||
|
useEffect(() => { if (isOpen && initialTab) setAba(initialTab); }, [isOpen, initialTab]);
|
||||||
const [erro, setErro] = useState<string | null>(null);
|
const [erro, setErro] = useState<string | null>(null);
|
||||||
const [ok, setOk] = useState<string | null>(null);
|
const [ok, setOk] = useState<string | null>(null);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
@@ -96,11 +97,13 @@ export const HorariosConfig: React.FC<{ isOpen: boolean; onClose: () => void; cl
|
|||||||
if (!clinicaId) return;
|
if (!clinicaId) return;
|
||||||
try {
|
try {
|
||||||
const d = await req(`/clinica/${clinicaId}/dentistas`);
|
const d = await req(`/clinica/${clinicaId}/dentistas`);
|
||||||
setDentistas(d.dentistas || []);
|
let list: Dentista[] = d.dentistas || [];
|
||||||
const first = (d.dentistas || []).find((x: Dentista) => x.eh_voce) || (d.dentistas || [])[0];
|
if (soDentista) { const meus = list.filter((x) => x.eh_voce); if (meus.length) list = meus; } // dentista só vê o próprio
|
||||||
|
setDentistas(list);
|
||||||
|
const first = list.find((x: Dentista) => x.eh_voce) || list[0];
|
||||||
if (first) setDentSel(first.id);
|
if (first) setDentSel(first.id);
|
||||||
} catch (e: any) { flash(setErro, e.message); }
|
} catch (e: any) { flash(setErro, e.message); }
|
||||||
}, [clinicaId]);
|
}, [clinicaId, soDentista]);
|
||||||
|
|
||||||
useEffect(() => { if (isOpen) { carregarClinica(); carregarDentistas(); } }, [isOpen, carregarClinica, carregarDentistas]);
|
useEffect(() => { if (isOpen) { carregarClinica(); carregarDentistas(); } }, [isOpen, carregarClinica, carregarDentistas]);
|
||||||
const carregarFolgas = useCallback((id: string) => {
|
const carregarFolgas = useCallback((id: string) => {
|
||||||
@@ -162,7 +165,7 @@ export const HorariosConfig: React.FC<{ isOpen: boolean; onClose: () => void; cl
|
|||||||
|
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<div className="flex gap-1 px-8 pt-4 border-b border-gray-50">
|
<div className="flex gap-1 px-8 pt-4 border-b border-gray-50">
|
||||||
{(['clinica', 'dentistas'] as const).map((t) => (
|
{(soDentista ? (['dentistas'] as const) : (['clinica', 'dentistas'] as const)).map((t) => (
|
||||||
<button key={t} onClick={() => setAba(t)}
|
<button key={t} onClick={() => setAba(t)}
|
||||||
className={`px-4 py-2.5 text-xs font-black uppercase tracking-wide rounded-t-xl transition-colors ${aba === t ? 'bg-teal-600 text-white' : 'text-gray-400 hover:text-gray-600'}`}>
|
className={`px-4 py-2.5 text-xs font-black uppercase tracking-wide rounded-t-xl transition-colors ${aba === t ? 'bg-teal-600 text-white' : 'text-gray-400 hover:text-gray-600'}`}>
|
||||||
{t === 'clinica' ? 'Clínica' : 'Dentistas'}
|
{t === 'clinica' ? 'Clínica' : 'Dentistas'}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import timeGridPlugin from '@fullcalendar/timegrid';
|
|||||||
import interactionPlugin from '@fullcalendar/interaction';
|
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, History, Download, PanelLeftClose, PanelLeftOpen } 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, Download, PanelLeftClose, PanelLeftOpen } from 'lucide-react';
|
||||||
import { AgendaSettingsModal } from '../components/AgendaSettingsModal.tsx';
|
import { AgendaSettingsModal } from '../components/AgendaSettingsModal.tsx';
|
||||||
|
import { HorariosConfig } from '../components/HorariosConfig.tsx';
|
||||||
import { AgendaDetailModal, ScoreBadge, FamiliaChips } from '../components/AgendaDetailModal.tsx';
|
import { AgendaDetailModal, ScoreBadge, FamiliaChips } from '../components/AgendaDetailModal.tsx';
|
||||||
import { WhatsChatDrawer } from './newwhats/WhatsChatDrawer.tsx';
|
import { WhatsChatDrawer } from './newwhats/WhatsChatDrawer.tsx';
|
||||||
import { AgendaPresence } from '../components/AgendaPresence.tsx';
|
import { AgendaPresence } from '../components/AgendaPresence.tsx';
|
||||||
@@ -343,13 +344,14 @@ const AppointmentModal: React.FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AgendaView: React.FC<{ onNavigate?: (view: string) => void; sidebarCollapsed?: boolean; onToggleSidebar?: () => void }> = ({ onNavigate, sidebarCollapsed, onToggleSidebar }) => {
|
export const AgendaView: React.FC<{ onNavigate?: (view: string) => void; sidebarCollapsed?: boolean; onToggleSidebar?: () => void; controlsOpen?: boolean; onCloseControls?: () => void }> = ({ onNavigate, sidebarCollapsed, onToggleSidebar, controlsOpen = false, onCloseControls }) => {
|
||||||
const { data: agendamentos, refresh } = useHybridBackend<Agendamento[]>(HybridBackend.getAgendamentos);
|
const { data: agendamentos, refresh } = useHybridBackend<Agendamento[]>(HybridBackend.getAgendamentos);
|
||||||
const { data: dentists, refresh: refreshDentists } = useHybridBackend<Dentista[]>(HybridBackend.getDentistas);
|
const { data: dentists, refresh: refreshDentists } = useHybridBackend<Dentista[]>(HybridBackend.getDentistas);
|
||||||
const { data: googleEvents, refresh: refreshGoogle } = useHybridBackend<any[]>(HybridBackend.getGoogleEvents);
|
const { data: googleEvents, refresh: refreshGoogle } = useHybridBackend<any[]>(HybridBackend.getGoogleEvents);
|
||||||
const [events, setEvents] = useState<any[]>([]);
|
const [events, setEvents] = useState<any[]>([]);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||||
|
const [showMeusHorarios, setShowMeusHorarios] = useState(false);
|
||||||
const [selectedDateInfo, setSelectedDateInfo] = useState<any>(null);
|
const [selectedDateInfo, setSelectedDateInfo] = useState<any>(null);
|
||||||
const [selectedAppointment, setSelectedAppointment] = useState<Agendamento | null>(null);
|
const [selectedAppointment, setSelectedAppointment] = useState<Agendamento | null>(null);
|
||||||
const [showDetails, setShowDetails] = useState(false);
|
const [showDetails, setShowDetails] = useState(false);
|
||||||
@@ -600,10 +602,25 @@ export const AgendaView: React.FC<{ onNavigate?: (view: string) => void; sidebar
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col lg:flex-row lg:gap-4 h-full">
|
<div className="flex flex-col lg:flex-row lg:gap-4 h-full">
|
||||||
{/* Coluna de controles (desktop: lateral esquerda vertical; mobile: topo compacto) */}
|
{/* Backdrop da gaveta de controles (mobile) — fecha ao tocar fora. Como a sidebar. */}
|
||||||
<aside className="shrink-0 mb-3 lg:mb-0 lg:w-56 lg:overflow-y-auto lg:pr-3 lg:border-r lg:border-gray-100">
|
{controlsOpen && (
|
||||||
{/* Título + recolher menu (desktop, ao lado) + AGENDAR (mobile, à direita) */}
|
<div className="fixed inset-0 bg-black/50 z-20 lg:hidden backdrop-blur-sm transition-opacity" onClick={onCloseControls} />
|
||||||
<div className="flex items-center gap-2 mb-3">
|
)}
|
||||||
|
{/* Coluna de controles — desktop: lateral esquerda fixa; mobile: gaveta deslizante
|
||||||
|
(oculta por padrão p/ dar o máximo de espaço ao calendário; abre pelo ⋯ do header). */}
|
||||||
|
<aside className={`
|
||||||
|
fixed inset-y-0 right-0 z-30 w-72 max-w-[85%] bg-white shadow-2xl overflow-y-auto p-4 transform transition-transform duration-300
|
||||||
|
${controlsOpen ? 'translate-x-0' : 'translate-x-full'}
|
||||||
|
lg:static lg:inset-auto lg:z-auto lg:w-56 lg:max-w-none lg:translate-x-0 lg:transform-none lg:transition-none lg:shadow-none lg:bg-transparent lg:p-0 lg:overflow-y-auto lg:pr-3 lg:border-r lg:border-gray-100
|
||||||
|
shrink-0 lg:mb-0`}>
|
||||||
|
{/* Fechar a gaveta (só mobile) */}
|
||||||
|
<button onClick={onCloseControls} title="Fechar"
|
||||||
|
className="lg:hidden absolute top-3 right-3 p-1.5 rounded-lg text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition-colors z-10">
|
||||||
|
<X size={20} />
|
||||||
|
</button>
|
||||||
|
{/* Título + recolher menu (desktop, ao lado). No mobile o AGENDAR e as
|
||||||
|
ações ficam empilhados abaixo (dentro da gaveta). */}
|
||||||
|
<div className="flex items-center gap-2 mb-3 pr-8 lg:pr-0">
|
||||||
{onToggleSidebar && (
|
{onToggleSidebar && (
|
||||||
<button onClick={onToggleSidebar} title={sidebarCollapsed ? 'Mostrar menu lateral' : 'Recolher menu lateral'}
|
<button onClick={onToggleSidebar} title={sidebarCollapsed ? 'Mostrar menu lateral' : 'Recolher menu lateral'}
|
||||||
className="hidden lg:flex items-center justify-center w-8 h-8 shrink-0 rounded-lg text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition-colors">
|
className="hidden lg:flex items-center justify-center w-8 h-8 shrink-0 rounded-lg text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition-colors">
|
||||||
@@ -611,56 +628,62 @@ export const AgendaView: React.FC<{ onNavigate?: (view: string) => void; sidebar
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<h2 className="text-xl lg:text-base font-bold text-gray-900 uppercase flex-1 min-w-0 leading-tight break-words">AGENDA CLÍNICA</h2>
|
<h2 className="text-xl lg:text-base font-bold text-gray-900 uppercase flex-1 min-w-0 leading-tight break-words">AGENDA CLÍNICA</h2>
|
||||||
{(currentRole !== 'paciente' && !souDentista) && (
|
|
||||||
<button onClick={() => { setReagendarPatient(null); setSelectedAppointment(null); setSelectedDateInfo({ dateStr: new Date().toISOString() }); setIsModalOpen(true); }} title="Agendar"
|
|
||||||
className="lg:hidden shrink-0 bg-teal-600 text-white px-3 py-2 rounded-lg text-xs font-bold uppercase flex items-center gap-1.5 hover:bg-teal-700 shadow-sm">
|
|
||||||
<Plus size={16} /> AGENDAR
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 sm:gap-3 items-center flex-wrap lg:flex-col lg:items-stretch">
|
<div className="flex gap-2 sm:gap-3 items-center flex-wrap lg:flex-col lg:items-stretch">
|
||||||
{/* AGENDAR (desktop, full-width no topo da coluna) */}
|
{/* AGENDAR (full-width no topo da coluna/gaveta — mobile e desktop) */}
|
||||||
{(currentRole !== 'paciente' && !souDentista) && (
|
{(currentRole !== 'paciente' && !souDentista) && (
|
||||||
<button onClick={() => { setReagendarPatient(null); setSelectedAppointment(null); setSelectedDateInfo({ dateStr: new Date().toISOString() }); setIsModalOpen(true); }} title="Agendar"
|
<button onClick={() => { setReagendarPatient(null); setSelectedAppointment(null); setSelectedDateInfo({ dateStr: new Date().toISOString() }); setIsModalOpen(true); onCloseControls?.(); }} title="Agendar"
|
||||||
className="hidden lg:flex w-full bg-teal-600 text-white px-4 py-2.5 rounded-lg text-sm font-bold uppercase items-center justify-center gap-2 hover:bg-teal-700 shadow-sm transition-colors">
|
className="flex w-full bg-teal-600 text-white px-4 py-2.5 rounded-lg text-sm font-bold uppercase items-center justify-center gap-2 hover:bg-teal-700 shadow-sm transition-colors">
|
||||||
<Plus size={18} /> AGENDAR
|
<Plus size={18} /> AGENDAR
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{/* ícones de ação — mesmo tamanho, distribuídos horizontalmente */}
|
{/* Ações — mobile: empilhadas, ícone + nome, largura cheia; desktop: fileira de ícones */}
|
||||||
<div className="flex gap-2 w-full">
|
<div className="flex flex-col lg:flex-row gap-2 w-full">
|
||||||
{(currentRole !== 'paciente') && (
|
{(currentRole !== 'paciente') && (
|
||||||
<button onClick={() => { setShowAtividade(true); loadAtividade(); }} title="Atividade da equipe hoje"
|
<button onClick={() => { setShowAtividade(true); loadAtividade(); onCloseControls?.(); }} title="Atividade da equipe hoje"
|
||||||
className="flex-1 flex items-center justify-center py-2.5 bg-white text-gray-400 hover:text-teal-600 border border-gray-200 rounded-xl transition-all hover:border-teal-200 hover:shadow-sm">
|
className="w-full lg:flex-1 flex items-center justify-start lg:justify-center gap-3 lg:gap-0 px-4 lg:px-0 py-2.5 bg-white text-gray-500 lg:text-gray-400 hover:text-teal-600 border border-gray-200 rounded-xl transition-all hover:border-teal-200 hover:shadow-sm">
|
||||||
<History size={20} />
|
<History size={20} className="shrink-0" />
|
||||||
|
<span className="lg:hidden text-sm font-bold">Atividade da equipe</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{(currentRole === 'admin' || currentRole === 'donoclinica' || currentRole === 'donoconsultorio') && (
|
{(currentRole === 'admin' || currentRole === 'donoclinica' || currentRole === 'donoconsultorio') && (
|
||||||
<button onClick={handleImportarGoogle} disabled={importando} title="Importar agendamentos do Google para o sistema"
|
<button onClick={handleImportarGoogle} disabled={importando} title="Importar agendamentos do Google para o sistema"
|
||||||
className="flex-1 flex items-center justify-center py-2.5 bg-white text-gray-400 hover:text-emerald-600 border border-gray-200 rounded-xl transition-all hover:border-emerald-200 hover:shadow-sm disabled:opacity-50">
|
className="w-full lg:flex-1 flex items-center justify-start lg:justify-center gap-3 lg:gap-0 px-4 lg:px-0 py-2.5 bg-white text-gray-500 lg:text-gray-400 hover:text-emerald-600 border border-gray-200 rounded-xl transition-all hover:border-emerald-200 hover:shadow-sm disabled:opacity-50">
|
||||||
{importando ? <Loader2 size={20} className="animate-spin" /> : <Download size={20} />}
|
{importando ? <Loader2 size={20} className="animate-spin shrink-0" /> : <Download size={20} className="shrink-0" />}
|
||||||
|
<span className="lg:hidden text-sm font-bold">Importar do Google</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{(currentRole === 'admin' || currentRole === 'donoclinica') && (
|
{(currentRole === 'admin' || currentRole === 'donoclinica' || currentRole === 'donoconsultorio') && (
|
||||||
<button onClick={() => setIsSettingsOpen(true)} title="Configurações da agenda"
|
<button onClick={() => { setIsSettingsOpen(true); onCloseControls?.(); }} title="Configurações da agenda"
|
||||||
className="flex-1 flex items-center justify-center py-2.5 bg-white text-gray-400 hover:text-teal-600 border border-gray-200 rounded-xl transition-all hover:border-teal-200 hover:shadow-sm">
|
className="w-full lg:flex-1 flex items-center justify-start lg:justify-center gap-3 lg:gap-0 px-4 lg:px-0 py-2.5 bg-white text-gray-500 lg:text-gray-400 hover:text-teal-600 border border-gray-200 rounded-xl transition-all hover:border-teal-200 hover:shadow-sm">
|
||||||
<GearIcon size={20} />
|
<GearIcon size={20} className="shrink-0" />
|
||||||
|
<span className="lg:hidden text-sm font-bold">Configurações</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{souDentista && (
|
{souDentista && (
|
||||||
<button onClick={() => { setShowInteresses(true); loadInteresses(); }} title="Pedidos de interesse na sua agenda"
|
<button onClick={() => { setShowMeusHorarios(true); onCloseControls?.(); }} title="Configurar meus dias e horários de atendimento"
|
||||||
className="relative flex-1 flex items-center justify-center py-2.5 bg-white text-gray-400 hover:text-teal-600 border border-gray-200 rounded-xl transition-all hover:border-teal-200 hover:shadow-sm">
|
className="w-full lg:flex-1 flex items-center justify-start lg:justify-center gap-3 lg:gap-0 px-4 lg:px-0 py-2.5 bg-white text-gray-500 lg:text-gray-400 hover:text-teal-600 border border-gray-200 rounded-xl transition-all hover:border-teal-200 hover:shadow-sm">
|
||||||
<Bell size={20} />
|
<Clock size={20} className="shrink-0" />
|
||||||
|
<span className="lg:hidden text-sm font-bold">Meus Horários</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{souDentista && (
|
||||||
|
<button onClick={() => { setShowInteresses(true); loadInteresses(); onCloseControls?.(); }} title="Pedidos de interesse na sua agenda"
|
||||||
|
className="relative w-full lg:flex-1 flex items-center justify-start lg:justify-center gap-3 lg:gap-0 px-4 lg:px-0 py-2.5 bg-white text-gray-500 lg:text-gray-400 hover:text-teal-600 border border-gray-200 rounded-xl transition-all hover:border-teal-200 hover:shadow-sm">
|
||||||
|
<Bell size={20} className="shrink-0" />
|
||||||
|
<span className="lg:hidden text-sm font-bold">Interesses na agenda</span>
|
||||||
{interessesAguardando > 0 && (
|
{interessesAguardando > 0 && (
|
||||||
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-[9px] font-black rounded-full w-4 h-4 flex items-center justify-center">{interessesAguardando}</span>
|
<span className="absolute top-1.5 left-6 lg:left-auto lg:-top-1 lg:-right-1 bg-red-500 text-white text-[9px] font-black rounded-full w-4 h-4 flex items-center justify-center">{interessesAguardando}</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{(currentRole !== 'paciente' && !souDentista) && (
|
{(currentRole !== 'paciente' && !souDentista) && (
|
||||||
<button onClick={() => { setShowPendencias(true); loadPendencias(); }} title="Pacientes a reagendar"
|
<button onClick={() => { setShowPendencias(true); loadPendencias(); onCloseControls?.(); }} title="Pacientes a reagendar"
|
||||||
className="relative flex-1 flex items-center justify-center py-2.5 bg-white text-gray-400 hover:text-amber-600 border border-gray-200 rounded-xl transition-all hover:border-amber-200 hover:shadow-sm">
|
className="relative w-full lg:flex-1 flex items-center justify-start lg:justify-center gap-3 lg:gap-0 px-4 lg:px-0 py-2.5 bg-white text-gray-500 lg:text-gray-400 hover:text-amber-600 border border-gray-200 rounded-xl transition-all hover:border-amber-200 hover:shadow-sm">
|
||||||
<CalendarClock size={20} />
|
<CalendarClock size={20} className="shrink-0" />
|
||||||
|
<span className="lg:hidden text-sm font-bold">Pacientes a reagendar</span>
|
||||||
{pendencias.length > 0 && (
|
{pendencias.length > 0 && (
|
||||||
<span className="absolute -top-1 -right-1 bg-amber-500 text-white text-[9px] font-black rounded-full w-4 h-4 flex items-center justify-center">{pendencias.length}</span>
|
<span className="absolute top-1.5 left-6 lg:left-auto lg:-top-1 lg:-right-1 bg-amber-500 text-white text-[9px] font-black rounded-full w-4 h-4 flex items-center justify-center">{pendencias.length}</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -747,7 +770,10 @@ export const AgendaView: React.FC<{ onNavigate?: (view: string) => void; sidebar
|
|||||||
onEditar={() => { setShowDetails(false); setIsModalOpen(true); }}
|
onEditar={() => { setShowDetails(false); setIsModalOpen(true); }}
|
||||||
onAtender={handleAtender}
|
onAtender={handleAtender}
|
||||||
onVerWhats={(info) => setWhatsTarget(info)}
|
onVerWhats={(info) => setWhatsTarget(info)}
|
||||||
podeAtender={!!onNavigate && currentRole !== 'paciente'}
|
// "Atender" (abre Lançar GTO) é ação CLÍNICA — atende quem trata: dentista,
|
||||||
|
// biomédico, dono da clínica/consultório, dentista locatário, admin. A recepção
|
||||||
|
// (funcionario) NÃO atende; ela só remarca/desmarca/agenda.
|
||||||
|
podeAtender={!!onNavigate && currentRole !== 'paciente' && currentRole !== 'funcionario'}
|
||||||
dentistaRestrito={souDentista}
|
dentistaRestrito={souDentista}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -861,6 +887,7 @@ export const AgendaView: React.FC<{ onNavigate?: (view: string) => void; sidebar
|
|||||||
initialPatient={reagendarPatient}
|
initialPatient={reagendarPatient}
|
||||||
/>
|
/>
|
||||||
<AgendaSettingsModal isOpen={isSettingsOpen} onClose={() => setIsSettingsOpen(false)} dentists={dentists} />
|
<AgendaSettingsModal isOpen={isSettingsOpen} onClose={() => setIsSettingsOpen(false)} dentists={dentists} />
|
||||||
|
<HorariosConfig isOpen={showMeusHorarios} onClose={() => setShowMeusHorarios(false)} clinicaId={(HybridBackend.getActiveWorkspace() as any)?.id} initialTab="dentistas" soDentista />
|
||||||
|
|
||||||
{showInteresses && (
|
{showInteresses && (
|
||||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center backdrop-blur-sm p-4" onClick={() => setShowInteresses(false)}>
|
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center backdrop-blur-sm p-4" onClick={() => setShowInteresses(false)}>
|
||||||
|
|||||||
Reference in New Issue
Block a user