fix(login): render standalone views edge-to-edge e esconde scrollbar do carrossel de onboarding

- App.tsx: wrappers de padding (p-4/md:p-8) e max-w-7xl/mx-auto agora só se aplicam quando não é tela standalone; login/landing/public renderizam full-bleed
- OnboardingChat.tsx: classe ob-noscroll esconde a barra de rolagem do slide intro (mantém scroll), substituindo a custom-scrollbar indefinida no fluxo de login

Inclui também demais alterações pendentes do working tree (snapshot do estado atual de dev).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-06-13 13:23:19 +02:00
parent ae16a9b333
commit ba062e92d0
65 changed files with 20893 additions and 1974 deletions
+279 -363
View File
@@ -1,363 +1,279 @@
import React, { useState, useEffect } from 'react';
import { X, ChevronLeft, ChevronRight, Save, Mail, Calendar, Settings as GearIcon, CheckCircle2 } from 'lucide-react';
import { HybridBackend } from '../services/backend.ts';
import { Settings, Dentista } from '../types.ts';
import { useToast } from '../contexts/ToastContext.tsx';
import { GoogleConnectButton } from './GoogleConnectButton.tsx';
import { Link as LinkIcon, Copy, Share2 } from 'lucide-react';
interface AgendaSettingsModalProps {
isOpen: boolean;
onClose: () => void;
dentists: Dentista[] | null;
}
export const AgendaSettingsModal: React.FC<AgendaSettingsModalProps> = ({ isOpen, onClose, dentists }) => {
const [page, setPage] = useState(1);
const [config, setConfig] = useState<Settings>({
clinicEmail: '',
clinicCalendarId: '',
googleApiKey: '',
googleCalendarIds: {}
});
const [isSaving, setIsSaving] = useState(false);
const [connectedAccounts, setConnectedAccounts] = useState<any[]>([]);
const toast = useToast();
const fetchGoogleStatus = async () => {
try {
const response = await fetch('/api/auth/google/status');
const data = await response.json();
setConnectedAccounts(data);
} catch (error) {
console.error("Erro ao buscar status do Google:", error);
}
};
useEffect(() => {
if (isOpen) {
const loadSettings = async () => {
try {
const settings = await HybridBackend.getSettings();
setConfig({
clinicEmail: settings.clinicEmail || '',
clinicCalendarId: settings.clinicCalendarId || '',
googleApiKey: settings.googleApiKey || '',
googleCalendarIds: settings.googleCalendarIds || {}
});
} catch (error) {
console.error("Erro ao carregar configurações:", error);
}
};
loadSettings();
fetchGoogleStatus();
setPage(1); // Reset to first page
}
}, [isOpen]);
const copyInviteLink = (dentistId: string) => {
const url = `/api/auth/google/url?dentistId=${dentistId}`;
navigator.clipboard.writeText(url);
toast.success("LINK DE CONVITE COPIADO!");
};
const handleSave = async () => {
setIsSaving(true);
try {
await HybridBackend.saveSettings(config);
toast.success("CONFIGURAÇÕES SALVAS COM SUCESSO!");
onClose();
} catch (error) {
toast.error("ERRO AO SALVAR CONFIGURAÇÕES.");
} finally {
setIsSaving(false);
}
};
if (!isOpen) return null;
const totalPages = 3;
return (
<div className="fixed inset-0 bg-black/60 z-[60] flex items-center justify-center backdrop-blur-md p-0">
<div className="bg-white shadow-2xl w-full h-dvh md:w-[96vw] md:h-[98vh] rounded-none md:rounded-[2.5rem] flex flex-col overflow-hidden border border-gray-100 animate-in fade-in zoom-in-95 duration-300">
{/* Header */}
<div className="px-10 py-8 border-b border-gray-50 flex justify-between items-center bg-gradient-to-br from-gray-50 to-white">
<div className="flex items-center gap-4">
<div className="p-3 bg-blue-600 rounded-2xl shadow-lg shadow-blue-200">
<GearIcon size={24} className="text-white animate-spin-slow" />
</div>
<div>
<h2 className="text-xl font-black text-gray-900 uppercase tracking-tighter">Configurações da Agenda</h2>
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mt-0.5">Integrações e Comunicação</p>
</div>
</div>
<button onClick={onClose} className="p-2 hover:bg-gray-100 rounded-xl transition-colors text-gray-400">
<X size={24} />
</button>
</div>
{/* Progress Bar */}
<div className="h-1.5 w-full bg-gray-100 flex">
<div
className="h-full bg-blue-600 transition-all duration-500 ease-out shadow-[0_0_10px_rgba(37,99,235,0.5)]"
style={{ width: `${(page / totalPages) * 100}%` }}
></div>
</div>
{/* Content Area - Horizontal Slider Simulation */}
<div className="flex-1 relative overflow-hidden bg-white">
<div
className="absolute inset-0 flex transition-transform duration-500 ease-in-out transform-gpu"
style={{ transform: `translateX(-${(page - 1) * 100}%)` }}
>
{/* Page 1: Clinic Emails */}
<div className="w-full shrink-0 p-10 animate-in fade-in duration-700">
<div className="flex items-center gap-3 mb-8">
<Mail className="text-blue-600" size={24} />
<h3 className="text-sm font-black text-gray-800 uppercase tracking-tight">E-mails da Clínica</h3>
</div>
<div className="space-y-6">
<div className="space-y-2">
<label className="text-[10px] font-black text-gray-400 uppercase tracking-[0.2em] ml-1">E-mail para Notificações</label>
<input
type="email"
placeholder="EX: CONTATO@CLINICA.COM"
className="w-full bg-gray-50 border-2 border-transparent focus:border-blue-600 focus:bg-white rounded-2xl p-4 font-bold text-gray-800 outline-none transition-all shadow-sm"
value={config.clinicEmail}
onChange={(e) => setConfig({ ...config, clinicEmail: e.target.value.toUpperCase() })}
/>
<p className="text-[10px] text-gray-400 italic ml-1">* Este e-mail será usado para alertas de sistema e envio de comprovantes.</p>
</div>
</div>
</div>
{/* Page 2: Google Setup Tutorial & Central Config */}
<div className="w-full shrink-0 p-10 animate-in fade-in duration-700 overflow-y-auto custom-scrollbar">
<div className="flex justify-between items-center mb-6">
<div className="flex items-center gap-3">
<Calendar className="text-emerald-600" size={24} />
<h3 className="text-sm font-black text-gray-800 uppercase tracking-tight">Configuração Google Agenda</h3>
</div>
<a href="https://console.cloud.google.com/apis/library/calendar-json.googleapis.com" target="_blank" rel="noreferrer" className="text-[9px] font-black text-white bg-black px-3 py-1.5 rounded-lg hover:opacity-80 transition-all uppercase tracking-widest">
1. Ativar API
</a>
</div>
<div className="grid grid-cols-2 gap-6 mb-8">
<div className="space-y-4">
<div className="bg-emerald-50 rounded-2xl p-4 border border-emerald-100">
<div className="flex items-center gap-2 mb-2">
<div className="w-5 h-5 bg-emerald-600 text-white text-[10px] font-black flex items-center justify-center rounded-full">1</div>
<span className="text-[10px] font-black text-emerald-800 uppercase">Google API Key</span>
</div>
<input
type="password"
placeholder="COLE A CHAVE AQUI"
className="w-full bg-white border border-emerald-200 rounded-xl px-4 py-2 text-xs font-bold text-gray-700 outline-none focus:ring-2 focus:ring-emerald-100"
value={config.googleApiKey}
onChange={(e) => setConfig({ ...config, googleApiKey: e.target.value })}
/>
<a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noreferrer" className="text-[8px] font-black text-emerald-600 hover:underline mt-2 inline-block uppercase">Criar Credenciais </a>
</div>
</div>
<div className="space-y-4">
<div className="bg-blue-50 rounded-2xl p-4 border border-blue-100">
<div className="flex items-center gap-2 mb-2">
<div className="w-5 h-5 bg-blue-600 text-white text-[10px] font-black flex items-center justify-center rounded-full">2</div>
<span className="text-[10px] font-black text-blue-800 uppercase">ID Agenda Clínica</span>
</div>
<input
type="text"
placeholder="ID DA AGENDA"
className="w-full bg-white border border-blue-200 rounded-xl px-4 py-2 text-xs font-bold text-gray-700 outline-none focus:ring-2 focus:ring-blue-100"
value={config.clinicCalendarId}
onChange={(e) => setConfig({ ...config, clinicCalendarId: e.target.value })}
/>
<a href="https://calendar.google.com/calendar/r/settings" target="_blank" rel="noreferrer" className="text-[8px] font-black text-blue-600 hover:underline mt-2 inline-block uppercase">Pegar ID nas Configs </a>
</div>
</div>
</div>
<div className="bg-gray-900 rounded-3xl p-6 text-white">
<h4 className="text-[10px] font-black text-blue-400 uppercase tracking-[0.2em] mb-4">Guia de Integração</h4>
<ul className="space-y-3">
<li className="flex gap-3 text-xs">
<CheckCircle2 size={16} className="text-emerald-500 shrink-0" />
<span className="text-gray-300">Crie um projeto no <strong>Google Cloud Console</strong>.</span>
</li>
<li className="flex gap-3 text-xs">
<CheckCircle2 size={16} className="text-emerald-500 shrink-0" />
<span className="text-gray-300">No menu 'Biblioteca', ative a <strong>Google Calendar API</strong>.</span>
</li>
<li className="flex gap-3 text-xs">
<CheckCircle2 size={16} className="text-emerald-500 shrink-0" />
<div className="flex flex-col gap-1">
<span className="text-gray-300">Pegue o <strong>ID da Agenda</strong> nas configurações (Integrar agenda).</span>
<a href="https://calendar.google.com/calendar/r/settings" target="_blank" rel="noreferrer" className="text-[9px] font-black text-blue-400 hover:underline flex items-center gap-1 uppercase">
Abrir Configurações de Agenda
</a>
</div>
</li>
<li className="flex gap-3 text-xs">
<CheckCircle2 size={16} className="text-emerald-500 shrink-0" />
<span className="text-gray-300">No Google Agenda, torne a agenda <strong>Pública</strong> nas autorizações de acesso.</span>
</li>
</ul>
</div>
</div>
{/* Page 3: Google Calendar Mapping */}
<div className="w-full shrink-0 p-10 animate-in fade-in duration-700 overflow-hidden flex flex-col">
<div className="flex justify-between items-center mb-8">
<div className="flex items-center gap-3">
<Calendar className="text-emerald-600" size={24} />
<h3 className="text-sm font-black text-gray-800 uppercase tracking-tight">Agendas dos Dentistas</h3>
</div>
<a href="https://calendar.google.com/calendar/u/0/r/settings" target="_blank" rel="noreferrer" className="text-[9px] font-black text-white bg-gray-900 px-3 py-1.5 rounded-lg hover:bg-black transition-colors uppercase tracking-widest">
Abrir Google Calendar
</a>
</div>
<div className="space-y-4 flex-1 overflow-y-auto pr-4 custom-scrollbar">
<div className="bg-blue-50/50 p-4 rounded-2xl border border-blue-100 mb-2">
<p className="text-[10px] text-blue-800 font-bold mb-1 uppercase tracking-wider">Nova Integração Inteligente (OAuth2)</p>
<p className="text-[10px] text-blue-600 italic">Agora você pode conectar agendas sem precisar torná-las públicas ou usar API Keys.</p>
</div>
{/* ADMIN / CLINIC CONNECTION */}
<div className="space-y-3 mb-6">
<h4 className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-1">Sua Conta (Administrador)</h4>
<GoogleConnectButton
ownerId="admin"
isConnected={connectedAccounts.some(a => a.owner_id === 'admin' || a.owner_id.includes('@'))}
onStatusChange={fetchGoogleStatus}
/>
</div>
<div className="space-y-4">
<h4 className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-1">Contas dos Dentistas</h4>
{dentists?.length ? dentists.map((dentist) => {
const isConnected = connectedAccounts.some(a => a.owner_id === dentist.id);
return (
<div key={dentist.id} className="bg-gray-50 rounded-2xl p-4 border border-transparent hover:border-gray-200 transition-all">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className="w-3 h-3 rounded-full" style={{ backgroundColor: dentist.corAgenda }}></div>
<span className="text-xs font-black text-gray-800 uppercase">{dentist.nome}</span>
</div>
{isConnected ? (
<span className="text-[9px] font-black text-emerald-600 bg-emerald-100 px-2 py-1 rounded-lg uppercase">Conectado</span>
) : (
<span className="text-[9px] font-black text-gray-400 uppercase">Não Vinculado</span>
)}
</div>
<div className="flex gap-2">
<div className="flex-1">
<GoogleConnectButton
ownerId={dentist.id}
isConnected={isConnected}
onStatusChange={fetchGoogleStatus}
/>
</div>
{!isConnected && (
<button
onClick={() => copyInviteLink(dentist.id)}
className="p-4 bg-gray-100 text-gray-600 rounded-2xl hover:bg-gray-200 transition-all flex items-center justify-center group"
title="Gerar Link para o Dentista"
>
<Share2 size={18} className="group-hover:scale-110 transition-transform" />
</button>
)}
</div>
{/* Legacy ID support remains for backward compatibility */}
<div className="mt-3 pt-3 border-t border-gray-200/50">
<label className="text-[9px] font-bold text-gray-400 uppercase ml-1">ID Alternativo (API Key/Legado)</label>
<input
type="text"
placeholder="ID DA AGENDA GOOGLE (SE NÃO USAR OAUTH2)"
className="w-full bg-white/50 border border-gray-200 rounded-xl px-4 py-2 text-[10px] font-bold text-gray-500 outline-none focus:ring-1 focus:ring-blue-100 mt-1"
value={config.googleCalendarIds?.[dentist.id] || ''}
onChange={(e) => setConfig({
...config,
googleCalendarIds: {
...config.googleCalendarIds,
[dentist.id]: e.target.value
}
})}
/>
</div>
</div>
);
}) : (
<p className="text-center text-xs text-gray-400 py-10 uppercase font-bold">Nenhum dentista cadastrado.</p>
)}
</div>
</div>
</div>
</div>
</div>
{/* Footer Navigation */}
<div className="px-10 py-8 bg-gray-50/50 border-t border-gray-100 flex justify-between items-center">
<div className="flex gap-2">
{page > 1 && (
<button
onClick={() => setPage(page - 1)}
className="flex items-center gap-2 px-6 py-3 text-xs font-black text-gray-500 hover:text-gray-900 bg-white rounded-2xl border border-gray-200 hover:shadow-md transition-all uppercase tracking-widest"
>
<ChevronLeft size={16} /> Anterior
</button>
)}
</div>
<div className="flex gap-4">
{page < totalPages ? (
<button
onClick={() => setPage(page + 1)}
className="flex items-center gap-2 px-8 py-4 bg-blue-600 text-white rounded-[1.5rem] text-xs font-black uppercase tracking-widest hover:bg-blue-700 hover:shadow-xl hover:shadow-blue-200 transition-all shadow-lg shadow-blue-100"
>
Próximo <ChevronRight size={16} />
</button>
) : (
<button
onClick={handleSave}
disabled={isSaving}
className="flex items-center gap-2 px-10 py-4 bg-emerald-600 text-white rounded-[1.5rem] text-xs font-black uppercase tracking-widest hover:bg-emerald-700 hover:shadow-xl hover:shadow-emerald-200 transition-all shadow-lg shadow-emerald-100 disabled:bg-gray-400"
>
{isSaving ? 'Salvando...' : <><Save size={16} /> Salvar Tudo</>}
</button>
)}
</div>
</div>
</div>
<style>{`
.animate-spin-slow {
animation: spin 8s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.custom-scrollbar::-webkit-scrollbar {
width: 4px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
`}</style>
</div>
);
};
import React, { useState, useEffect } from 'react';
import { X, ChevronLeft, ChevronRight, Save, Mail, Calendar, Settings as GearIcon, CheckCircle2 } from 'lucide-react';
import { HybridBackend } from '../services/backend.ts';
import { Settings, Dentista } from '../types.ts';
import { useToast } from '../contexts/ToastContext.tsx';
import { GoogleConnectButton } from './GoogleConnectButton.tsx';
import { Link as LinkIcon, Copy, Share2 } from 'lucide-react';
interface AgendaSettingsModalProps {
isOpen: boolean;
onClose: () => void;
dentists: Dentista[] | null;
}
export const AgendaSettingsModal: React.FC<AgendaSettingsModalProps> = ({ isOpen, onClose, dentists }) => {
const [page, setPage] = useState(1);
const [config, setConfig] = useState<Settings>({
clinicEmail: '',
clinicCalendarId: '',
googleApiKey: '',
googleCalendarIds: {}
});
const [isSaving, setIsSaving] = useState(false);
const [connectedAccounts, setConnectedAccounts] = useState<any[]>([]);
const toast = useToast();
// Escopo: tudo (status Google, convites, settings) é da clínica/workspace ativa.
const clinicaId = HybridBackend.getActiveWorkspace()?.id;
const fetchGoogleStatus = async () => {
try {
const response = await fetch(`/api/auth/google/status${clinicaId ? `?clinicaId=${clinicaId}` : ''}`);
const data = await response.json();
setConnectedAccounts(Array.isArray(data) ? data : []);
} catch (error) {
console.error("Erro ao buscar status do Google:", error);
}
};
useEffect(() => {
if (isOpen) {
const loadSettings = async () => {
try {
const settings = await HybridBackend.getSettings();
setConfig({
clinicEmail: settings.clinicEmail || '',
clinicCalendarId: settings.clinicCalendarId || '',
googleApiKey: settings.googleApiKey || '',
googleCalendarIds: settings.googleCalendarIds || {}
});
} catch (error) {
console.error("Erro ao carregar configurações:", error);
}
};
loadSettings();
fetchGoogleStatus();
setPage(1); // Reset to first page
}
}, [isOpen]);
const copyInviteLink = (dentistId: string) => {
const url = `/api/auth/google/url?dentistId=${dentistId}${clinicaId ? `&clinicaId=${clinicaId}` : ''}`;
navigator.clipboard.writeText(url);
toast.success("LINK DE CONVITE COPIADO!");
};
const handleSave = async () => {
setIsSaving(true);
try {
await HybridBackend.saveSettings(config);
toast.success("CONFIGURAÇÕES SALVAS COM SUCESSO!");
onClose();
} catch (error) {
toast.error("ERRO AO SALVAR CONFIGURAÇÕES.");
} finally {
setIsSaving(false);
}
};
if (!isOpen) return null;
const totalPages = 2;
// Conta admin/clínica conectada NESTA clínica: usa o owner_id real (email) p/ o
// disconnect funcionar; 'admin' fica só no connect (o backend resolve o email).
const adminAccount = connectedAccounts.find(a => a.owner_id && a.owner_id.includes('@'));
return (
<div className="fixed inset-0 bg-black/60 z-[60] flex items-center justify-center backdrop-blur-md p-0">
<div className="bg-white shadow-2xl w-full h-dvh md:w-[96vw] md:h-[98vh] rounded-none md:rounded-[2.5rem] flex flex-col overflow-hidden border border-gray-100 animate-in fade-in zoom-in-95 duration-300">
{/* Header */}
<div className="px-10 py-8 border-b border-gray-50 flex justify-between items-center bg-gradient-to-br from-gray-50 to-white">
<div className="flex items-center gap-4">
<div className="p-3 bg-blue-600 rounded-2xl shadow-lg shadow-blue-200">
<GearIcon size={24} className="text-white animate-spin-slow" />
</div>
<div>
<h2 className="text-xl font-black text-gray-900 uppercase tracking-tighter">Configurações da Agenda</h2>
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mt-0.5">Integrações e Comunicação</p>
</div>
</div>
<button onClick={onClose} className="p-2 hover:bg-gray-100 rounded-xl transition-colors text-gray-400">
<X size={24} />
</button>
</div>
{/* Progress Bar */}
<div className="h-1.5 w-full bg-gray-100 flex">
<div
className="h-full bg-blue-600 transition-all duration-500 ease-out shadow-[0_0_10px_rgba(37,99,235,0.5)]"
style={{ width: `${(page / totalPages) * 100}%` }}
></div>
</div>
{/* Content Area - Horizontal Slider Simulation */}
<div className="flex-1 relative overflow-hidden bg-white">
<div
className="absolute inset-0 flex transition-transform duration-500 ease-in-out transform-gpu"
style={{ transform: `translateX(-${(page - 1) * 100}%)` }}
>
{/* Page 1: Clinic Emails */}
<div className="w-full shrink-0 p-10 animate-in fade-in duration-700">
<div className="flex items-center gap-3 mb-8">
<Mail className="text-blue-600" size={24} />
<h3 className="text-sm font-black text-gray-800 uppercase tracking-tight">E-mails da Clínica</h3>
</div>
<div className="space-y-6">
<div className="space-y-2">
<label className="text-[10px] font-black text-gray-400 uppercase tracking-[0.2em] ml-1">E-mail para Notificações</label>
<input
type="email"
placeholder="EX: CONTATO@CLINICA.COM"
className="w-full bg-gray-50 border-2 border-transparent focus:border-blue-600 focus:bg-white rounded-2xl p-4 font-bold text-gray-800 outline-none transition-all shadow-sm"
value={config.clinicEmail}
onChange={(e) => setConfig({ ...config, clinicEmail: e.target.value.toUpperCase() })}
/>
<p className="text-[10px] text-gray-400 italic ml-1">* Este e-mail será usado para alertas de sistema e envio de comprovantes.</p>
</div>
</div>
</div>
{/* Page 3: Google Calendar Mapping */}
<div className="w-full shrink-0 p-10 animate-in fade-in duration-700 overflow-hidden flex flex-col">
<div className="flex justify-between items-center mb-8">
<div className="flex items-center gap-3">
<Calendar className="text-emerald-600" size={24} />
<h3 className="text-sm font-black text-gray-800 uppercase tracking-tight">Agendas dos Dentistas</h3>
</div>
<a href="https://calendar.google.com/calendar/u/0/r/settings" target="_blank" rel="noreferrer" className="text-[9px] font-black text-white bg-gray-900 px-3 py-1.5 rounded-lg hover:bg-black transition-colors uppercase tracking-widest">
Abrir Google Calendar
</a>
</div>
<div className="space-y-4 flex-1 overflow-y-auto pr-4 custom-scrollbar">
<div className="bg-blue-50/50 p-4 rounded-2xl border border-blue-100 mb-2">
<p className="text-[10px] text-blue-800 font-bold mb-1 uppercase tracking-wider">Conectar Google Agenda</p>
<p className="text-[10px] text-blue-600 italic">É clicar e autorizar com a conta Google. Você NÃO precisa criar projeto, ativar API nem colar nenhum ID a integração está pronta.</p>
<p className="text-[10px] text-blue-700 font-bold mt-1.5">Os agendamentos são copiados para o Google (backup) e o paciente recebe convite + lembretes (1 dia e 1h antes). Se estava conectado, <u>reconecte uma vez</u> para liberar a escrita.</p>
</div>
{/* ADMIN / CLINIC CONNECTION */}
<div className="space-y-3 mb-6">
<h4 className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-1">Sua Conta (Administrador)</h4>
<GoogleConnectButton
ownerId={adminAccount?.owner_id || 'admin'}
clinicaId={clinicaId}
isConnected={!!adminAccount}
onStatusChange={fetchGoogleStatus}
/>
</div>
<div className="space-y-4">
<h4 className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-1">Contas dos Dentistas</h4>
{dentists?.length ? dentists.map((dentist) => {
const isConnected = connectedAccounts.some(a => a.owner_id === dentist.id);
return (
<div key={dentist.id} className="bg-gray-50 rounded-2xl p-4 border border-transparent hover:border-gray-200 transition-all">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className="w-3 h-3 rounded-full" style={{ backgroundColor: dentist.corAgenda }}></div>
<span className="text-xs font-black text-gray-800 uppercase">{dentist.nome}</span>
</div>
{isConnected ? (
<span className="text-[9px] font-black text-emerald-600 bg-emerald-100 px-2 py-1 rounded-lg uppercase">Conectado</span>
) : (
<span className="text-[9px] font-black text-gray-400 uppercase">Não Vinculado</span>
)}
</div>
<div className="flex gap-2">
<div className="flex-1">
<GoogleConnectButton
ownerId={dentist.id}
clinicaId={clinicaId}
isConnected={isConnected}
onStatusChange={fetchGoogleStatus}
/>
</div>
{!isConnected && (
<button
onClick={() => copyInviteLink(dentist.id)}
className="p-4 bg-gray-100 text-gray-600 rounded-2xl hover:bg-gray-200 transition-all flex items-center justify-center group"
title="Gerar Link para o Dentista"
>
<Share2 size={18} className="group-hover:scale-110 transition-transform" />
</button>
)}
</div>
</div>
);
}) : (
<p className="text-center text-xs text-gray-400 py-10 uppercase font-bold">Nenhum dentista cadastrado.</p>
)}
</div>
</div>
</div>
</div>
</div>
{/* Footer Navigation */}
<div className="px-10 py-8 bg-gray-50/50 border-t border-gray-100 flex justify-between items-center">
<div className="flex gap-2">
{page > 1 && (
<button
onClick={() => setPage(page - 1)}
className="flex items-center gap-2 px-6 py-3 text-xs font-black text-gray-500 hover:text-gray-900 bg-white rounded-2xl border border-gray-200 hover:shadow-md transition-all uppercase tracking-widest"
>
<ChevronLeft size={16} /> Anterior
</button>
)}
</div>
<div className="flex gap-4">
{page < totalPages ? (
<button
onClick={() => setPage(page + 1)}
className="flex items-center gap-2 px-8 py-4 bg-blue-600 text-white rounded-[1.5rem] text-xs font-black uppercase tracking-widest hover:bg-blue-700 hover:shadow-xl hover:shadow-blue-200 transition-all shadow-lg shadow-blue-100"
>
Próximo <ChevronRight size={16} />
</button>
) : (
<button
onClick={handleSave}
disabled={isSaving}
className="flex items-center gap-2 px-10 py-4 bg-emerald-600 text-white rounded-[1.5rem] text-xs font-black uppercase tracking-widest hover:bg-emerald-700 hover:shadow-xl hover:shadow-emerald-200 transition-all shadow-lg shadow-emerald-100 disabled:bg-gray-400"
>
{isSaving ? 'Salvando...' : <><Save size={16} /> Salvar Tudo</>}
</button>
)}
</div>
</div>
</div>
<style>{`
.animate-spin-slow {
animation: spin 8s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.custom-scrollbar::-webkit-scrollbar {
width: 4px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
`}</style>
</div>
);
};