import React, { useState, useEffect, useCallback } from 'react'; import { DoorOpen, MapPin, Search, RefreshCw, Plus, Pencil, Trash2, X, Save, CalendarClock, CheckCircle2, XCircle, Clock, Building2, Briefcase, Home, Inbox, Send } from 'lucide-react'; import { PageHeader } from '../../components/PageHeader.tsx'; import { useToast } from '../../contexts/ToastContext.tsx'; import { useConfirm } from '../../contexts/ConfirmContext.tsx'; import { HybridBackend } from '../../services/backend.ts'; import { buscarCep } from '../../services/viacep.ts'; const API_URL = (window as any).__API_URL__ || ''; const UF_LIST = ['', 'AC', 'AL', 'AM', 'AP', 'BA', 'CE', 'DF', 'ES', 'GO', 'MA', 'MG', 'MS', 'MT', 'PA', 'PB', 'PE', 'PI', 'PR', 'RJ', 'RN', 'RO', 'RR', 'RS', 'SC', 'SE', 'SP', 'TO']; const COLOR = '#0d9488'; // teal — identidade do plugin const apiFetch = (path: string, opts: RequestInit = {}) => fetch(`${API_URL}${path}`, { ...opts, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${localStorage.getItem('SCOREODONTO_AUTH_TOKEN')}`, ...(opts.headers || {}), }, }); // ─── Types ─────────────────────────────────────────────────────────────────── interface Sala { id: string; nome: string; tipo: 'clinica' | 'consultorio' | 'independente'; descricao: string | null; equipamentos: string | null; endereco: string | null; bairro: string | null; cidade: string; estado: string; cep: string | null; valor_hora: string | null; valor_turno: string | null; valor_diaria: string | null; valor_semanal: string | null; valor_mensal: string | null; hora_consulta?: boolean; turno_consulta?: boolean; diaria_consulta?: boolean; semanal_consulta?: boolean; mensal_consulta?: boolean; area?: string; disponivel?: boolean; owner_nome?: string; clinica_id?: string | null; clinica_nome?: string | null; reservas_pendentes?: number; dist_km?: number | string | null; } interface Reserva { id: string; sala_id: string; sala_nome?: string; solicitante_nome?: string; owner_nome?: string; cidade?: string; estado?: string; inicio: string; fim: string; modelo: string; valor?: number | string | null; status: 'pendente' | 'confirmada' | 'recusada' | 'cancelada'; observacoes?: string | null; clinica_nome?: string | null; profissional_nome?: string | null; } const TIPO_META: Record = { clinica: { label: 'Sala Clínica', icon: Building2 }, consultorio: { label: 'Sala Consultório', icon: Briefcase }, independente: { label: 'Sala Independente', icon: Home }, }; const AREA_META: Record = { odontologia: { nome: 'Odontologia', cor: '#2563eb' }, biomedicina: { nome: 'Biomedicina', cor: '#db2777' }, protese: { nome: 'Prótese', cor: '#7c3aed' }, }; const STATUS_STYLE: Record = { pendente: 'bg-amber-100 text-amber-700', confirmada: 'bg-emerald-100 text-emerald-700', recusada: 'bg-red-100 text-red-600', cancelada: 'bg-gray-100 text-gray-500', }; const fmtValor = (v: string | null) => (v == null || v === '' ? null : Number(v).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' })); const fmtData = (iso: string) => new Date(iso).toLocaleString('pt-BR', { day: '2-digit', month: '2-digit', year: '2-digit', hour: '2-digit', minute: '2-digit' }); const precos = (s: Sala): Array<[string, string]> => { const mods: Array<[string, string | null, boolean | undefined]> = [ ['HORA', s.valor_hora, s.hora_consulta], ['TURNO', s.valor_turno, s.turno_consulta], ['DIÁRIA', s.valor_diaria, s.diaria_consulta], ['SEMANA', s.valor_semanal, s.semanal_consulta], ['MÊS', s.valor_mensal, s.mensal_consulta], ]; return mods .filter(([, v, c]) => fmtValor(v as string | null) || c) .map(([l, v]) => [l, fmtValor(v as string | null) || 'Sob consulta'] as [string, string]); }; const inputCls = 'w-full px-3 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-xs font-bold text-gray-700 outline-none focus:border-teal-400'; const labelCls = 'text-[10px] font-black text-gray-400 uppercase tracking-widest block mb-1'; // ─── Modal: cadastrar / editar sala ────────────────────────────────────────── const SalaFormModal: React.FC<{ sala: Sala | null; onClose: () => void; onSaved: () => void }> = ({ sala, onClose, onSaved }) => { const toast = useToast(); const ws = HybridBackend.getActiveWorkspace(); const [saving, setSaving] = useState(false); const [vincularUnidade, setVincularUnidade] = useState(sala ? !!sala.clinica_id : !!ws?.id); const [areas, setAreas] = useState<{ slug: string; nome: string }[]>([]); useEffect(() => { HybridBackend.getAreas().then(setAreas); }, []); const [f, setF] = useState({ nome: sala?.nome || '', tipo: sala?.tipo || 'independente', descricao: sala?.descricao || '', equipamentos: sala?.equipamentos || '', endereco: sala?.endereco || '', bairro: sala?.bairro || '', cidade: sala?.cidade || '', estado: sala?.estado || '', cep: sala?.cep || '', area: sala?.area || '', valorHora: sala?.valor_hora || '', valorTurno: sala?.valor_turno || '', valorDiaria: sala?.valor_diaria || '', valorSemanal: sala?.valor_semanal || '', valorMensal: sala?.valor_mensal || '', horaConsulta: sala?.hora_consulta === true, turnoConsulta: sala?.turno_consulta === true, diariaConsulta: sala?.diaria_consulta === true, semanalConsulta: sala?.semanal_consulta === true, mensalConsulta: sala?.mensal_consulta === true, disponivel: sala?.disponivel !== false, }); const set = (k: string, v: any) => setF(p => ({ ...p, [k]: v })); const handleSave = async () => { if (!f.nome || !f.cidade || !f.estado) { toast.error('NOME, CIDADE E ESTADO SÃO OBRIGATÓRIOS.'); return; } setSaving(true); try { const body = JSON.stringify({ ...f, clinicaId: vincularUnidade && ws?.id ? ws.id : null, valorHora: f.valorHora === '' ? null : +f.valorHora, valorTurno: f.valorTurno === '' ? null : +f.valorTurno, valorDiaria: f.valorDiaria === '' ? null : +f.valorDiaria, valorSemanal: f.valorSemanal === '' ? null : +f.valorSemanal, valorMensal: f.valorMensal === '' ? null : +f.valorMensal, }); const res = sala ? await apiFetch(`/api/salas/${sala.id}`, { method: 'PUT', body }) : await apiFetch('/api/salas', { method: 'POST', body }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Erro ao salvar.'); toast.success(sala ? 'SALA ATUALIZADA!' : 'SALA CADASTRADA!'); // Sala é workspace próprio: atualiza o seletor. Se for a 1ª (sem workspace ativo), recarrega p/ assumir contexto. const semWs = !HybridBackend.getActiveWorkspace(); await HybridBackend.refreshWorkspaces(); if (!sala && semWs) { window.location.reload(); return; } onSaved(); onClose(); } catch (e: any) { toast.error(e.message.toUpperCase()); } finally { setSaving(false); } }; return (

{sala ? 'EDITAR SALA' : 'CADASTRAR SALA'}

set('nome', e.target.value)} placeholder="EX: SALA 2 — EQUIPADA P/ HOF" />