Files
simple-pos-system/CLAUDE_DESIGN/ops-ui.jsx

146 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Shared primitives for the ops dashboard
function Avatar({ name, size = 36, status }) {
const palette = ['#3758c9', '#7a44c9', '#2f9e5e', '#d94b26', '#8a6d2b', '#0d7a8a', '#c93775'];
let h = 0;
for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;
const bg = palette[h % palette.length];
const parts = name.split(' ');
const initials = (parts[0][0] + (parts[1]?.[0] || '')).toUpperCase();
return (
<div style={{ position: 'relative', flexShrink: 0 }}>
<div style={{
width: size, height: size, borderRadius: '50%',
background: bg, color: 'white',
fontSize: size * 0.4, fontWeight: 600,
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>{initials}</div>
{status && (
<div style={{
position: 'absolute', bottom: -1, right: -1,
width: size * 0.32, height: size * 0.32,
borderRadius: '50%',
background: status === 'active' ? 'var(--open-500)' : status === 'break' ? 'var(--dirty-500)' : 'var(--ink-300)',
border: '2px solid white',
}} />
)}
</div>
);
}
function Card({ title, action, children, padding = 22, style }) {
return (
<div style={{
background: 'white',
border: '1px solid var(--ink-100)',
borderRadius: 16,
boxShadow: '0 1px 2px rgba(16,20,24,0.04)',
display: 'flex', flexDirection: 'column',
...style,
}}>
{title && (
<div style={{
padding: `18px ${padding}px 0`,
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
}}>
<div style={{ fontSize: 14, fontWeight: 700, color: 'var(--ink-900)', textTransform: 'uppercase', letterSpacing: 0.6 }}>{title}</div>
{action}
</div>
)}
<div style={{ padding: padding, paddingTop: title ? 14 : padding, flex: 1 }}>{children}</div>
</div>
);
}
function StatPill({ delta }) {
if (delta == null) return null;
const up = delta > 0;
return (
<span style={{
display: 'inline-flex', alignItems: 'center', gap: 3,
padding: '3px 8px',
borderRadius: 999,
background: up ? 'var(--open-50)' : 'var(--alert-50)',
color: up ? 'var(--open-700)' : 'var(--alert-700)',
fontSize: 12, fontWeight: 600,
}}>
<span style={{ fontSize: 10 }}>{up ? '▲' : '▼'}</span>
{Math.abs(delta).toFixed(1)}%
</span>
);
}
function Stepper({ value, onChange, min = 0, max = 99 }) {
return (
<div style={{
display: 'inline-flex', alignItems: 'center',
height: 36, borderRadius: 18,
background: 'white', border: '1px solid var(--ink-200)',
}} onClick={(e) => e.stopPropagation()}>
<button onClick={() => onChange(Math.max(min, value - 1))} disabled={value <= min}
style={{ width: 36, height: 36, border: 'none', background: 'transparent', fontSize: 18, color: value <= min ? 'var(--ink-300)' : 'var(--ink-900)', cursor: value <= min ? 'default' : 'pointer' }}></button>
<div style={{ minWidth: 26, textAlign: 'center', fontSize: 15, fontWeight: 600, fontFamily: "'Geist Mono', monospace" }}>{value}</div>
<button onClick={() => onChange(Math.min(max, value + 1))} style={{ width: 36, height: 36, border: 'none', background: 'transparent', fontSize: 18, cursor: 'pointer' }}>+</button>
</div>
);
}
function Btn({ children, variant = 'secondary', size = 'md', onClick, style }) {
const variants = {
primary: { bg: 'var(--brand-500)', fg: 'white', bd: 'var(--brand-500)' },
danger: { bg: 'var(--alert-500)', fg: 'white', bd: 'var(--alert-500)' },
secondary: { bg: 'white', fg: 'var(--ink-900)', bd: 'var(--ink-200)' },
ghost: { bg: 'transparent', fg: 'var(--ink-700)', bd: 'transparent' },
};
const sizes = {
sm: { h: 32, px: 12, fs: 13 },
md: { h: 40, px: 16, fs: 14 },
lg: { h: 48, px: 22, fs: 15 },
};
const v = variants[variant];
const s = sizes[size];
return (
<button onClick={onClick} style={{
height: s.h, padding: `0 ${s.px}px`,
borderRadius: s.h / 2,
background: v.bg, color: v.fg,
border: '1px solid ' + v.bd,
fontSize: s.fs, fontWeight: 600,
fontFamily: 'inherit',
cursor: 'pointer',
display: 'inline-flex', alignItems: 'center', gap: 8,
whiteSpace: 'nowrap',
...style,
}}>{children}</button>
);
}
function Icon({ name, size = 20, color = 'currentColor' }) {
const paths = {
play: 'M8 5V19L19 12L8 5Z',
stop: 'M6 6H18V18H6V6Z',
bell: 'M12 22C13.1 22 14 21.1 14 20H10C10 21.1 10.9 22 12 22ZM18 16V11C18 7.9 16.4 5.4 13.5 4.7V4C13.5 3.2 12.8 2.5 12 2.5C11.2 2.5 10.5 3.2 10.5 4V4.7C7.6 5.4 6 7.9 6 11V16L4 18V19H20V18L18 16Z',
plus: 'M12 5V19M5 12H19',
chat: 'M20 2H4C2.9 2 2 2.9 2 4V22L6 18H20C21.1 18 22 17.1 22 16V4C22 2.9 21.1 2 20 2Z',
clock: 'M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22ZM12.5 7H11V13L16.2 16.2L17 14.9L12.5 12.2V7Z',
coffee: 'M2 21H20V19H2V21ZM20 8H18V5H4V13C4 15.2 5.8 17 8 17H14C16.2 17 18 15.2 18 13V10H20C21.1 10 22 9.1 22 8V8C22 6.9 21.1 8 20 8Z',
users: 'M16 11C17.7 11 19 9.7 19 8C19 6.3 17.7 5 16 5C14.3 5 13 6.3 13 8C13 9.7 14.3 11 16 11ZM8 11C9.7 11 11 9.7 11 8C11 6.3 9.7 5 8 5C6.3 5 5 6.3 5 8C5 9.7 6.3 11 8 11ZM8 13C5.3 13 0 14.3 0 17V19H16V17C16 14.3 10.7 13 8 13ZM16 13C15.7 13 15.3 13 14.9 13.1C16.2 14 17 15.3 17 17V19H24V17C24 14.3 18.7 13 16 13Z',
table: 'M3 5C3 3.9 3.9 3 5 3H19C20.1 3 21 3.9 21 5V19C21 20.1 20.1 21 19 21H5C3.9 21 3 20.1 3 19V5ZM5 11H19V5H5V11ZM5 13V19H11V13H5ZM13 13V19H19V13H13Z',
check: 'M9 16.2L4.8 12L3.4 13.4L9 19L21 7L19.6 5.6L9 16.2Z',
x: 'M19 6.4L17.6 5L12 10.6L6.4 5L5 6.4L10.6 12L5 17.6L6.4 19L12 13.4L17.6 19L19 17.6L13.4 12L19 6.4Z',
chevron: 'M9 6L15 12L9 18',
send: 'M2 21L23 12L2 3V10L17 12L2 14V21Z',
pause: 'M6 4H10V20H6V4ZM14 4H18V20H14V4Z',
search: 'M15.5 14H14.7L14.4 13.7C15.4 12.5 16 10.8 16 9C16 5.1 12.9 2 9 2C5.1 2 2 5.1 2 9C2 12.9 5.1 16 9 16C10.8 16 12.5 15.4 13.7 14.4L14 14.7V15.5L19 20.5L20.5 19L15.5 14Z',
bolt: 'M11 21H10L11 14H7.5C7 14 7 13.7 7.1 13.5L13 3H14L13 10H16.5C16.9 10 17 10.2 16.9 10.5L11 21Z',
sun: 'M12 7C9.2 7 7 9.2 7 12C7 14.8 9.2 17 12 17C14.8 17 17 14.8 17 12C17 9.2 14.8 7 12 7ZM2 13H4C4.6 13 5 12.6 5 12C5 11.4 4.6 11 4 11H2C1.4 11 1 11.4 1 12C1 12.6 1.4 13 2 13ZM20 13H22C22.6 13 23 12.6 23 12C23 11.4 22.6 11 22 11H20C19.4 11 19 11.4 19 12C19 12.6 19.4 13 20 13ZM11 2V4C11 4.6 11.4 5 12 5C12.6 5 13 4.6 13 4V2C13 1.4 12.6 1 12 1C11.4 1 11 1.4 11 2ZM11 20V22C11 22.6 11.4 23 12 23C12.6 23 13 22.6 13 22V20C13 19.4 12.6 19 12 19C11.4 19 11 19.4 11 20ZM5.99 4.58C5.6 4.19 4.96 4.19 4.58 4.58C4.19 4.97 4.19 5.6 4.58 5.99L5.64 7.05C6.03 7.44 6.66 7.44 7.05 7.05C7.44 6.66 7.44 6.03 7.05 5.64L5.99 4.58ZM18.36 16.95C17.97 16.56 17.34 16.56 16.95 16.95C16.56 17.34 16.56 17.97 16.95 18.36L18.01 19.42C18.4 19.81 19.04 19.81 19.42 19.42C19.81 19.03 19.81 18.4 19.42 18.01L18.36 16.95Z',
};
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none">
<path d={paths[name]} stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
window.OpsUI = { Avatar, Card, StatPill, Stepper, Btn, Icon };