feat: add configurable view-aware dashboards
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import type { DragEvent as ReactDragEvent } from "react";
|
||||
import { GripVertical, Plus, RotateCcw, Search } from "lucide-react";
|
||||
import {
|
||||
IconButton,
|
||||
type DashboardWidgetContribution
|
||||
} from "@govoplan/core-webui";
|
||||
import type { DashboardDragItem } from "./dashboardEditorTypes";
|
||||
|
||||
type WidgetLibraryProps = {
|
||||
widgets: DashboardWidgetContribution[];
|
||||
atCapacity: boolean;
|
||||
query: string;
|
||||
onQueryChange: (query: string) => void;
|
||||
onReset: () => void;
|
||||
onAdd: (widget: DashboardWidgetContribution) => void;
|
||||
onDragStart: (
|
||||
event: ReactDragEvent,
|
||||
item: DashboardDragItem
|
||||
) => void;
|
||||
onDragEnd: () => void;
|
||||
};
|
||||
|
||||
export default function WidgetLibrary({
|
||||
widgets,
|
||||
atCapacity,
|
||||
query,
|
||||
onQueryChange,
|
||||
onReset,
|
||||
onAdd,
|
||||
onDragStart,
|
||||
onDragEnd
|
||||
}: WidgetLibraryProps) {
|
||||
return (
|
||||
<aside className="dashboard-widget-library" aria-label="Widget library">
|
||||
<div className="dashboard-widget-library-controls">
|
||||
<div className="dashboard-widget-library-header">
|
||||
<h2>Widget library</h2>
|
||||
</div>
|
||||
<label className="dashboard-widget-search">
|
||||
<Search size={16} aria-hidden="true" />
|
||||
<input
|
||||
type="search"
|
||||
value={query}
|
||||
placeholder="Search widgets"
|
||||
onChange={(event) => onQueryChange(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="dashboard-widget-library-list">
|
||||
{widgets.map((widget) => (
|
||||
<div
|
||||
key={widget.id}
|
||||
className={`dashboard-widget-library-item${atCapacity ? " is-disabled" : ""}`}
|
||||
draggable={!atCapacity}
|
||||
onDragStart={(event) =>
|
||||
onDragStart(event, { kind: "catalogue", widgetId: widget.id })
|
||||
}
|
||||
onDragEnd={onDragEnd}
|
||||
>
|
||||
<GripVertical size={16} aria-hidden="true" />
|
||||
<div>
|
||||
<strong>{widget.title}</strong>
|
||||
<span>{widget.description ?? widget.category ?? widget.moduleId}</span>
|
||||
</div>
|
||||
<IconButton
|
||||
label={`Add ${widget.title}`}
|
||||
icon={<Plus size={16} />}
|
||||
variant="ghost"
|
||||
disabledReason={
|
||||
atCapacity
|
||||
? "A Dashboard can contain at most 100 widgets."
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onAdd(widget)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{widgets.length === 0 && !atCapacity && (
|
||||
<p className="muted dashboard-widget-library-empty">
|
||||
{query.trim()
|
||||
? "No available widgets match the search."
|
||||
: "All available widgets are already on this Dashboard."}
|
||||
</p>
|
||||
)}
|
||||
{atCapacity && (
|
||||
<p className="muted dashboard-widget-library-empty">
|
||||
This Dashboard has reached the 100-widget limit.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<IconButton
|
||||
label="Reset to module defaults"
|
||||
icon={<RotateCcw size={16} />}
|
||||
variant="ghost"
|
||||
onClick={onReset}
|
||||
/>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user