// Shared UI primitives for Brisas do Lago const { useState, useEffect, useRef, useMemo, createContext, useContext } = React; // ─── Icons (lineart) ───────────────────────────────────────────── const Icon = ({name, size=18, stroke=2}) => { const paths = { map: <>, home: <>, user: <>, users: <>, plus: <>, minus: <>, check: <>, x: <>, chevR: <>, chevL: <>, chevD: <>, chevU: <>, bell: <>, search: <>, filter: <>, file: <>, creditCard: <>, qr: <>, download: <>, share: <>, heart: <>, pin: <>, ruler: <>, sun: <>, moon: <>, wave: <>, tree: <>, dashboard: <>, layers: <>, funnel: <>, briefcase: <>, invoice: <>, coin: <>, settings: <>, sparkle: <>, pix: <>, edit: <>, trash: <>, upload: <>, arrowR: <>, arrowL: <>, arrowU: <>, arrowD: <>, eye: <>, grid: <>, list: <>, calendar: <>, clock: <>, phone: <>, mail: <>, warn: <>, info: <>, star: <>, logout: <>, construction: <>, }; return ( {paths[name] || null} ); }; // ─── Brand mark ───────────────────────────────────────────── const BrandMark = ({size=38, dark=false}) => (
); // ─── Status pill ──────────────────────────────────────────── const StatusPill = ({status, size='md'}) => { const colors = { disponivel: { bg: '#FBF6EC', fg: '#1A2E2A', bd: '#2D4F3C', label: 'Disponível' }, reservado: { bg: '#F5DCC6', fg: '#5C2F12', bd: '#A8632F', label: 'Reservado' }, negociacao: { bg: '#D7E5EC', fg: '#1F3D49', bd: '#3B6B7E', label: 'Em negociação' }, vendido: { bg: '#DDE3DD', fg: '#3A4640', bd: '#6E7B72', label: 'Vendido' }, adimplente: { bg: '#E0EDDF', fg: '#2D4F3C', bd: '#4F7A52', label: 'Adimplente' }, atraso: { bg: '#F5DCC6', fg: '#8B4513', bd: '#C97B4A', label: 'Em atraso' }, inadimplente: { bg: '#F4CFC9', fg: '#8B2A1F', bd: '#B84A3A', label: 'Inadimplente' }, }; const c = colors[status] || colors.disponivel; return ( {c.label} ); }; // ─── KPI Card ─────────────────────────────────────────────── const KpiCard = ({label, value, sub, trend, icon, color='var(--green)'}) => (
{icon &&
}
{label}
{value}
{sub &&
{sub}
} {trend && (
{trend.value}
)}
); // ─── Modal ────────────────────────────────────────────────── const Modal = ({open, onClose, children, width=480}) => { useEffect(() => { if (!open) return; const h = (e) => { if (e.key === 'Escape') onClose?.(); }; window.addEventListener('keydown', h); return () => window.removeEventListener('keydown', h); }, [open, onClose]); if (!open) return null; return (
e.stopPropagation()} style={{ background:'var(--bg-card)', borderRadius: 18, width:'100%', maxWidth: width, maxHeight:'90vh', overflow:'auto', boxShadow:'var(--shadow-3)', animation: 'fadeIn .2s ease-out' }}> {children}
); }; // ─── Number formatters ───────────────────────────────────── const fmtBRL = (v) => v.toLocaleString('pt-BR', {style:'currency', currency:'BRL', maximumFractionDigits: 0}); const fmtBRLcents = (v) => v.toLocaleString('pt-BR', {style:'currency', currency:'BRL'}); // ─── Photo placeholders (organic generated SVGs) ────────── const PhotoPlaceholder = ({type='lago', height=200, label}) => { const presets = { lago: { bg: ['#5B8FA8', '#7AA1AF', '#A6C2CF'], shapes: 'water' }, aerea: { bg: ['#9CB58E', '#7A9070', '#5E7558'], shapes: 'forest' }, portaria: { bg: ['#8B7355', '#A8896B', '#D9C7A8'], shapes: 'building' }, pier: { bg: ['#5B8FA8', '#A6C2CF', '#D9C7A8'], shapes: 'pier' }, }; const p = presets[type] || presets.lago; return (
{p.shapes === 'water' && ( <> )} {p.shapes === 'forest' && ( <> )} {p.shapes === 'building' && ( <> )} {p.shapes === 'pier' && ( <> )} {label && (
{label}
)}
); }; // Section header (eyebrow + display title + sub) const SectionHeader = ({eyebrow, title, sub, action}) => (
{eyebrow &&
{eyebrow}
}
{title}
{sub &&
{sub}
}
{action}
); Object.assign(window, { Icon, BrandMark, StatusPill, KpiCard, Modal, PhotoPlaceholder, SectionHeader, fmtBRL, fmtBRLcents, });