import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { X, Loader2 } from 'lucide-react';
// ---- Kleurmapping voor statussen ----
export const STATUS_COLORS = {
// clients
lead: 'yellow', active: 'green', paused: 'muted', archived: 'muted',
// engagements
planned: 'blue', in_progress: 'accent', completed: 'green', on_hold: 'yellow',
// tasks
todo: 'blue', done: 'green', cancelled: 'muted',
// facturen
draft: 'muted', sent: 'blue', paid: 'green', overdue: 'red',
// overig
critical: 'red', high: 'yellow', medium: 'blue', low: 'muted',
planning: 'blue', inactive: 'muted', standby: 'yellow', decommissioned: 'muted',
new: 'yellow', contacted: 'blue', qualified: 'accent', converted: 'green', lost: 'muted'
};
const BADGE_STYLES = {
accent: 'bg-accent-soft text-accent',
green: 'bg-green/15 text-green',
yellow: 'bg-yellow/15 text-yellow',
red: 'bg-red/15 text-red',
blue: 'bg-blue/15 text-blue',
purple: 'bg-purple/15 text-purple',
pink: 'bg-pink/15 text-pink',
muted: 'bg-muted/15 text-muted'
};
export function Badge({ color = 'muted', children, className = '' }) {
return (
{children}
);
}
export function StatusBadge({ status, labels = {} }) {
return {labels[status] || status};
}
export function Card({ title, subtitle, actions, children, className = '', padding = true }) {
return (
{(title || actions) && (
{title}
{subtitle &&
{subtitle}
}
{actions &&
{actions}
}
)}
{children}
);
}
export function KpiCard({ icon: Icon, label, value, sub, color = 'accent', to, iconColor: iconCol }) {
const navigate = useNavigate();
const colors = {
accent: 'text-accent bg-accent-soft',
green: 'text-green bg-green/15',
yellow: 'text-yellow bg-yellow/15',
red: 'text-red bg-red/15',
blue: 'text-blue bg-blue/15',
purple: 'text-purple bg-purple/15'
};
return (
navigate(to) : undefined}
className={`bg-card border border-border-soft rounded-xl p-4 card-hover h-full ${to ? 'cursor-pointer' : ''}`}
>
{label}
{value}
{sub &&
{sub}
}
{Icon && (
)}
);
}
const BTN_VARIANTS = {
primary: 'bg-accent text-bg hover:bg-accent/90 font-semibold',
secondary: 'bg-card border border-border text-text hover:bg-card-hover',
danger: 'bg-red/15 text-red hover:bg-red/25',
ghost: 'text-muted hover:text-text hover:bg-card'
};
export function Btn({ variant = 'primary', size = 'md', loading, disabled, children, className = '', ...props }) {
const sizes = { sm: 'px-2.5 py-1 text-[12px]', md: 'px-3.5 py-2 text-[13px]', lg: 'px-5 py-2.5 text-[14px]' };
return (
);
}
export function Field({ label, error, children, className = '' }) {
return (
);
}
export const inputCls =
'w-full bg-bg-soft border border-border rounded-lg px-3 py-2 text-[13px] placeholder:text-muted focus:outline-none focus:border-accent/60 transition-colors';
export function Modal({ open, onClose, title, children, wide }) {
useEffect(() => {
if (!open) return;
const handler = (e) => e.key === 'Escape' && onClose?.();
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [open, onClose]);
if (!open) return null;
return (
);
}
export function Spinner({ text = 'Laden...' }) {
return (
{text}
);
}
export function EmptyState({ icon: Icon, title, hint, action }) {
return (
{Icon && (
)}
{title}
{hint &&
{hint}
}
{action &&
{action}
}
);
}
export function ErrorBox({ error, onRetry }) {
if (!error) return null;
return (
{String(error.message || error)}
{onRetry && (
)}
);
}
// Data-tabel met uniforme styling
export function Table({ columns, rows, keyFn, onRowClick, empty }) {
if (!rows || rows.length === 0) {
return empty || Geen gegevens
;
}
return (
{columns.map((c, i) => (
| {c.label} |
))}
{rows.map((row, i) => (
onRowClick(row) : undefined}
>
{columns.map((c, j) => (
|
{c.render ? c.render(row) : row[c.key]}
|
))}
))}
);
}