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:
VPS 4 Builder
2026-07-06 04:44:38 +02:00
parent 0a324bf8c8
commit 773e8d4bf2
3 changed files with 71 additions and 40 deletions
+9 -6
View File
@@ -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 }) => {
const [aba, setAba] = useState<'clinica' | 'dentistas'>('clinica');
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'>(initialTab || 'clinica');
useEffect(() => { if (isOpen && initialTab) setAba(initialTab); }, [isOpen, initialTab]);
const [erro, setErro] = useState<string | null>(null);
const [ok, setOk] = useState<string | null>(null);
const [saving, setSaving] = useState(false);
@@ -96,11 +97,13 @@ export const HorariosConfig: React.FC<{ isOpen: boolean; onClose: () => void; cl
if (!clinicaId) return;
try {
const d = await req(`/clinica/${clinicaId}/dentistas`);
setDentistas(d.dentistas || []);
const first = (d.dentistas || []).find((x: Dentista) => x.eh_voce) || (d.dentistas || [])[0];
let list: Dentista[] = d.dentistas || [];
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);
} catch (e: any) { flash(setErro, e.message); }
}, [clinicaId]);
}, [clinicaId, soDentista]);
useEffect(() => { if (isOpen) { carregarClinica(); carregarDentistas(); } }, [isOpen, carregarClinica, carregarDentistas]);
const carregarFolgas = useCallback((id: string) => {
@@ -162,7 +165,7 @@ export const HorariosConfig: React.FC<{ isOpen: boolean; onClose: () => void; cl
{/* Tabs */}
<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)}
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'}