diff --git a/ui/src/components/layout/PageHeader.tsx b/ui/src/components/layout/PageHeader.tsx index 4771162..93178e9 100644 --- a/ui/src/components/layout/PageHeader.tsx +++ b/ui/src/components/layout/PageHeader.tsx @@ -40,6 +40,7 @@ import { PROJECT_CYCLES_FILTER_EVENT, PROJECT_CYCLES_REFRESH_EVENT, } from '../../lib/projectCyclesEvents'; +import { dispatchOpenHomeWidgets } from '../../lib/homeWidgetsEvents'; import { DEFAULT_PROJECT_ISSUES_FILTERS, PROJECT_ISSUES_DISPLAY_EVENT, @@ -752,6 +753,7 @@ function HomeHeader() { variant="ghost" size="sm" className="gap-1.5 text-[13px] font-medium text-(--txt-secondary)" + onClick={() => dispatchOpenHomeWidgets()} > Manage widgets diff --git a/ui/src/lib/homeWidgetsEvents.ts b/ui/src/lib/homeWidgetsEvents.ts new file mode 100644 index 0000000..885529d --- /dev/null +++ b/ui/src/lib/homeWidgetsEvents.ts @@ -0,0 +1,8 @@ +export const OPEN_HOME_WIDGETS = 'devlane:open-home-widgets'; + +export type OpenHomeWidgetsPayload = unknown; + +export function dispatchOpenHomeWidgets(payload?: OpenHomeWidgetsPayload) { + if (typeof window === 'undefined') return; + window.dispatchEvent(new CustomEvent(OPEN_HOME_WIDGETS, { detail: payload })); +} diff --git a/ui/src/pages/SettingsPage.tsx b/ui/src/pages/SettingsPage.tsx index ddab8be..a47cbb1 100644 --- a/ui/src/pages/SettingsPage.tsx +++ b/ui/src/pages/SettingsPage.tsx @@ -1808,13 +1808,19 @@ export function SettingsPage() { className="flex items-start justify-between gap-4 rounded-(--radius-md) border border-(--border-subtle) px-4 py-3" >
-

{label}

+

+ {label} +

{desc}

+ } + > +
+ {widgets.map((widget) => ( +
{ + e.dataTransfer.setData('text/plain', widget.id); + e.dataTransfer.effectAllowed = 'move'; + setDraggingWidgetId(widget.id); + }} + onDragEnd={() => setDraggingWidgetId(null)} + onDragOver={(e) => e.preventDefault()} + onDrop={(e) => { + e.preventDefault(); + const draggedWidgetId = e.dataTransfer.getData('text/plain') as string; + if ( + draggedWidgetId && + !widgets.some((candidate) => candidate.id === (draggedWidgetId as HomeWidgetId)) + ) { + return; + } + // Pass the dragged id to handleWidgetDrop so reordering is driven + // by the drag source (dataTransfer) rather than relying on state + // which can be out-of-sync due to timing. + handleWidgetDrop(widget.id, (draggedWidgetId as HomeWidgetId) || undefined); + }} + className={`flex items-center justify-between rounded-(--radius-md) border px-3 py-2 ${ + draggingWidgetId === widget.id + ? 'border-(--border-strong) bg-(--bg-layer-1)' + : 'border-(--border-subtle) bg-(--bg-surface-1)' + }`} + > +
+ + + + + {widget.label} + +
+
+ +
+
+ ))} +
+ + {/* Welcome */} +
+

+ {getGreeting()}, {user?.name ?? 'User'} +

+

+ + + + {formatDateTime(new Date())} +

+
+ + {widgets + .filter((widget) => widget.enabled) + .map((widget) => ( +
{sectionByWidgetId[widget.id]}
+ ))} ); }