// Componentes de UI compartilhados const { useState, useEffect, useRef, useMemo, useCallback } = React; // ----- Icon set (inline SVG, small set) ----- function Icon({ name, size = 18, color = 'currentColor', strokeWidth = 2 }) { const props = { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: color, strokeWidth, strokeLinecap: 'round', strokeLinejoin: 'round' }; const paths = { home: <>, building: <>, plus: <>, search: <>, filter: <>, download: <>, arrowLeft: <>, arrowUp: <>, arrowDown: <>, check: <>, x: <>, edit: <>, trash: <>, more: <>, chart: <>, pie: <>, file: <>, bank: <>, wallet: <>, lock: <>, target: <>, calendar: ( <> ), list: <>, receipt: <>, eye: <>, chevronDown: <>, chevronUp: <>, chevronRight: <>, alert: <>, settings: <>, user: <>, menu: <>, logout: <>, upload: <>, creditCard: <>, }; return {paths[name] || null}; } // ----- Buttons ----- function Btn({ variant = 'primary', size = 'md', icon, children, onClick, disabled, type = 'button', style = {}, title }) { const pad = size === 'sm' ? '6px 10px' : size === 'lg' ? '12px 20px' : '8px 14px'; const fs = size === 'sm' ? 13 : size === 'lg' ? 15 : 14; const variants = { primary: { background: 'var(--c-primary)', color: '#fff', border: '1px solid var(--c-primary)' }, secondary: { background: 'var(--c-surface)', color: 'var(--c-text)', border: '1px solid var(--c-border)' }, ghost: { background: 'transparent', color: 'var(--c-text-muted)', border: '1px solid transparent' }, danger: { background: 'var(--c-surface)', color: '#dc2626', border: '1px solid var(--c-red-bg)' }, success: { background: '#16a34a', color: '#fff', border: '1px solid #16a34a' }, dark: { background: 'var(--c-text)', color: '#fff', border: '1px solid var(--c-text)' }, }; return ( ); } // ----- Badge ----- function Badge({ status, children, dot = true }) { const c = statusColor(status) || { bg: 'var(--c-bg)', fg: 'var(--c-text-muted)', dot: '#94a3b8', label: status }; return ( {dot && } {children || c.label} ); } // ----- Card ----- function Card({ children, style = {}, padding = 20, onClick }) { return (
{children}
); } // ----- KPI Card ----- function KPI({ label, value, delta, deltaLabel, icon, color = 'var(--c-text)', sub }) { return (
{label} {icon &&
}
{value}
{(delta != null || sub) && (
{delta != null && ( = 0 ? '#16a34a' : '#dc2626', fontWeight: 600, display: 'inline-flex', alignItems: 'center', gap: 2 }}> = 0 ? 'arrowUp' : 'arrowDown'} size={12} /> {Math.abs(delta).toFixed(1)}% )} {sub || deltaLabel}
)}
); } // ----- Modal ----- function Modal({ open, onClose, title, children, width = 560, footer, disableBackdropClick = false }) { const isMobile = useIsMobile(); useEffect(() => { function esc(e) { if (e.key === 'Escape' && open) onClose(); } document.addEventListener('keydown', esc); return () => document.removeEventListener('keydown', esc); }, [open, onClose]); if (!open) return null; return (
e.stopPropagation()} style={{ background: 'var(--c-surface)', borderRadius: 12, width: isMobile ? '95vw' : width || 520, maxWidth: '95vw', maxHeight: '90vh', margin: isMobile ? '20px auto' : 'auto', display: 'flex', flexDirection: 'column', boxShadow: '0 20px 60px rgba(0,0,0,.2)' }}>
{title}
{children}
{footer &&
{footer}
}
); } // ----- Form fields ----- function Field({ label, children, hint, required, span = 1 }) { return (
{children} {hint && {hint}}
); } const inputStyle = { width: '100%', padding: '8px 12px', border: '1.5px solid var(--c-border)', borderRadius: 8, fontSize: 13, background: 'var(--c-surface)', color: 'var(--c-text)', fontFamily: 'inherit', outline: 'none', transition: 'border-color 0.15s, box-shadow 0.15s' }; function Input(props) { const { style, ...rest } = props; return ( { e.target.style.borderColor = 'var(--c-primary)'; e.target.style.boxShadow = '0 0 0 3px var(--c-primary-soft)'; }} onBlur={e => { e.target.style.borderColor = 'var(--c-border)'; e.target.style.boxShadow = 'none'; }} {...rest} /> ); } function DateInput({ value, onChange, placeholder, style = {} }) { return (
{ e.target.style.borderColor = 'var(--c-primary)' e.target.style.boxShadow = '0 0 0 3px var(--c-primary-soft)' }} onBlur={e => { e.target.style.borderColor = 'var(--c-border)' e.target.style.boxShadow = 'none' }} />
) } function CustomSelect({ value, onChange, options, placeholder = 'Selecionar', style = {}, disabled = false, size = 'md' }) { const { useState, useEffect, useRef } = React; const opts = options.map(o => typeof o === 'string' ? { value: o, label: o } : o); const [open, setOpen] = useState(false); const ref = useRef(null); const selected = opts.find(o => o.value === value); const heights = { sm: 32, md: 38, lg: 44 }; const fontSizes = { sm: 12, md: 13, lg: 14 }; useEffect(() => { function onClick(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false); } document.addEventListener('mousedown', onClick); return () => document.removeEventListener('mousedown', onClick); }, []); function select(val) { if (onChange) onChange({ target: { value: val } }); setOpen(false); } return (
{open && (
{opts.map((o, i) => ( ))}
)}
); } function Textarea(props) { const { style, ...rest } = props; return (