feat: Komplett-Redesign + Ausbau zur Plattform
- Ruhiges Dark-Designsystem (Tokens, Karten, Badges/Pills, KPIs) in global.css, animierter PixelBlast-Hintergrund entfernt (kleineres Bundle) - Aufgeraeumter App-Rahmen: feste Sidebar mit sichtbaren Labels, gruppierter Navigation, aktivem Zustand und Collapse - Wiederverwendbare UI-Bausteine unter src/components/ui/ - Ticketliste als scanbare Karten (TicketCard) statt rowSpan-Tabelle - Neue Bereiche: Kunden (CRM-360), Finanzen-Cockpit, Dokumente-Browser - Dashboard als echtes Cockpit (Ticket-/Finanz-/Dokument-Kennzahlen) - Alle Seiten auf einheitlichen PageHeader/Container/Tokens umgestellt Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
8f6927fbb0
commit
d20a4de117
28 files changed
+1639
-840
No files matched your search
@@ -0,0 +1,8 @@
|
||||
export default function Badge({ tone = 'muted', dot = false, children, className = '', style }) {
|
||||
return (
|
||||
<span className={`badge badge-${tone} ${className}`} style={style}>
|
||||
{dot && <span className="dot" />}
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
const VARIANTS = {
|
||||
default: 'ui-btn',
|
||||
primary: 'ui-btn ui-btn-primary',
|
||||
ghost: 'ui-btn ui-btn-ghost',
|
||||
danger: 'ui-btn ui-btn-danger',
|
||||
}
|
||||
|
||||
export default function Button({
|
||||
variant = 'default',
|
||||
size,
|
||||
as = 'button',
|
||||
className = '',
|
||||
children,
|
||||
...props
|
||||
}) {
|
||||
const cls = `${VARIANTS[variant] || VARIANTS.default}${size === 'sm' ? ' ui-btn-sm' : ''} ${className}`
|
||||
const Comp = as
|
||||
return (
|
||||
<Comp className={cls} {...props}>
|
||||
{children}
|
||||
</Comp>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export default function Card({ title, actions, children, pad = true, className = '', bodyStyle, style }) {
|
||||
if (!title && !actions) {
|
||||
return (
|
||||
<div className={`ui-card ${pad ? 'pad' : ''} ${className}`} style={style}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className={`ui-card ${className}`} style={style}>
|
||||
<div className="ui-card-head">
|
||||
<h3>{title}</h3>
|
||||
{actions && <div className="flex gap-2 items-center">{actions}</div>}
|
||||
</div>
|
||||
<div className="ui-card-body" style={bodyStyle}>{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export default function EmptyState({ icon, title, hint, action }) {
|
||||
return (
|
||||
<div className="empty">
|
||||
{icon && <div className="empty-icon">{icon}</div>}
|
||||
<div style={{ fontWeight: 700, marginBottom: 4 }}>{title}</div>
|
||||
{hint && <div className="faint" style={{ fontSize: 13 }}>{hint}</div>}
|
||||
{action && <div style={{ marginTop: 16 }}>{action}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export default function Field({ label, children, hint }) {
|
||||
return (
|
||||
<div className="field">
|
||||
{label && <label>{label}</label>}
|
||||
{children}
|
||||
{hint && <div className="faint" style={{ fontSize: 12 }}>{hint}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export default function Kpi({ label, value, sub, icon, accent = 'var(--accent)' }) {
|
||||
return (
|
||||
<div className="kpi">
|
||||
<span className="kpi-accent" style={{ background: accent }} />
|
||||
<div className="kpi-label">{icon}{label}</div>
|
||||
<div className="kpi-value">{value}</div>
|
||||
{sub && <div className="kpi-sub">{sub}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export default function PageHeader({ title, subtitle, actions, children }) {
|
||||
return (
|
||||
<div className="page-header">
|
||||
<div>
|
||||
<h1 className="page-title">{title}</h1>
|
||||
{subtitle && <div className="page-subtitle">{subtitle}</div>}
|
||||
{children}
|
||||
</div>
|
||||
{actions && <div className="page-actions">{actions}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
const STATUS_CLASS = {
|
||||
Open: 'status-open',
|
||||
Occupied: 'status-occupied',
|
||||
Assigned: 'status-assigned',
|
||||
Awaiting: 'status-awaiting',
|
||||
'Added Info': 'status-awaiting',
|
||||
Closed: 'status-closed',
|
||||
}
|
||||
|
||||
const PRIORITY = {
|
||||
0: { cls: 'priority-none', label: 'Keine' },
|
||||
1: { cls: 'priority-low', label: 'Niedrig' },
|
||||
2: { cls: 'priority-medium', label: 'Mittel' },
|
||||
3: { cls: 'priority-high', label: 'Hoch' },
|
||||
4: { cls: 'priority-critical', label: 'Kritisch' },
|
||||
}
|
||||
|
||||
const pillBase = {
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
padding: '3px 10px',
|
||||
borderRadius: 999,
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
}
|
||||
|
||||
export function StatusPill({ status }) {
|
||||
const cls = STATUS_CLASS[status] || 'status-open'
|
||||
return <span className={cls} style={pillBase}>{status || 'Open'}</span>
|
||||
}
|
||||
|
||||
export function PriorityPill({ priority }) {
|
||||
const p = PRIORITY[priority] ?? PRIORITY[1]
|
||||
return <span className={p.cls} style={pillBase}>{p.label}</span>
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export default function Tabs({ tabs, active, onChange }) {
|
||||
return (
|
||||
<div className="ticket-tabs">
|
||||
{tabs.map((t) => (
|
||||
<button
|
||||
key={t.id}
|
||||
type="button"
|
||||
className={`ticket-tab ${active === t.id ? 'ticket-tab-active' : ''}`}
|
||||
onClick={() => onChange(t.id)}
|
||||
>
|
||||
{t.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export { default as PageHeader } from './PageHeader'
|
||||
export { default as Card } from './Card'
|
||||
export { default as Button } from './Button'
|
||||
export { default as Badge } from './Badge'
|
||||
export { StatusPill, PriorityPill } from './StatusPill'
|
||||
export { default as Tabs } from './Tabs'
|
||||
export { default as EmptyState } from './EmptyState'
|
||||
export { default as Field } from './Field'
|
||||
export { default as Kpi } from './Kpi'
|
||||
Reference in new issue
Block a user