feat(web): Dashboard mit Begruessung, Befehlsleiste und ruhigeren Kacheln
Begruessung nach Tageszeit mit Datum, Befehlsleiste oben rechts statt schwebendem Knopf, einheitliche Kachel-Kopfzeilen mit Symbol, Kalender mit Pfeilknoepfen, runden Tagen und Terminpunkten, Favoriten als 36-px-Zeilen bzw. App-Starter-Kacheln, Notiz-Kaestchen im Akzent, leerer Zustand mit drei Vorschlaegen. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,9 +6,9 @@ import type { Compactor, ResponsiveLayouts } from 'react-grid-layout';
|
||||
import 'react-grid-layout/css/styles.css';
|
||||
import 'react-resizable/css/styles.css';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { WIDGET_CONSTRAINTS, type WidgetType } from './widget-registry';
|
||||
import { WIDGET_CONSTRAINTS, WIDGET_REGISTRY, type WidgetType } from './widget-registry';
|
||||
import { WidgetIcon } from './widgets/widget-icon';
|
||||
import { WidgetWrapper } from './widgets/widget-wrapper';
|
||||
import { TesseraLogo } from '@/components/brand/tessera-logo';
|
||||
|
||||
// quick-260916-bwo: Raster doppelt so fein wie zuvor (24 statt 12 Spalten,
|
||||
// 20 statt 40 px Zeilenhoehe, 8 statt 16 px Abstand). Gespeicherte Anordnungen
|
||||
@@ -60,8 +60,15 @@ interface DashboardGridProps {
|
||||
isEditMode: boolean;
|
||||
onLayoutChange: (allLayouts: ResponsiveLayouts) => void;
|
||||
onRemoveWidget: (id: string) => void;
|
||||
/** Leerer Zustand: oeffnet den Widget-Katalog. */
|
||||
onOpenCatalog?: () => void;
|
||||
/** Leerer Zustand: fuegt eine vorgeschlagene Kachel direkt hinzu. */
|
||||
onQuickAdd?: (type: WidgetType) => void;
|
||||
}
|
||||
|
||||
/** Vorschlaege im leeren Zustand — Plattform-Kacheln, immer verfuegbar. */
|
||||
const QUICK_ADD_TYPES: WidgetType[] = ['clock', 'calendar', 'note'];
|
||||
|
||||
/**
|
||||
* quick-260916-dyv: Gespeicherte minW/minH aus den Konstanten ueberschreiben.
|
||||
*
|
||||
@@ -123,6 +130,8 @@ export function DashboardGrid({
|
||||
isEditMode,
|
||||
onLayoutChange,
|
||||
onRemoveWidget,
|
||||
onOpenCatalog,
|
||||
onQuickAdd,
|
||||
}: DashboardGridProps) {
|
||||
const t = useTranslations('widgets');
|
||||
const [width, setWidth] = useState(1200);
|
||||
@@ -203,20 +212,46 @@ export function DashboardGrid({
|
||||
// stabil bleibt.
|
||||
const effectiveLayouts = useMemo(() => applyConstraintMinima(layouts, widgets), [layouts, widgets]);
|
||||
|
||||
// Empty state (D-02)
|
||||
// Leerer Zustand (D-02, Design „Mosaik“ Runde 2): freundlich ohne
|
||||
// Illustration — Titel, ein Satz, drei Vorschlaege zum direkten
|
||||
// Hinzufuegen und der Hauptknopf zum Katalog.
|
||||
if (widgets.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh] gap-4">
|
||||
{/* Bildmarke statt des frueheren handgezeichneten Vier-Quadrate-Symbols.
|
||||
Ohne Rahmen und ohne Kontur: die Marke steht hier frei auf dem
|
||||
Seitenhintergrund, nicht auf einer eigenen Flaeche. */}
|
||||
<TesseraLogo variant="mark" size={64} plateOutline={false} />
|
||||
<h2 className="text-lg font-semibold text-muted-foreground">
|
||||
{t('emptyHeading')}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('emptyBody')}
|
||||
</p>
|
||||
<div className="flex min-h-[55vh] flex-col items-center justify-center px-4 text-center">
|
||||
<div className="dashboard-chrome flex max-w-md flex-col items-center rounded-xl px-6 py-8">
|
||||
<span className="mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-muted text-muted-foreground">
|
||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect x="3" y="3" width="7" height="9" rx="1.5" />
|
||||
<rect x="14" y="3" width="7" height="5" rx="1.5" />
|
||||
<rect x="14" y="12" width="7" height="9" rx="1.5" />
|
||||
<rect x="3" y="16" width="7" height="5" rx="1.5" />
|
||||
</svg>
|
||||
</span>
|
||||
<h2 className="text-base font-semibold text-foreground">{t('emptyState.title')}</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">{t('emptyState.body')}</p>
|
||||
{onQuickAdd && (
|
||||
<ul className="mt-4 flex flex-wrap justify-center gap-2">
|
||||
{QUICK_ADD_TYPES.map((type) => (
|
||||
<li key={type}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onQuickAdd(type)}
|
||||
className="inline-flex h-8 items-center gap-2 rounded-full border border-border bg-card px-3 text-sm text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
<WidgetIcon type={type} />
|
||||
{t(WIDGET_REGISTRY[type].nameKey)}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{onOpenCatalog && (
|
||||
<button type="button" onClick={onOpenCatalog} className="btn btn-primary mt-5">
|
||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M12 5v14M5 12h14" /></svg>
|
||||
{t('addWidget')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user