// 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 (
);
};
// ─── 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 (
{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,
});