diff --git a/frontend/components/HorariosConfig.tsx b/frontend/components/HorariosConfig.tsx index 4539afc..b608bb7 100644 --- a/frontend/components/HorariosConfig.tsx +++ b/frontend/components/HorariosConfig.tsx @@ -3,6 +3,7 @@ // ownership: horário/feriado da clínica = dono; horário do dentista = ele ou dono). import React, { useState, useEffect, useCallback } from 'react'; import { X, Clock, CalendarOff, Plus, Trash2, Save, Loader2, User, Copy, AlertTriangle } from 'lucide-react'; +import { SegmentedTabs } from './SegmentedTabs.tsx'; const API = (import.meta as any).env?.VITE_API_URL || '/api'; const BASE = `${API}/nw/agenda-config`; @@ -204,13 +205,10 @@ export const HorariosConfig: React.FC<{ isOpen: boolean; onClose: () => void; cl {/* Tabs */} -
- {(soDentista ? (['dentistas'] as const) : (['clinica', 'dentistas', 'situacoes'] as const)).map((t) => ( - - ))} +
+ ({ id: t, label: t === 'clinica' ? 'Clínica' : t === 'dentistas' ? 'Dentistas' : 'Situações' }))} + value={aba} onChange={setAba} />
{(erro || ok) && ( diff --git a/frontend/components/SegmentedTabs.tsx b/frontend/components/SegmentedTabs.tsx new file mode 100644 index 0000000..eb2387a --- /dev/null +++ b/frontend/components/SegmentedTabs.tsx @@ -0,0 +1,46 @@ +// SegmentedTabs — controle segmentado (abas em cápsula) PADRÃO do app. +// Grupo de abas mutuamente exclusivas num trilho cinza; a ativa vira um cartão +// branco com a cor de destaque. Responsivo: no mobile ocupa a largura toda e +// distribui igual; no desktop encolhe ao conteúdo. Referência: /minhas-salas. +import React from 'react'; + +export interface SegTab { + id: T; + label: string; + icon?: React.ElementType; + badge?: number; +} + +interface SegmentedTabsProps { + tabs: SegTab[]; + value: T; + onChange: (id: T) => void; + /** Cor de destaque da aba ativa (texto + badge). Padrão: teal do app. */ + accent?: string; + className?: string; +} + +export function SegmentedTabs({ tabs, value, onChange, accent = '#0d9488', className = '' }: SegmentedTabsProps) { + return ( +
+ {tabs.map((t) => { + const active = value === t.id; + const Icon = t.icon; + return ( + + ); + })} +
+ ); +} diff --git a/frontend/views/ComissoesView.tsx b/frontend/views/ComissoesView.tsx index 112e2e9..92da9ca 100644 --- a/frontend/views/ComissoesView.tsx +++ b/frontend/views/ComissoesView.tsx @@ -4,6 +4,7 @@ import { HybridBackend } from '../services/backend.ts'; import { useToast } from '../contexts/ToastContext.tsx'; import { useConfirm } from '../contexts/ConfirmContext.tsx'; import { PageHeader } from '../components/PageHeader.tsx'; +import { SegmentedTabs } from '../components/SegmentedTabs.tsx'; const COLOR = '#0d9488'; const fmtBRL = (v: number) => 'R$ ' + Number(v || 0).toLocaleString('pt-BR', { minimumFractionDigits: 2 }); @@ -22,14 +23,9 @@ export const ComissoesView: React.FC = () => { return (
-
- {([['relatorio', 'Relatório / Fechamento', Wallet], ['regras', 'Regras de %', Percent], ['historico', 'Histórico', History]] as const).map(([id, label, Icon]) => ( - - ))} -
+ {aba === 'relatorio' && } {aba === 'regras' && } diff --git a/frontend/views/ContratosView.tsx b/frontend/views/ContratosView.tsx index 6e0928c..ae98542 100644 --- a/frontend/views/ContratosView.tsx +++ b/frontend/views/ContratosView.tsx @@ -7,6 +7,7 @@ import { HybridBackend } from '../services/backend.ts'; import { useToast } from '../contexts/ToastContext.tsx'; import { useConfirm } from '../contexts/ConfirmContext.tsx'; import { PageHeader } from '../components/PageHeader.tsx'; +import { SegmentedTabs } from '../components/SegmentedTabs.tsx'; const COLOR = '#0d9488'; // teal @@ -473,13 +474,7 @@ export const ContratosView: React.FC = () => { : }
-
- {([['contratos', 'Contratos', FileSignature], ['modelos', 'Modelos', Library]] as const).map(([id, label, Icon]) => ( - - ))} -
+ {loading ?
: ( <> diff --git a/frontend/views/GlosasView.tsx b/frontend/views/GlosasView.tsx index a4f32e8..3515c6f 100644 --- a/frontend/views/GlosasView.tsx +++ b/frontend/views/GlosasView.tsx @@ -4,6 +4,7 @@ import { HybridBackend } from '../services/backend.ts'; import { useToast } from '../contexts/ToastContext.tsx'; import { useConfirm } from '../contexts/ConfirmContext.tsx'; import { PageHeader } from '../components/PageHeader.tsx'; +import { SegmentedTabs } from '../components/SegmentedTabs.tsx'; const fmtBRL = (v: number) => 'R$ ' + Number(v || 0).toLocaleString('pt-BR', { minimumFractionDigits: 2 }); const dataBR = (d: string) => (d || '').slice(0, 10).split('-').reverse().join('/'); @@ -137,11 +138,7 @@ export const GlosasView: React.FC = () => { return (
-
- {([['registrar', 'Recebíveis / Marcar glosa'], ['glosas', `Glosas (${glosas.length})`]] as const).map(([id, label]) => ( - - ))} -
+ {loading ?
: aba === 'registrar' ? (
diff --git a/frontend/views/LabClientesView.tsx b/frontend/views/LabClientesView.tsx index 92cb5ca..cab6142 100644 --- a/frontend/views/LabClientesView.tsx +++ b/frontend/views/LabClientesView.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react'; import { Building2, Wallet, Clock, RefreshCw, Inbox, CheckCircle2, Phone, CalendarDays, Stethoscope, Briefcase, User } from 'lucide-react'; import { HybridBackend } from '../services/backend.ts'; import { PageHeader } from '../components/PageHeader.tsx'; +import { SegmentedTabs } from '../components/SegmentedTabs.tsx'; const fmtBRL = (v: number) => 'R$ ' + Number(v || 0).toLocaleString('pt-BR', { minimumFractionDigits: 2 }); const dataBR = (d: string) => (d || '').slice(0, 10).split('-').reverse().join('/'); @@ -55,11 +56,7 @@ export const LabClientesView: React.FC<{ tab?: 'clientes' | 'financeiro' }> = ({ -
- {(['clientes', 'financeiro'] as const).map(t => ( - - ))} -
+ {loading ?
: ( <> diff --git a/frontend/views/ProteseView.tsx b/frontend/views/ProteseView.tsx index b403fde..6225361 100644 --- a/frontend/views/ProteseView.tsx +++ b/frontend/views/ProteseView.tsx @@ -4,6 +4,7 @@ import { HybridBackend } from '../services/backend.ts'; import { useToast } from '../contexts/ToastContext.tsx'; import { useConfirm } from '../contexts/ConfirmContext.tsx'; import { PageHeader } from '../components/PageHeader.tsx'; +import { SegmentedTabs } from '../components/SegmentedTabs.tsx'; import { rotulosPaciente } from '../utils/pacienteLabel.ts'; import { ProteticoPicker } from '../components/ProteticoPicker.tsx'; import { SearchSelect } from '../components/SearchSelect.tsx'; @@ -318,11 +319,8 @@ const OSDetailModal: React.FC<{ id: string; modo: 'bancada' | 'clinica'; onClose
{/* Abas */} -
- {TABS.map(t => ( - - ))} +
+ ({ id: t.id, label: t.label }))} value={tab} onChange={setTab} accent="#7c3aed" />
@@ -1037,10 +1035,8 @@ const CotacoesClinicaModal: React.FC<{ onClose: () => void; onEscolhida: () => v

Cotações de prótese

-
- {(['lista', 'novo'] as const).map(t => ( - - ))} +
+
{aba === 'novo' ? ( diff --git a/frontend/views/plugins/ProfissionaisPlugin.tsx b/frontend/views/plugins/ProfissionaisPlugin.tsx index 2e38397..2e32fcb 100644 --- a/frontend/views/plugins/ProfissionaisPlugin.tsx +++ b/frontend/views/plugins/ProfissionaisPlugin.tsx @@ -147,6 +147,7 @@ const VitrineProtetico: React.FC<{ proteticoId: string }> = ({ proteticoId }) => ); }; import { PageHeader } from '../../components/PageHeader.tsx'; +import { SegmentedTabs } from '../../components/SegmentedTabs.tsx'; import { useToast } from '../../contexts/ToastContext.tsx'; import { useConfirm } from '../../contexts/ConfirmContext.tsx'; import { HybridBackend } from '../../services/backend.ts'; @@ -705,22 +706,8 @@ export const ProfissionaisPlugin: React.FC = () => { /> {/* Tabs */} -
- {TABS.filter(t => t.show).map(t => { - const Icon = t.icon; - const active = tab === t.id; - return ( - - ); - })} -
+ t.show).map(t => ({ id: t.id, label: t.label, icon: t.icon, badge: t.id === 'propostas' && pendentesRecebidas > 0 ? pendentesRecebidas : undefined }))} /> {/* BUSCAR — bloqueia se faltar endereço/CEP da origem */} {tab === 'buscar' && enderecoOk === false && (