// GuinchoFlow — Shared Components // ── Icons (inline SVG via lucide-style paths) ────────────────────────────── const Icon = ({ name, size = 20, color = 'currentColor', className = '' }) => { const paths = { home: 'M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z M9 22V12h6v10', truck: 'M1 3h15v13H1zM16 8h4l3 3v5h-7V8z M5.5 21a1.5 1.5 0 100-3 1.5 1.5 0 000 3zM18.5 21a1.5 1.5 0 100-3 1.5 1.5 0 000 3z', alert: 'M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z M12 9v4 M12 17h.01', search: 'M11 19a8 8 0 100-16 8 8 0 000 16z M21 21l-4.35-4.35', calendar: 'M3 4h18v18H3z M16 2v4 M8 2v4 M3 10h18', check: 'M22 11.08V12a10 10 0 11-5.93-9.14 M22 4L12 14.01l-3-3', x: 'M18 6L6 18 M6 6l12 12', bell: 'M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9 M13.73 21a2 2 0 01-3.46 0', user: 'M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2 M12 11a4 4 0 100-8 4 4 0 000 8z', users: 'M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2 M23 21v-2a4 4 0 00-3-3.87 M16 3.13a4 4 0 010 7.75', dollar: 'M12 1v22 M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6', chart: 'M18 20V10 M12 20V4 M6 20v-6', settings: 'M12 15a3 3 0 100-6 3 3 0 000 6z M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z', eye: 'M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z M12 12a3 3 0 100-6 3 3 0 000 6', edit: 'M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7 M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z', trash: 'M3 6h18 M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6 M10 11v6 M14 11v6 M9 6V4a1 1 0 011-1h4a1 1 0 011 1v2', plus: 'M12 5v14 M5 12h14', menu: 'M3 12h18 M3 6h18 M3 18h18', close: 'M18 6L6 18 M6 6l12 12', chevronRight: 'M9 18l6-6-6-6', chevronDown: 'M6 9l6 6 6-6', logout: 'M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4 M16 17l5-5-5-5 M21 12H9', clipboard: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2 M9 5a2 2 0 002 2h2a2 2 0 002-2 M9 5a2 2 0 012-2h2a2 2 0 012 2', map: 'M1 6v16l7-4 8 4 7-4V2l-7 4-8-4-7 4 M8 2v16 M16 6v16', camera: 'M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012 2z M12 17a4 4 0 100-8 4 4 0 000 8z', fileText: 'M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z M14 2v6h6 M16 13H8 M16 17H8 M10 9H8', wrench: 'M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z', trendUp: 'M23 6l-9.5 9.5-5-5L1 18 M17 6h6v6', trendDown: 'M23 18l-9.5-9.5-5 5L1 6 M17 18h6v-6', building: 'M6 2h12a2 2 0 012 2v18H4V4a2 2 0 012-2z M9 22V12h6v10 M9 6h.01 M15 6h.01 M9 10h.01 M15 10h.01', download: 'M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4 M7 10l5 5 5-5 M12 15V3', shield: 'M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z', key: 'M21 2l-2 2m-7.61 7.61a5.5 5.5 0 11-7.778 7.778 5.5 5.5 0 017.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4', battery: 'M1 9h18v6H1z M23 13v-2', zap: 'M13 2L3 14h9l-1 8 10-12h-9l1-8z', phone: 'M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.67A2 2 0 014.11 2h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 16.92z', mail: 'M4 4h16a2 2 0 012 2v12a2 2 0 01-2 2H4a2 2 0 01-2-2V6a2 2 0 012-2z M22 6l-10 7L2 6', }; const d = paths[name] || paths.alert; return ( {d.split(' M').map((seg, i) => ( ))} ); }; // ── Status Badge ────────────────────────────────────────────────────────── const StatusBadge = ({ status }) => { const map = { aberta: { label: 'Aberta', bg: 'rgba(59,130,246,0.12)', color: '#2563EB' }, atendimento: { label: 'Em Atendimento', bg: 'rgba(245,158,11,0.15)', color: '#B45309' }, agendada: { label: 'Agendada', bg: 'rgba(139,92,246,0.12)', color: '#7C3AED' }, finalizada: { label: 'Finalizada', bg: 'rgba(16,185,129,0.12)', color: '#047857' }, cancelada: { label: 'Cancelada', bg: 'rgba(239,68,68,0.12)', color: '#DC2626' }, disponivel: { label: 'Disponível', bg: 'rgba(16,185,129,0.12)', color: '#047857' }, ativo: { label: 'Ativo', bg: 'rgba(16,185,129,0.12)', color: '#047857' }, inativo: { label: 'Inativo', bg: 'rgba(148,163,184,0.15)',color: '#64748B' }, manutencao: { label: 'Manutenção', bg: 'rgba(245,158,11,0.15)', color: '#B45309' }, entrada: { label: 'Entrada', bg: 'rgba(16,185,129,0.12)', color: '#047857' }, saida: { label: 'Saída', bg: 'rgba(239,68,68,0.12)', color: '#DC2626' }, }; const s = map[status] || map.inativo; return ( {s.label} ); }; // ── Avatar ──────────────────────────────────────────────────────────────── const AVATAR_COLORS = ['#3B82F6','#8B5CF6','#F59E0B','#10B981','#EF4444','#EC4899','#14B8A6']; const Avatar = ({ initials, size = 32 }) => { const idx = (initials.charCodeAt(0) + (initials.charCodeAt(1) || 0)) % AVATAR_COLORS.length; return ( {initials} ); }; // ── Card ────────────────────────────────────────────────────────────────── const Card = ({ children, style = {}, className = '', ...rest }) => (
{children}
); // ── Modal ───────────────────────────────────────────────────────────────── const Modal = ({ open, onClose, title, children, width = 680, noPad = false }) => { if (!open) return null; return (
e.target === e.currentTarget && onClose()}>

{title}

{children}
); }; // ── Toast ───────────────────────────────────────────────────────────────── let _toastFn = null; const Toast = () => { const [toasts, setToasts] = React.useState([]); _toastFn = (msg, type='success') => { const id = Date.now(); setToasts(t => [...t, { id, msg, type }]); setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), 3200); }; const colors = { success: '#10B981', error: '#EF4444', warning: '#F59E0B', info: '#3B82F6' }; return (
{toasts.map(t => (
{t.msg}
))}
); }; const showToast = (msg, type='success') => _toastFn && _toastFn(msg, type); // ── SignaturePad (assinatura digital responsiva) ────────────────────────── const SignaturePad = ({ label, onSign, height = 160 }) => { const canvasRef = React.useRef(null); const containerRef = React.useRef(null); const [drawing, setDrawing] = React.useState(false); const [hasSign, setHasSign] = React.useState(false); // Ajuste de DPI e largura responsiva React.useEffect(() => { const setupCanvas = () => { const canvas = canvasRef.current; const container = containerRef.current; if (!canvas || !container) return; const dpr = window.devicePixelRatio || 1; const w = container.offsetWidth; canvas.width = w * dpr; canvas.height = height * dpr; canvas.style.width = w + 'px'; canvas.style.height = height + 'px'; const ctx = canvas.getContext('2d'); ctx.scale(dpr, dpr); ctx.strokeStyle = '#0F172A'; ctx.lineWidth = 2.5; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; }; setupCanvas(); window.addEventListener('resize', setupCanvas); return () => window.removeEventListener('resize', setupCanvas); }, [height]); const getPos = (e) => { const canvas = canvasRef.current; const r = canvas.getBoundingClientRect(); const src = e.touches && e.touches[0] ? e.touches[0] : e; return { x: src.clientX - r.left, y: src.clientY - r.top }; }; const start = (e) => { e.preventDefault(); setDrawing(true); const pos = getPos(e); const ctx = canvasRef.current.getContext('2d'); ctx.beginPath(); ctx.moveTo(pos.x, pos.y); }; const move = (e) => { if (!drawing) return; e.preventDefault(); const pos = getPos(e); const ctx = canvasRef.current.getContext('2d'); ctx.lineTo(pos.x, pos.y); ctx.stroke(); if (!hasSign) setHasSign(true); }; const end = (e) => { if (!drawing) return; e && e.preventDefault(); setDrawing(false); if (hasSign && onSign) onSign(canvasRef.current.toDataURL('image/png')); }; const clear = () => { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); setHasSign(false); if (onSign) onSign(null); }; return (
{!hasSign && Assine acima usando o mouse ou o dedo}
); }; // ── Input ───────────────────────────────────────────────────────────────── const Input = ({ label, placeholder, value, onChange, type = 'text', style = {} }) => { const [focused, setFocused] = React.useState(false); return (
{label && } setFocused(true)} onBlur={() => setFocused(false)} style={{ padding: '9px 12px', borderRadius: 8, fontSize: 14, color: '#0F172A', border: `1px solid ${focused ? '#3B82F6' : '#E2E8F0'}`, outline: 'none', background: '#fff', fontFamily: 'Inter, sans-serif', boxShadow: focused ? '0 0 0 3px rgba(59,130,246,0.12)' : 'none', transition: 'all 0.15s' }} />
); }; // ── Select ──────────────────────────────────────────────────────────────── const Select = ({ label, value, onChange, options = [], style = {} }) => (
{label && }
); // ── Btn ─────────────────────────────────────────────────────────────────── const Btn = ({ children, onClick, variant = 'primary', size = 'md', icon, disabled = false, style = {} }) => { const [hov, setHov] = React.useState(false); const base = { display: 'inline-flex', alignItems: 'center', gap: 6, cursor: disabled ? 'not-allowed' : 'pointer', border: 'none', borderRadius: 8, fontWeight: 600, fontFamily: 'DM Sans, sans-serif', transition: 'all 0.15s', opacity: disabled ? 0.6 : 1, ...style }; const sizes = { sm: { padding: '6px 12px', fontSize: 13 }, md: { padding: '9px 16px', fontSize: 14 }, lg: { padding: '12px 20px', fontSize: 15 } }; const variants = { primary: { background: hov ? '#E08E00' : '#F59E0B', color: '#0F172A', boxShadow: hov ? '0 4px 12px rgba(245,158,11,0.35)' : '0 1px 4px rgba(245,158,11,0.25)' }, secondary: { background: hov ? '#F1F5F9' : '#F8FAFC', color: '#374151', border: '1px solid #E2E8F0' }, danger: { background: hov ? '#DC2626' : '#EF4444', color: '#fff' }, ghost: { background: hov ? '#F1F5F9' : 'transparent', color: '#64748B' }, blue: { background: hov ? '#2563EB' : '#3B82F6', color: '#fff', boxShadow: hov ? '0 4px 12px rgba(59,130,246,0.35)' : 'none' }, }; return ( ); }; // ── Pagination ──────────────────────────────────────────────────────────── const Pagination = ({ page, total, perPage, onChange }) => { const pages = Math.ceil(total / perPage); return (
onChange(Math.max(1, page-1))} disabled={page===1}>← Anterior {Array.from({length: Math.min(pages, 5)}, (_, i) => { const p = pages <= 5 ? i+1 : page <= 3 ? i+1 : page >= pages-2 ? pages-4+i : page-2+i; return ( ); })} onChange(Math.min(pages, page+1))} disabled={page===pages}>Próximo →
); }; // ── Empty State ─────────────────────────────────────────────────────────── const EmptyState = ({ icon = 'clipboard', title = 'Nenhum dado encontrado', subtitle = 'Tente ajustar os filtros ou adicione um novo registro.' }) => (
{title}
{subtitle}
); // ── Skeleton ────────────────────────────────────────────────────────────── const Skeleton = ({ w = '100%', h = 16, rounded = 4, style = {} }) => (
); // Export Object.assign(window, { Icon, StatusBadge, Avatar, Card, Modal, Toast, showToast, Input, Select, Btn, Pagination, EmptyState, Skeleton, AVATAR_COLORS });