@burdenoff/microfe-workspaces 2026.830.2 → 2026.917.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/pages/dashboard/DashboardCustomizer.js +112 -52
- package/dist/pages/dashboard/DashboardCustomizer.js.map +1 -1
- package/dist/pages/dashboard/reorder.js +26 -0
- package/dist/pages/dashboard/reorder.js.map +1 -0
- package/dist/pages/dashboard/widgets/WelcomeWidget.js +26 -16
- package/dist/pages/dashboard/widgets/WelcomeWidget.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { tWithFallback as e } from "../../utils/i18n.js";
|
|
2
2
|
import { DEFAULT_DASHBOARD_WIDGETS as t, DEFAULT_DASHBOARD_WIDGETS_BY_PRODUCT as n, WIDGET_DEFINITIONS as r, isWidgetAvailable as i, resolveWidgetCopy as a } from "./config.js";
|
|
3
3
|
import { nativeImpact as o } from "../../utils/nativeBridge.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
4
|
+
import { dropPositionFromPointer as s, reorderByDropIndex as c } from "./reorder.js";
|
|
5
|
+
import { useCallback as l, useEffect as u, useRef as d, useState as f } from "react";
|
|
6
|
+
import { ArrowDown as p, ArrowUp as m, GripVertical as h, Loader2 as g, RotateCcw as _, Save as v } from "lucide-react";
|
|
7
|
+
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
8
|
+
import { Badge as x, Button as S, Separator as C, Sheet as w, SheetContent as T, SheetDescription as E, SheetFooter as D, SheetHeader as O, SheetTitle as ee, Switch as te } from "@burdenoff/fe-libs/ui";
|
|
9
|
+
import { useI18n as ne } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
9
10
|
//#region src/pages/dashboard/DashboardCustomizer.tsx
|
|
10
11
|
function k({ open: k, onOpenChange: A, widgets: j, onSave: M, isSaving: N, productId: P = "workspaces" }) {
|
|
11
|
-
let { t: F } =
|
|
12
|
-
|
|
12
|
+
let { t: F } = ne(), I = l((t, n) => e(F, t, n), [F]), [L, R] = f(j), [z, B] = f(!1), [V, H] = f(null), [U, W] = f(null), [G, K] = f(""), q = d(/* @__PURE__ */ new Map()), J = d(null), Y = d(0);
|
|
13
|
+
u(() => {
|
|
13
14
|
k && (R([...j]), B(!1));
|
|
14
15
|
}, [k, j]);
|
|
15
|
-
let
|
|
16
|
+
let X = l((e) => {
|
|
16
17
|
R((t) => t.map((t) => t.widgetId === e ? {
|
|
17
18
|
...t,
|
|
18
19
|
enabled: !t.enabled
|
|
19
20
|
} : t)), B(!0);
|
|
20
|
-
}, []),
|
|
21
|
+
}, []), Z = l((e, t) => {
|
|
21
22
|
R((n) => {
|
|
22
23
|
let r = [...n].sort((e, t) => e.order - t.order), i = r.findIndex((t) => t.widgetId === e);
|
|
23
24
|
if (i < 0) return n;
|
|
@@ -32,96 +33,155 @@ function k({ open: k, onOpenChange: A, widgets: j, onSave: M, isSaving: N, produ
|
|
|
32
33
|
order: o
|
|
33
34
|
}, r;
|
|
34
35
|
}), B(!0);
|
|
35
|
-
}, []),
|
|
36
|
+
}, []), re = l(() => {
|
|
36
37
|
R([...n[P] ?? t]), B(!0);
|
|
37
|
-
}, [P]),
|
|
38
|
+
}, [P]), ie = l((e) => (t) => {
|
|
39
|
+
t ? q.current.set(e, t) : q.current.delete(e);
|
|
40
|
+
}, []), ae = l((e, t) => {
|
|
41
|
+
H(e), J.current = null, cancelAnimationFrame(Y.current), t.dataTransfer.effectAllowed = "move", t.dataTransfer.setData("text/plain", e);
|
|
42
|
+
}, []), oe = l((e, t) => {
|
|
43
|
+
!V || V === e || (t.preventDefault(), t.dataTransfer.dropEffect = "move", W(e), cancelAnimationFrame(Y.current), Y.current = requestAnimationFrame(() => {
|
|
44
|
+
let n = q.current.get(e);
|
|
45
|
+
if (!n) return;
|
|
46
|
+
let r = s(t.clientY, n.getBoundingClientRect()), i = J.current;
|
|
47
|
+
i && i.fromId === V && i.toId === e && i.position === r || (J.current = {
|
|
48
|
+
fromId: V,
|
|
49
|
+
toId: e,
|
|
50
|
+
position: r
|
|
51
|
+
}, R((t) => c(t, V, e, r)), B(!0));
|
|
52
|
+
}));
|
|
53
|
+
}, [V]), Q = l((e, t) => {
|
|
54
|
+
t.currentTarget.contains(t.relatedTarget) || U === e && W(null);
|
|
55
|
+
}, [U]), se = l((e, t) => {
|
|
56
|
+
t.preventDefault(), t.dataTransfer && (t.dataTransfer.dropEffect = "move"), cancelAnimationFrame(Y.current), H(null), W(null);
|
|
57
|
+
}, []), ce = l(() => {
|
|
58
|
+
if (cancelAnimationFrame(Y.current), V) {
|
|
59
|
+
let t = [...L].sort((e, t) => e.order - t.order), n = t.findIndex((e) => e.widgetId === V), i = r[V], o = i ? a(I, i).title : V;
|
|
60
|
+
n >= 0 && K(e(F, "dashboardCustomizer.reorderAnnouncement", `${o} moved to position ${n + 1} of ${t.length}`));
|
|
61
|
+
}
|
|
62
|
+
H(null), W(null), J.current = null;
|
|
63
|
+
}, [
|
|
64
|
+
V,
|
|
65
|
+
L,
|
|
66
|
+
I,
|
|
67
|
+
F
|
|
68
|
+
]), le = l(async () => {
|
|
38
69
|
await M(L), B(!1), A(!1);
|
|
39
70
|
}, [
|
|
40
71
|
L,
|
|
41
72
|
M,
|
|
42
73
|
A
|
|
43
|
-
]),
|
|
44
|
-
return /* @__PURE__ */
|
|
74
|
+
]), $ = [...L].sort((e, t) => e.order - t.order);
|
|
75
|
+
return /* @__PURE__ */ y(w, {
|
|
45
76
|
open: k,
|
|
46
77
|
onOpenChange: A,
|
|
47
|
-
children: /* @__PURE__ */
|
|
78
|
+
children: /* @__PURE__ */ b(T, {
|
|
48
79
|
className: "w-full sm:max-w-md overflow-y-auto",
|
|
49
80
|
side: "right",
|
|
50
81
|
children: [
|
|
51
|
-
/* @__PURE__ */
|
|
52
|
-
/* @__PURE__ */
|
|
82
|
+
/* @__PURE__ */ b(O, { children: [/* @__PURE__ */ y(ee, { children: e(F, "dashboardCustomizer.title", "Customize Dashboard") }), /* @__PURE__ */ y(E, { children: e(F, "dashboardCustomizer.description", "Toggle widgets on or off and reorder them to personalize your dashboard.") })] }),
|
|
83
|
+
/* @__PURE__ */ b("div", {
|
|
84
|
+
role: "listbox",
|
|
85
|
+
"aria-label": e(F, "dashboardCustomizer.listLabel", "Dashboard widgets"),
|
|
53
86
|
className: "py-4 space-y-1",
|
|
54
|
-
children:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
87
|
+
children: [/* @__PURE__ */ y("div", {
|
|
88
|
+
"aria-live": "assertive",
|
|
89
|
+
className: "sr-only",
|
|
90
|
+
children: G
|
|
91
|
+
}), $.map((t, n) => {
|
|
92
|
+
let s = r[t.widgetId];
|
|
93
|
+
if (!s || !i(s, P)) return null;
|
|
94
|
+
let c = V === t.widgetId, l = U === t.widgetId && V !== null, u = a(I, s).title;
|
|
95
|
+
return /* @__PURE__ */ b("div", {
|
|
96
|
+
ref: ie(t.widgetId),
|
|
97
|
+
role: "option",
|
|
98
|
+
"aria-roledescription": e(F, "dashboardCustomizer.sortableItem", "sortable item"),
|
|
99
|
+
"aria-selected": c,
|
|
100
|
+
"aria-label": e(F, "dashboardCustomizer.widgetRow", `Widget: ${u}. Use the arrow buttons to reorder.`),
|
|
101
|
+
onDragOver: (e) => oe(t.widgetId, e),
|
|
102
|
+
onDragLeave: (e) => Q(t.widgetId, e),
|
|
103
|
+
onDrop: (e) => se(t.widgetId, e),
|
|
104
|
+
"data-widget-id": t.widgetId,
|
|
105
|
+
className: `flex items-center gap-3 p-3 rounded-lg border transition-colors ${t.enabled ? "border-border-default bg-bg-surface" : "border-transparent bg-bg-sunken/30 opacity-60"} ${c ? "opacity-50" : ""} ${l ? "ring-2 ring-border-strong border-transparent" : ""}`,
|
|
58
106
|
children: [
|
|
59
|
-
/* @__PURE__ */
|
|
60
|
-
|
|
107
|
+
/* @__PURE__ */ y("span", {
|
|
108
|
+
draggable: !0,
|
|
109
|
+
onDragStart: (e) => ae(t.widgetId, e),
|
|
110
|
+
onDragEnd: ce,
|
|
111
|
+
"aria-label": e(F, "dashboardCustomizer.dragHandle", `Drag handle for ${u}`),
|
|
112
|
+
className: "cursor-grab active:cursor-grabbing touch-none select-none",
|
|
113
|
+
children: /* @__PURE__ */ y(h, {
|
|
114
|
+
"aria-hidden": !0,
|
|
115
|
+
className: "size-4 text-text-secondary shrink-0"
|
|
116
|
+
})
|
|
117
|
+
}),
|
|
118
|
+
/* @__PURE__ */ b("div", {
|
|
61
119
|
className: "flex-1 min-w-0",
|
|
62
|
-
children: [/* @__PURE__ */
|
|
120
|
+
children: [/* @__PURE__ */ b("div", {
|
|
63
121
|
className: "flex items-center gap-2",
|
|
64
|
-
children: [/* @__PURE__ */
|
|
122
|
+
children: [/* @__PURE__ */ y("span", {
|
|
65
123
|
className: "text-sm font-medium text-text-primary",
|
|
66
|
-
children:
|
|
67
|
-
}), /* @__PURE__ */
|
|
124
|
+
children: u
|
|
125
|
+
}), /* @__PURE__ */ y(x, {
|
|
68
126
|
variant: "outline",
|
|
69
127
|
className: "text-[10px]",
|
|
70
|
-
children:
|
|
128
|
+
children: s.defaultSize
|
|
71
129
|
})]
|
|
72
|
-
}), /* @__PURE__ */
|
|
130
|
+
}), /* @__PURE__ */ y("p", {
|
|
73
131
|
className: "text-xs text-text-secondary truncate",
|
|
74
|
-
children: a(I,
|
|
132
|
+
children: a(I, s).description
|
|
75
133
|
})]
|
|
76
134
|
}),
|
|
77
|
-
/* @__PURE__ */
|
|
135
|
+
/* @__PURE__ */ b("div", {
|
|
78
136
|
className: "flex items-center gap-1 shrink-0",
|
|
79
137
|
children: [
|
|
80
|
-
/* @__PURE__ */
|
|
138
|
+
/* @__PURE__ */ y(S, {
|
|
81
139
|
variant: "ghost",
|
|
82
140
|
size: "sm",
|
|
83
141
|
className: "size-7 p-0",
|
|
84
|
-
disabled:
|
|
85
|
-
onClick: () =>
|
|
86
|
-
|
|
142
|
+
disabled: n === 0,
|
|
143
|
+
onClick: () => Z(t.widgetId, "up"),
|
|
144
|
+
"aria-label": e(F, "dashboardCustomizer.moveUp", `Move ${u} up`),
|
|
145
|
+
children: /* @__PURE__ */ y(m, { className: "size-3" })
|
|
87
146
|
}),
|
|
88
|
-
/* @__PURE__ */
|
|
147
|
+
/* @__PURE__ */ y(S, {
|
|
89
148
|
variant: "ghost",
|
|
90
149
|
size: "sm",
|
|
91
150
|
className: "size-7 p-0",
|
|
92
|
-
disabled:
|
|
93
|
-
onClick: () =>
|
|
94
|
-
|
|
151
|
+
disabled: n === $.length - 1,
|
|
152
|
+
onClick: () => Z(t.widgetId, "down"),
|
|
153
|
+
"aria-label": e(F, "dashboardCustomizer.moveDown", `Move ${u} down`),
|
|
154
|
+
children: /* @__PURE__ */ y(p, { className: "size-3" })
|
|
95
155
|
}),
|
|
96
|
-
/* @__PURE__ */
|
|
97
|
-
checked:
|
|
156
|
+
/* @__PURE__ */ y(te, {
|
|
157
|
+
checked: t.enabled,
|
|
98
158
|
onCheckedChange: () => {
|
|
99
|
-
o("medium"),
|
|
159
|
+
o("medium"), X(t.widgetId);
|
|
100
160
|
},
|
|
101
|
-
disabled: !
|
|
161
|
+
disabled: !s.removable,
|
|
102
162
|
className: "ml-1"
|
|
103
163
|
})
|
|
104
164
|
]
|
|
105
165
|
})
|
|
106
166
|
]
|
|
107
|
-
},
|
|
108
|
-
})
|
|
167
|
+
}, t.widgetId);
|
|
168
|
+
})]
|
|
109
169
|
}),
|
|
110
|
-
/* @__PURE__ */
|
|
111
|
-
/* @__PURE__ */
|
|
170
|
+
/* @__PURE__ */ y(C, {}),
|
|
171
|
+
/* @__PURE__ */ b(D, {
|
|
112
172
|
className: "flex-row gap-2 pt-4",
|
|
113
|
-
children: [/* @__PURE__ */
|
|
173
|
+
children: [/* @__PURE__ */ b(S, {
|
|
114
174
|
variant: "outline",
|
|
115
175
|
size: "sm",
|
|
116
|
-
onClick:
|
|
176
|
+
onClick: re,
|
|
117
177
|
className: "flex-1",
|
|
118
|
-
children: [/* @__PURE__ */
|
|
119
|
-
}), /* @__PURE__ */
|
|
178
|
+
children: [/* @__PURE__ */ y(_, { className: "size-3 mr-1.5" }), e(F, "dashboardCustomizer.resetToDefaults", "Reset to defaults")]
|
|
179
|
+
}), /* @__PURE__ */ b(S, {
|
|
120
180
|
size: "sm",
|
|
121
|
-
onClick:
|
|
181
|
+
onClick: le,
|
|
122
182
|
disabled: !z || N,
|
|
123
183
|
className: "flex-1",
|
|
124
|
-
children: [N ? /* @__PURE__ */ g
|
|
184
|
+
children: [N ? /* @__PURE__ */ y(g, { className: "size-3 mr-1.5 animate-spin" }) : /* @__PURE__ */ y(v, { className: "size-3 mr-1.5" }), e(F, "dashboardCustomizer.saveLayout", "Save layout")]
|
|
125
185
|
})]
|
|
126
186
|
})
|
|
127
187
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardCustomizer.js","names":[],"sources":["../../../src/pages/dashboard/DashboardCustomizer.tsx"],"sourcesContent":["/**\n * DashboardCustomizer — Sheet overlay for customizing dashboard widgets.\n *\n * Allows users to toggle widgets on/off, reorder them, and save their layout.\n */\nimport { useState, useCallback, useEffect } from 'react';\nimport {\n Sheet,\n SheetContent,\n SheetHeader,\n SheetTitle,\n SheetDescription,\n SheetFooter,\n Button,\n Switch,\n Badge,\n Separator,\n} from '@burdenoff/fe-libs/ui';\nimport { ArrowUp, ArrowDown, RotateCcw, Save, Loader2, GripVertical } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../../utils/i18n';\nimport type { WidgetConfig } from './types';\nimport {\n WIDGET_DEFINITIONS,\n DEFAULT_DASHBOARD_WIDGETS_BY_PRODUCT,\n DEFAULT_DASHBOARD_WIDGETS,\n isWidgetAvailable,\n resolveWidgetCopy,\n} from './config';\nimport { nativeImpact } from '../../utils/nativeBridge';\n\ninterface DashboardCustomizerProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n widgets: WidgetConfig[];\n onSave: (widgets: WidgetConfig[]) => Promise<void>;\n isSaving: boolean;\n productId?: string;\n}\n\nexport function DashboardCustomizer({\n open,\n onOpenChange,\n widgets,\n onSave,\n isSaving,\n productId = 'workspaces',\n}: DashboardCustomizerProps) {\n const { t } = useI18n();\n // Widget titles/descriptions live in module-scope WIDGET_DEFINITIONS, so they\n // are translated here at render time with literal keys. [BOFF-5997]\n const trLiteral = (key: string, fallback: string): string => tWithFallback(t, key, fallback);\n\n const [localWidgets, setLocalWidgets] = useState<WidgetConfig[]>(widgets);\n const [hasChanges, setHasChanges] = useState(false);\n\n // Sync local state when sheet opens or widgets prop changes while open\n useEffect(() => {\n if (open) {\n setLocalWidgets([...widgets]);\n setHasChanges(false);\n }\n }, [open, widgets]);\n\n const toggleWidget = useCallback((widgetId: string) => {\n setLocalWidgets((prev) =>\n prev.map((w) => (w.widgetId === widgetId ? { ...w, enabled: !w.enabled } : w))\n );\n setHasChanges(true);\n }, []);\n\n const moveWidget = useCallback((widgetId: string, direction: 'up' | 'down') => {\n setLocalWidgets((prev) => {\n const sorted = [...prev].sort((a, b) => a.order - b.order);\n const idx = sorted.findIndex((w) => w.widgetId === widgetId);\n if (idx < 0) return prev;\n\n const swapIdx = direction === 'up' ? idx - 1 : idx + 1;\n if (swapIdx < 0 || swapIdx >= sorted.length) return prev;\n\n // Swap orders\n const tempOrder = sorted[idx].order;\n sorted[idx] = { ...sorted[idx], order: sorted[swapIdx].order };\n sorted[swapIdx] = { ...sorted[swapIdx], order: tempOrder };\n\n return sorted;\n });\n setHasChanges(true);\n }, []);\n\n const resetToDefaults = useCallback(() => {\n const defaults = DEFAULT_DASHBOARD_WIDGETS_BY_PRODUCT[productId] ?? DEFAULT_DASHBOARD_WIDGETS;\n setLocalWidgets([...defaults]);\n setHasChanges(true);\n }, [productId]);\n\n const handleSave = useCallback(async () => {\n await onSave(localWidgets);\n setHasChanges(false);\n onOpenChange(false);\n }, [localWidgets, onSave, onOpenChange]);\n\n const sorted = [...localWidgets].sort((a, b) => a.order - b.order);\n\n return (\n <Sheet open={open} onOpenChange={onOpenChange}>\n <SheetContent className=\"w-full sm:max-w-md overflow-y-auto\" side=\"right\">\n <SheetHeader>\n <SheetTitle>\n {tWithFallback(t, 'dashboardCustomizer.title', 'Customize Dashboard')}\n </SheetTitle>\n <SheetDescription>\n {tWithFallback(\n t,\n 'dashboardCustomizer.description',\n 'Toggle widgets on or off and reorder them to personalize your dashboard.'\n )}\n </SheetDescription>\n </SheetHeader>\n\n <div className=\"py-4 space-y-1\">\n {sorted.map((widget, idx) => {\n const definition = WIDGET_DEFINITIONS[widget.widgetId];\n if (!definition) return null;\n if (!isWidgetAvailable(definition, productId)) return null;\n\n return (\n <div\n key={widget.widgetId}\n className={`flex items-center gap-3 p-3 rounded-lg border transition-colors ${\n widget.enabled\n ? 'border-border-default bg-bg-surface'\n : 'border-transparent bg-bg-sunken/30 opacity-60'\n }`}\n >\n <GripVertical className=\"size-4 text-text-secondary shrink-0\" />\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">\n {resolveWidgetCopy(trLiteral, definition).title}\n </span>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {definition.defaultSize}\n </Badge>\n </div>\n <p className=\"text-xs text-text-secondary truncate\">\n {resolveWidgetCopy(trLiteral, definition).description}\n </p>\n </div>\n\n <div className=\"flex items-center gap-1 shrink-0\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"size-7 p-0\"\n disabled={idx === 0}\n onClick={() => moveWidget(widget.widgetId, 'up')}\n >\n <ArrowUp className=\"size-3\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"size-7 p-0\"\n disabled={idx === sorted.length - 1}\n onClick={() => moveWidget(widget.widgetId, 'down')}\n >\n <ArrowDown className=\"size-3\" />\n </Button>\n <Switch\n checked={widget.enabled}\n onCheckedChange={() => {\n void nativeImpact('medium');\n toggleWidget(widget.widgetId);\n }}\n disabled={!definition.removable}\n className=\"ml-1\"\n />\n </div>\n </div>\n );\n })}\n </div>\n\n <Separator />\n\n <SheetFooter className=\"flex-row gap-2 pt-4\">\n <Button variant=\"outline\" size=\"sm\" onClick={resetToDefaults} className=\"flex-1\">\n <RotateCcw className=\"size-3 mr-1.5\" />\n {tWithFallback(t, 'dashboardCustomizer.resetToDefaults', 'Reset to defaults')}\n </Button>\n <Button\n size=\"sm\"\n onClick={handleSave}\n disabled={!hasChanges || isSaving}\n className=\"flex-1\"\n >\n {isSaving ? (\n <Loader2 className=\"size-3 mr-1.5 animate-spin\" />\n ) : (\n <Save className=\"size-3 mr-1.5\" />\n )}\n {tWithFallback(t, 'dashboardCustomizer.saveLayout', 'Save layout')}\n </Button>\n </SheetFooter>\n </SheetContent>\n </Sheet>\n );\n}\n"],"mappings":";;;;;;;;;AAwCA,SAAgB,EAAoB,EAClC,SACA,iBACA,YACA,WACA,aACA,eAAY,gBACe;CAC3B,IAAM,EAAE,SAAM,GAAS,EAGjB,KAAa,GAAa,MAA6B,EAAc,GAAG,GAAK,EAAS,EAEtF,CAAC,GAAc,KAAmB,EAAyB,EAAQ,EACnE,CAAC,GAAY,KAAiB,EAAS,GAAM;AAGnD,SAAgB;AACd,EAAI,MACF,EAAgB,CAAC,GAAG,EAAQ,CAAC,EAC7B,EAAc,GAAM;IAErB,CAAC,GAAM,EAAQ,CAAC;CAEnB,IAAM,IAAe,GAAa,MAAqB;AAIrD,EAHA,GAAiB,MACf,EAAK,KAAK,MAAO,EAAE,aAAa,IAAW;GAAE,GAAG;GAAG,SAAS,CAAC,EAAE;GAAS,GAAG,EAAG,CAC/E,EACD,EAAc,GAAK;IAClB,EAAE,CAAC,EAEA,IAAa,GAAa,GAAkB,MAA6B;AAgB7E,EAfA,GAAiB,MAAS;GACxB,IAAM,IAAS,CAAC,GAAG,EAAK,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,EACpD,IAAM,EAAO,WAAW,MAAM,EAAE,aAAa,EAAS;AAC5D,OAAI,IAAM,EAAG,QAAO;GAEpB,IAAM,IAAU,MAAc,OAAO,IAAM,IAAI,IAAM;AACrD,OAAI,IAAU,KAAK,KAAW,EAAO,OAAQ,QAAO;GAGpD,IAAM,IAAY,EAAO,GAAK;AAI9B,UAHA,EAAO,KAAO;IAAE,GAAG,EAAO;IAAM,OAAO,EAAO,GAAS;IAAO,EAC9D,EAAO,KAAW;IAAE,GAAG,EAAO;IAAU,OAAO;IAAW,EAEnD;IACP,EACF,EAAc,GAAK;IAClB,EAAE,CAAC,EAEA,IAAkB,QAAkB;AAGxC,EADA,EAAgB,CAAC,GADA,EAAqC,MAAc,EACvC,CAAC,EAC9B,EAAc,GAAK;IAClB,CAAC,EAAU,CAAC,EAET,IAAa,EAAY,YAAY;AAGzC,EAFA,MAAM,EAAO,EAAa,EAC1B,EAAc,GAAM,EACpB,EAAa,GAAM;IAClB;EAAC;EAAc;EAAQ;EAAa,CAAC,EAElC,IAAS,CAAC,GAAG,EAAa,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM;AAElE,QACE,kBAAC,GAAD;EAAa;EAAoB;YAC/B,kBAAC,GAAD;GAAc,WAAU;GAAqC,MAAK;aAAlE;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACG,EAAc,GAAG,6BAA6B,sBAAsB,EAC1D,CAAA,EACb,kBAAC,GAAD,EAAA,UACG,EACC,GACA,mCACA,2EACD,EACgB,CAAA,CACP,EAAA,CAAA;IAEd,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAO,KAAK,GAAQ,MAAQ;MAC3B,IAAM,IAAa,EAAmB,EAAO;AAI7C,aAHI,CAAC,KACD,CAAC,EAAkB,GAAY,EAAU,GAAS,OAGpD,kBAAC,OAAD;OAEE,WAAW,mEACT,EAAO,UACH,wCACA;iBALR;QAQE,kBAAC,GAAD,EAAc,WAAU,uCAAwC,CAAA;QAEhE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAkB,GAAW,EAAW,CAAC;WACrC,CAAA,EACP,kBAAC,GAAD;WAAO,SAAQ;WAAU,WAAU;qBAChC,EAAW;WACN,CAAA,CACJ;aACN,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAkB,GAAW,EAAW,CAAC;UACxC,CAAA,CACA;;QAEN,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,GAAD;WACE,SAAQ;WACR,MAAK;WACL,WAAU;WACV,UAAU,MAAQ;WAClB,eAAe,EAAW,EAAO,UAAU,KAAK;qBAEhD,kBAAC,GAAD,EAAS,WAAU,UAAW,CAAA;WACvB,CAAA;UACT,kBAAC,GAAD;WACE,SAAQ;WACR,MAAK;WACL,WAAU;WACV,UAAU,MAAQ,EAAO,SAAS;WAClC,eAAe,EAAW,EAAO,UAAU,OAAO;qBAElD,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;WACzB,CAAA;UACT,kBAAC,GAAD;WACE,SAAS,EAAO;WAChB,uBAAuB;AAErB,YADK,EAAa,SAAS,EAC3B,EAAa,EAAO,SAAS;;WAE/B,UAAU,CAAC,EAAW;WACtB,WAAU;WACV,CAAA;UACE;;QACF;SApDC,EAAO,SAoDR;OAER;KACE,CAAA;IAEN,kBAAC,GAAD,EAAa,CAAA;IAEb,kBAAC,GAAD;KAAa,WAAU;eAAvB,CACE,kBAAC,GAAD;MAAQ,SAAQ;MAAU,MAAK;MAAK,SAAS;MAAiB,WAAU;gBAAxE,CACE,kBAAC,GAAD,EAAW,WAAU,iBAAkB,CAAA,EACtC,EAAc,GAAG,uCAAuC,oBAAoB,CACtE;SACT,kBAAC,GAAD;MACE,MAAK;MACL,SAAS;MACT,UAAU,CAAC,KAAc;MACzB,WAAU;gBAJZ,CAMG,IACC,kBAAC,GAAD,EAAS,WAAU,8BAA+B,CAAA,GAElD,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,EAEnC,EAAc,GAAG,kCAAkC,cAAc,CAC3D;QACG;;IACD;;EACT,CAAA"}
|
|
1
|
+
{"version":3,"file":"DashboardCustomizer.js","names":[],"sources":["../../../src/pages/dashboard/DashboardCustomizer.tsx"],"sourcesContent":["/**\n * DashboardCustomizer — Sheet overlay for customizing dashboard widgets.\n *\n * Allows users to toggle widgets on/off, reorder them, and save their layout.\n */\nimport { useState, useCallback, useEffect, useRef } from 'react';\nimport {\n Sheet,\n SheetContent,\n SheetHeader,\n SheetTitle,\n SheetDescription,\n SheetFooter,\n Button,\n Switch,\n Badge,\n Separator,\n} from '@burdenoff/fe-libs/ui';\nimport { ArrowUp, ArrowDown, RotateCcw, Save, Loader2, GripVertical } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../../utils/i18n';\nimport type { WidgetConfig } from './types';\nimport {\n WIDGET_DEFINITIONS,\n DEFAULT_DASHBOARD_WIDGETS_BY_PRODUCT,\n DEFAULT_DASHBOARD_WIDGETS,\n isWidgetAvailable,\n resolveWidgetCopy,\n} from './config';\nimport { nativeImpact } from '../../utils/nativeBridge';\nimport { reorderByDropIndex, dropPositionFromPointer, type DropPosition } from './reorder';\n\ninterface DashboardCustomizerProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n widgets: WidgetConfig[];\n onSave: (widgets: WidgetConfig[]) => Promise<void>;\n isSaving: boolean;\n productId?: string;\n}\n\nexport function DashboardCustomizer({\n open,\n onOpenChange,\n widgets,\n onSave,\n isSaving,\n productId = 'workspaces',\n}: DashboardCustomizerProps) {\n const { t } = useI18n();\n // Widget titles/descriptions live in module-scope WIDGET_DEFINITIONS, so they\n // are translated here at render time with literal keys. [BOFF-5997]\n const trLiteral = useCallback(\n (key: string, fallback: string): string => tWithFallback(t, key, fallback),\n [t]\n );\n\n const [localWidgets, setLocalWidgets] = useState<WidgetConfig[]>(widgets);\n const [hasChanges, setHasChanges] = useState(false);\n const [draggingId, setDraggingId] = useState<string | null>(null);\n const [dropTargetId, setDropTargetId] = useState<string | null>(null);\n /** Live-region announcement for screen readers after a successful reorder. */\n const [liveAnnouncement, setLiveAnnouncement] = useState('');\n const rowRefs = useRef(new Map<string, HTMLDivElement>());\n /** Last (from, to, position) applied during the current drag — used to skip\n * repeated `dragOver` firings that would otherwise churn renders. */\n const lastAppliedMoveRef = useRef<{\n fromId: string;\n toId: string;\n position: DropPosition;\n } | null>(null);\n /** Pending rAF id for coalescing `dragOver` reorders to one per frame. */\n const dragOverRafRef = useRef(0);\n\n // Sync local state when sheet opens or widgets prop changes while open\n useEffect(() => {\n if (open) {\n setLocalWidgets([...widgets]);\n setHasChanges(false);\n }\n }, [open, widgets]);\n\n const toggleWidget = useCallback((widgetId: string) => {\n setLocalWidgets((prev) =>\n prev.map((w) => (w.widgetId === widgetId ? { ...w, enabled: !w.enabled } : w))\n );\n setHasChanges(true);\n }, []);\n\n const moveWidget = useCallback((widgetId: string, direction: 'up' | 'down') => {\n setLocalWidgets((prev) => {\n const sorted = [...prev].sort((a, b) => a.order - b.order);\n const idx = sorted.findIndex((w) => w.widgetId === widgetId);\n if (idx < 0) return prev;\n\n const swapIdx = direction === 'up' ? idx - 1 : idx + 1;\n if (swapIdx < 0 || swapIdx >= sorted.length) return prev;\n\n // Swap orders\n const tempOrder = sorted[idx].order;\n sorted[idx] = { ...sorted[idx], order: sorted[swapIdx].order };\n sorted[swapIdx] = { ...sorted[swapIdx], order: tempOrder };\n\n return sorted;\n });\n setHasChanges(true);\n }, []);\n\n const resetToDefaults = useCallback(() => {\n const defaults = DEFAULT_DASHBOARD_WIDGETS_BY_PRODUCT[productId] ?? DEFAULT_DASHBOARD_WIDGETS;\n setLocalWidgets([...defaults]);\n setHasChanges(true);\n }, [productId]);\n\n // ─── Drag-and-drop (HTML5 native) ──────────────────────────────────────────\n // The 6-dot icon next to each widget is the drag handle. Up/down arrow\n // buttons remain the keyboard-accessible reorder path. [BOFF-7219]\n const setRowRef = useCallback(\n (widgetId: string) => (el: HTMLDivElement | null) => {\n if (el) rowRefs.current.set(widgetId, el);\n else rowRefs.current.delete(widgetId);\n },\n []\n );\n\n const handleDragStart = useCallback(\n (widgetId: string, event: React.DragEvent<HTMLSpanElement>) => {\n setDraggingId(widgetId);\n lastAppliedMoveRef.current = null;\n cancelAnimationFrame(dragOverRafRef.current);\n event.dataTransfer.effectAllowed = 'move';\n // Firefox refuses to fire drag events without a setData payload.\n event.dataTransfer.setData('text/plain', widgetId);\n },\n []\n );\n\n const handleDragOver = useCallback(\n (widgetId: string, event: React.DragEvent<HTMLDivElement>) => {\n if (!draggingId || draggingId === widgetId) return;\n event.preventDefault();\n event.dataTransfer.dropEffect = 'move';\n setDropTargetId(widgetId);\n\n // Coalesce the expensive reorder work to once per animation frame.\n // `dragOver` fires at ~60 FPS even when the pointer is stationary;\n // without this guard every firing triggers a re-sort + re-render.\n cancelAnimationFrame(dragOverRafRef.current);\n dragOverRafRef.current = requestAnimationFrame(() => {\n const row = rowRefs.current.get(widgetId);\n if (!row) return;\n const position = dropPositionFromPointer(event.clientY, row.getBoundingClientRect());\n const last = lastAppliedMoveRef.current;\n if (\n last &&\n last.fromId === draggingId &&\n last.toId === widgetId &&\n last.position === position\n ) {\n return; // same move as last firing — skip to avoid re-render churn\n }\n lastAppliedMoveRef.current = { fromId: draggingId, toId: widgetId, position };\n setLocalWidgets((prev) => reorderByDropIndex(prev, draggingId, widgetId, position));\n setHasChanges(true);\n });\n },\n [draggingId]\n );\n\n const handleDragLeave = useCallback(\n (widgetId: string, event: React.DragEvent<HTMLDivElement>) => {\n // Only clear the drop target when the pointer truly leaves the row\n // (entering a child element bubbles dragleave on the parent).\n if (event.currentTarget.contains(event.relatedTarget as Node | null)) return;\n if (dropTargetId === widgetId) setDropTargetId(null);\n },\n [dropTargetId]\n );\n\n const handleDrop = useCallback((widgetId: string, event: React.DragEvent<HTMLDivElement>) => {\n event.preventDefault();\n if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';\n cancelAnimationFrame(dragOverRafRef.current);\n setDraggingId(null);\n setDropTargetId(null);\n }, []);\n\n const handleDragEnd = useCallback(() => {\n cancelAnimationFrame(dragOverRafRef.current);\n // Announce the final position of the dragged widget for screen-reader\n // users. aria-live=\"assertive\" reads the new text immediately.\n if (draggingId) {\n const finalWidgets = [...localWidgets].sort((a, b) => a.order - b.order);\n const finalIdx = finalWidgets.findIndex((w) => w.widgetId === draggingId);\n const definition = WIDGET_DEFINITIONS[draggingId];\n const title = definition ? resolveWidgetCopy(trLiteral, definition).title : draggingId;\n if (finalIdx >= 0) {\n setLiveAnnouncement(\n tWithFallback(\n t,\n 'dashboardCustomizer.reorderAnnouncement',\n `${title} moved to position ${finalIdx + 1} of ${finalWidgets.length}`\n )\n );\n }\n }\n setDraggingId(null);\n setDropTargetId(null);\n lastAppliedMoveRef.current = null;\n }, [draggingId, localWidgets, trLiteral, t]);\n\n const handleSave = useCallback(async () => {\n await onSave(localWidgets);\n setHasChanges(false);\n onOpenChange(false);\n }, [localWidgets, onSave, onOpenChange]);\n\n const sorted = [...localWidgets].sort((a, b) => a.order - b.order);\n\n return (\n <Sheet open={open} onOpenChange={onOpenChange}>\n <SheetContent className=\"w-full sm:max-w-md overflow-y-auto\" side=\"right\">\n <SheetHeader>\n <SheetTitle>\n {tWithFallback(t, 'dashboardCustomizer.title', 'Customize Dashboard')}\n </SheetTitle>\n <SheetDescription>\n {tWithFallback(\n t,\n 'dashboardCustomizer.description',\n 'Toggle widgets on or off and reorder them to personalize your dashboard.'\n )}\n </SheetDescription>\n </SheetHeader>\n\n <div\n role=\"listbox\"\n aria-label={tWithFallback(t, 'dashboardCustomizer.listLabel', 'Dashboard widgets')}\n className=\"py-4 space-y-1\"\n >\n {/* Live region for screen-reader announcements after a successful\n reorder. `aria-live=\"assertive\"` interrupts the user to make\n the new position obvious; the region itself is visually\n hidden via `sr-only`. */}\n <div aria-live=\"assertive\" className=\"sr-only\">\n {liveAnnouncement}\n </div>\n {sorted.map((widget, idx) => {\n const definition = WIDGET_DEFINITIONS[widget.widgetId];\n if (!definition) return null;\n if (!isWidgetAvailable(definition, productId)) return null;\n\n const isDragging = draggingId === widget.widgetId;\n const isDropTarget = dropTargetId === widget.widgetId && draggingId !== null;\n const widgetTitle = resolveWidgetCopy(trLiteral, definition).title;\n\n return (\n <div\n key={widget.widgetId}\n ref={setRowRef(widget.widgetId)}\n role=\"option\"\n aria-roledescription={tWithFallback(\n t,\n 'dashboardCustomizer.sortableItem',\n 'sortable item'\n )}\n aria-selected={isDragging}\n aria-label={tWithFallback(\n t,\n 'dashboardCustomizer.widgetRow',\n `Widget: ${widgetTitle}. Use the arrow buttons to reorder.`\n )}\n onDragOver={(e) => handleDragOver(widget.widgetId, e)}\n onDragLeave={(e) => handleDragLeave(widget.widgetId, e)}\n onDrop={(e) => handleDrop(widget.widgetId, e)}\n data-widget-id={widget.widgetId}\n className={`flex items-center gap-3 p-3 rounded-lg border transition-colors ${\n widget.enabled\n ? 'border-border-default bg-bg-surface'\n : 'border-transparent bg-bg-sunken/30 opacity-60'\n } ${isDragging ? 'opacity-50' : ''} ${\n isDropTarget ? 'ring-2 ring-border-strong border-transparent' : ''\n }`}\n >\n <span\n draggable\n onDragStart={(e) => handleDragStart(widget.widgetId, e)}\n onDragEnd={handleDragEnd}\n aria-label={tWithFallback(\n t,\n 'dashboardCustomizer.dragHandle',\n `Drag handle for ${widgetTitle}`\n )}\n className=\"cursor-grab active:cursor-grabbing touch-none select-none\"\n >\n <GripVertical aria-hidden className=\"size-4 text-text-secondary shrink-0\" />\n </span>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">{widgetTitle}</span>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {definition.defaultSize}\n </Badge>\n </div>\n <p className=\"text-xs text-text-secondary truncate\">\n {resolveWidgetCopy(trLiteral, definition).description}\n </p>\n </div>\n\n <div className=\"flex items-center gap-1 shrink-0\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"size-7 p-0\"\n disabled={idx === 0}\n onClick={() => moveWidget(widget.widgetId, 'up')}\n aria-label={tWithFallback(\n t,\n 'dashboardCustomizer.moveUp',\n `Move ${widgetTitle} up`\n )}\n >\n <ArrowUp className=\"size-3\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"size-7 p-0\"\n disabled={idx === sorted.length - 1}\n onClick={() => moveWidget(widget.widgetId, 'down')}\n aria-label={tWithFallback(\n t,\n 'dashboardCustomizer.moveDown',\n `Move ${widgetTitle} down`\n )}\n >\n <ArrowDown className=\"size-3\" />\n </Button>\n <Switch\n checked={widget.enabled}\n onCheckedChange={() => {\n void nativeImpact('medium');\n toggleWidget(widget.widgetId);\n }}\n disabled={!definition.removable}\n className=\"ml-1\"\n />\n </div>\n </div>\n );\n })}\n </div>\n\n <Separator />\n\n <SheetFooter className=\"flex-row gap-2 pt-4\">\n <Button variant=\"outline\" size=\"sm\" onClick={resetToDefaults} className=\"flex-1\">\n <RotateCcw className=\"size-3 mr-1.5\" />\n {tWithFallback(t, 'dashboardCustomizer.resetToDefaults', 'Reset to defaults')}\n </Button>\n <Button\n size=\"sm\"\n onClick={handleSave}\n disabled={!hasChanges || isSaving}\n className=\"flex-1\"\n >\n {isSaving ? (\n <Loader2 className=\"size-3 mr-1.5 animate-spin\" />\n ) : (\n <Save className=\"size-3 mr-1.5\" />\n )}\n {tWithFallback(t, 'dashboardCustomizer.saveLayout', 'Save layout')}\n </Button>\n </SheetFooter>\n </SheetContent>\n </Sheet>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/pages/dashboard/reorder.ts
|
|
2
|
+
var e = 10;
|
|
3
|
+
function t(t) {
|
|
4
|
+
return t.map((t, n) => ({
|
|
5
|
+
...t,
|
|
6
|
+
order: (n + 1) * e
|
|
7
|
+
}));
|
|
8
|
+
}
|
|
9
|
+
function n(e, n, r, i) {
|
|
10
|
+
if (n === r) return e;
|
|
11
|
+
let a = [...e].sort((e, t) => e.order - t.order), o = a.findIndex((e) => e.widgetId === n), s = a.findIndex((e) => e.widgetId === r);
|
|
12
|
+
if (o < 0 || s < 0) return e;
|
|
13
|
+
let c = a[o], l = a.filter((e, t) => t !== o), u = i === "above" ? l.findIndex((e) => e.widgetId === r) : l.findIndex((e) => e.widgetId === r) + 1;
|
|
14
|
+
return t([
|
|
15
|
+
...l.slice(0, u),
|
|
16
|
+
c,
|
|
17
|
+
...l.slice(u)
|
|
18
|
+
]);
|
|
19
|
+
}
|
|
20
|
+
function r(e, t) {
|
|
21
|
+
return e < t.top + t.height / 2 ? "above" : "below";
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { r as dropPositionFromPointer, n as reorderByDropIndex };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=reorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reorder.js","names":[],"sources":["../../../src/pages/dashboard/reorder.ts"],"sourcesContent":["/**\n * Pure reorder helpers for the dashboard customizer drag-and-drop.\n *\n * Extracted from `DashboardCustomizer.tsx` so the ordering logic can be unit-\n * tested without DOM/React Test Renderer. The caller owns the live `WidgetConfig[]`\n * state and is responsible for cloning before mutating.\n *\n * Invariants:\n * - Input widgets are expected to already be sorted by `order` ascending. The\n * helper re-sorts defensively so callers don't have to.\n * - `enabled` and `size` are preserved on every widget; only `order` changes.\n * - Drop positions are evaluated relative to the row the pointer is over: the\n * upper half means \"insert before\", the lower half means \"insert after\".\n */\nimport type { WidgetConfig } from './types';\n\n/** Side of the target row the pointer is on, relative to the row midpoint. */\nexport type DropPosition = 'above' | 'below';\n\nconst ORDER_STEP = 10;\n\n/**\n * Re-number `order` so it is strictly increasing by `ORDER_STEP` from the\n * given array's order. Returns a fresh array; input is not mutated.\n */\nexport function renumberOrders(widgets: WidgetConfig[]): WidgetConfig[] {\n return widgets.map((w, idx) => ({ ...w, order: (idx + 1) * ORDER_STEP }));\n}\n\n/**\n * Move `fromId` to a position adjacent to `toId`.\n *\n * - `position === 'above'` — `fromId` ends up immediately before `toId`.\n * - `position === 'below'` — `fromId` ends up immediately after `toId`.\n *\n * If either id is missing, or `fromId === toId`, the input is returned\n * unchanged (referential equality preserved). Adjacent swaps are valid\n * moves — flicker prevention is the caller's responsibility (see\n * `DashboardCustomizer.handleDragOver`, which tracks the last-applied\n * move via a ref). [BOFF-7219]\n */\nexport function reorderByDropIndex(\n widgets: WidgetConfig[],\n fromId: string,\n toId: string,\n position: DropPosition\n): WidgetConfig[] {\n if (fromId === toId) return widgets;\n\n const sorted = [...widgets].sort((a, b) => a.order - b.order);\n const fromIdx = sorted.findIndex((w) => w.widgetId === fromId);\n const toIdx = sorted.findIndex((w) => w.widgetId === toId);\n if (fromIdx < 0 || toIdx < 0) return widgets;\n\n const moving = sorted[fromIdx];\n const without = sorted.filter((_, i) => i !== fromIdx);\n const insertIdx =\n position === 'above'\n ? without.findIndex((w) => w.widgetId === toId)\n : without.findIndex((w) => w.widgetId === toId) + 1;\n\n const reordered = [...without.slice(0, insertIdx), moving, ...without.slice(insertIdx)];\n return renumberOrders(reordered);\n}\n\n/**\n * Resolve the drop position from a pointer event and a target row's bounding\n * rect. `clientY` above the midpoint yields 'above', otherwise 'below'.\n */\nexport function dropPositionFromPointer(clientY: number, rect: DOMRect): DropPosition {\n return clientY < rect.top + rect.height / 2 ? 'above' : 'below';\n}\n"],"mappings":";AAmBA,IAAM,IAAa;AAMnB,SAAgB,EAAe,GAAyC;AACtE,QAAO,EAAQ,KAAK,GAAG,OAAS;EAAE,GAAG;EAAG,QAAQ,IAAM,KAAK;EAAY,EAAE;;AAe3E,SAAgB,EACd,GACA,GACA,GACA,GACgB;AAChB,KAAI,MAAW,EAAM,QAAO;CAE5B,IAAM,IAAS,CAAC,GAAG,EAAQ,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,EACvD,IAAU,EAAO,WAAW,MAAM,EAAE,aAAa,EAAO,EACxD,IAAQ,EAAO,WAAW,MAAM,EAAE,aAAa,EAAK;AAC1D,KAAI,IAAU,KAAK,IAAQ,EAAG,QAAO;CAErC,IAAM,IAAS,EAAO,IAChB,IAAU,EAAO,QAAQ,GAAG,MAAM,MAAM,EAAQ,EAChD,IACJ,MAAa,UACT,EAAQ,WAAW,MAAM,EAAE,aAAa,EAAK,GAC7C,EAAQ,WAAW,MAAM,EAAE,aAAa,EAAK,GAAG;AAGtD,QAAO,EADW;EAAC,GAAG,EAAQ,MAAM,GAAG,EAAU;EAAE;EAAQ,GAAG,EAAQ,MAAM,EAAU;EAAC,CACvD;;AAOlC,SAAgB,EAAwB,GAAiB,GAA6B;AACpF,QAAO,IAAU,EAAK,MAAM,EAAK,SAAS,IAAI,UAAU"}
|
|
@@ -5,10 +5,16 @@ import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
|
5
5
|
import { GlassCard as l, GradientText as u } from "@burdenoff/fe-libs/ui";
|
|
6
6
|
import { useI18n as d } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
7
7
|
//#region src/pages/dashboard/widgets/WelcomeWidget.tsx
|
|
8
|
-
function f({ icon: e, label: t, value: n }) {
|
|
8
|
+
function f({ icon: e, label: t, value: n, onClick: r }) {
|
|
9
9
|
return /* @__PURE__ */ s(l, {
|
|
10
10
|
treatment: "metric",
|
|
11
|
-
className:
|
|
11
|
+
className: `p-3${r ? " cursor-pointer hover:shadow-[var(--shadow-pop-hover)] hover:border-border-strong focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]" : ""}`,
|
|
12
|
+
onClick: r,
|
|
13
|
+
onKeyDown: r ? (e) => {
|
|
14
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), r?.());
|
|
15
|
+
} : void 0,
|
|
16
|
+
role: r ? "button" : void 0,
|
|
17
|
+
tabIndex: r ? 0 : void 0,
|
|
12
18
|
children: /* @__PURE__ */ c("div", {
|
|
13
19
|
className: "flex items-center gap-3",
|
|
14
20
|
children: [/* @__PURE__ */ s("div", {
|
|
@@ -24,10 +30,10 @@ function f({ icon: e, label: t, value: n }) {
|
|
|
24
30
|
})
|
|
25
31
|
});
|
|
26
32
|
}
|
|
27
|
-
function p({ workspaceData: p, globalData: m, isLoading: h }) {
|
|
28
|
-
let { t:
|
|
33
|
+
function p({ workspaceData: p, globalData: m, isLoading: h, navigate: g }) {
|
|
34
|
+
let { t: _ } = d(), v = m?.currentUser, y = v?.firstName || v?.name?.split(" ")[0] || e(_, "dashboard.welcome.fallbackName", "there"), b = (/* @__PURE__ */ new Date()).getHours(), x = b < 12 ? e(_, "dashboard.welcome.greetingMorning", "Good morning") : b < 18 ? e(_, "dashboard.welcome.greetingAfternoon", "Good afternoon") : e(_, "dashboard.welcome.greetingEvening", "Good evening"), S = p?.workspaces.total ?? 0, C = p?.projects.total ?? 0, w = p?.groups.totalCount ?? 0, T = p?.recentActivities.length ?? 0;
|
|
29
35
|
return /* @__PURE__ */ s(t, {
|
|
30
|
-
title: e(
|
|
36
|
+
title: e(_, "dashboard.welcome.title", "Dashboard"),
|
|
31
37
|
icon: /* @__PURE__ */ s(a, { className: "size-4" }),
|
|
32
38
|
size: "large",
|
|
33
39
|
isLoading: h,
|
|
@@ -40,37 +46,41 @@ function p({ workspaceData: p, globalData: m, isLoading: h }) {
|
|
|
40
46
|
children: [/* @__PURE__ */ c("h2", {
|
|
41
47
|
className: "text-xl font-bold text-text-primary",
|
|
42
48
|
children: [
|
|
43
|
-
|
|
49
|
+
x,
|
|
44
50
|
", ",
|
|
45
|
-
/* @__PURE__ */ s(u, { children:
|
|
51
|
+
/* @__PURE__ */ s(u, { children: y }),
|
|
46
52
|
"!"
|
|
47
53
|
]
|
|
48
54
|
}), /* @__PURE__ */ s("p", {
|
|
49
55
|
className: "text-sm text-text-secondary mt-1",
|
|
50
|
-
children: e(
|
|
56
|
+
children: e(_, "dashboard.welcome.subtitle", "Here's what's happening across your workspaces today.")
|
|
51
57
|
})]
|
|
52
58
|
}), /* @__PURE__ */ c("div", {
|
|
53
59
|
className: "grid grid-cols-2 sm:grid-cols-4 gap-3",
|
|
54
60
|
children: [
|
|
55
61
|
/* @__PURE__ */ s(f, {
|
|
56
62
|
icon: /* @__PURE__ */ s(r, { className: "size-4" }),
|
|
57
|
-
label: e(
|
|
58
|
-
value:
|
|
63
|
+
label: e(_, "dashboard.welcome.statWorkspaces", "Workspaces"),
|
|
64
|
+
value: S,
|
|
65
|
+
onClick: () => g("/workspaces/list")
|
|
59
66
|
}),
|
|
60
67
|
/* @__PURE__ */ s(f, {
|
|
61
68
|
icon: /* @__PURE__ */ s(i, { className: "size-4" }),
|
|
62
|
-
label: e(
|
|
63
|
-
value:
|
|
69
|
+
label: e(_, "dashboard.welcome.statProjects", "Projects"),
|
|
70
|
+
value: C,
|
|
71
|
+
onClick: () => g("/workspaces/projects")
|
|
64
72
|
}),
|
|
65
73
|
/* @__PURE__ */ s(f, {
|
|
66
74
|
icon: /* @__PURE__ */ s(o, { className: "size-4" }),
|
|
67
|
-
label: e(
|
|
68
|
-
value:
|
|
75
|
+
label: e(_, "dashboard.welcome.statGroups", "Groups"),
|
|
76
|
+
value: w,
|
|
77
|
+
onClick: () => g("/groups")
|
|
69
78
|
}),
|
|
70
79
|
/* @__PURE__ */ s(f, {
|
|
71
80
|
icon: /* @__PURE__ */ s(n, { className: "size-4" }),
|
|
72
|
-
label: e(
|
|
73
|
-
value:
|
|
81
|
+
label: e(_, "dashboard.welcome.statRecentActivity", "Recent Activity"),
|
|
82
|
+
value: T,
|
|
83
|
+
onClick: () => g("/workspaces/activity")
|
|
74
84
|
})
|
|
75
85
|
]
|
|
76
86
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WelcomeWidget.js","names":[],"sources":["../../../../src/pages/dashboard/widgets/WelcomeWidget.tsx"],"sourcesContent":["import { LayoutDashboard, Building2, FolderKanban, Users, Activity } from 'lucide-react';\nimport { GlassCard, GradientText } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { WidgetWrapper } from '../WidgetWrapper';\nimport { tWithFallback } from '../../../utils/i18n';\nimport type { WidgetProps } from '../types';\n\nfunction StatCard({\n icon,\n label,\n value,\n}: {\n icon: React.ReactNode;\n label: string;\n value: number | string;\n}) {\n return (\n <GlassCard
|
|
1
|
+
{"version":3,"file":"WelcomeWidget.js","names":[],"sources":["../../../../src/pages/dashboard/widgets/WelcomeWidget.tsx"],"sourcesContent":["import { LayoutDashboard, Building2, FolderKanban, Users, Activity } from 'lucide-react';\nimport { GlassCard, GradientText } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { WidgetWrapper } from '../WidgetWrapper';\nimport { tWithFallback } from '../../../utils/i18n';\nimport type { WidgetProps } from '../types';\n\nfunction StatCard({\n icon,\n label,\n value,\n onClick,\n}: {\n icon: React.ReactNode;\n label: string;\n value: number | string;\n onClick?: () => void;\n}) {\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n };\n\n return (\n <GlassCard\n treatment=\"metric\"\n className={`p-3${onClick ? ' cursor-pointer hover:shadow-[var(--shadow-pop-hover)] hover:border-border-strong focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]' : ''}`}\n onClick={onClick}\n onKeyDown={onClick ? handleKeyDown : undefined}\n role={onClick ? 'button' : undefined}\n tabIndex={onClick ? 0 : undefined}\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-md bg-action-primary-bg/10 p-2 text-primary\">{icon}</div>\n <div>\n <p className=\"text-2xl font-bold text-text-primary tabular-nums\">{value}</p>\n <p className=\"text-xs text-text-muted\">{label}</p>\n </div>\n </div>\n </GlassCard>\n );\n}\n\nexport function WelcomeWidget({ workspaceData, globalData, isLoading, navigate }: WidgetProps) {\n const { t } = useI18n();\n const user = globalData?.currentUser;\n const firstName =\n user?.firstName ||\n user?.name?.split(' ')[0] ||\n tWithFallback(t, 'dashboard.welcome.fallbackName', 'there');\n const hour = new Date().getHours();\n const greeting =\n hour < 12\n ? tWithFallback(t, 'dashboard.welcome.greetingMorning', 'Good morning')\n : hour < 18\n ? tWithFallback(t, 'dashboard.welcome.greetingAfternoon', 'Good afternoon')\n : tWithFallback(t, 'dashboard.welcome.greetingEvening', 'Good evening');\n\n const totalWorkspaces = workspaceData?.workspaces.total ?? 0;\n const totalProjects = workspaceData?.projects.total ?? 0;\n const totalGroups = workspaceData?.groups.totalCount ?? 0;\n const activityCount = workspaceData?.recentActivities.length ?? 0;\n\n return (\n <WidgetWrapper\n title={tWithFallback(t, 'dashboard.welcome.title', 'Dashboard')}\n icon={<LayoutDashboard className=\"size-4\" />}\n size=\"large\"\n isLoading={isLoading}\n >\n <div className=\"space-y-4\">\n <GlassCard treatment=\"glass\" glow className=\"relative overflow-hidden p-4\">\n <h2 className=\"text-xl font-bold text-text-primary\">\n {greeting}, <GradientText>{firstName}</GradientText>!\n </h2>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tWithFallback(\n t,\n 'dashboard.welcome.subtitle',\n \"Here's what's happening across your workspaces today.\"\n )}\n </p>\n </GlassCard>\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-3\">\n <StatCard\n icon={<Building2 className=\"size-4\" />}\n label={tWithFallback(t, 'dashboard.welcome.statWorkspaces', 'Workspaces')}\n value={totalWorkspaces}\n onClick={() => navigate('/workspaces/list')}\n />\n <StatCard\n icon={<FolderKanban className=\"size-4\" />}\n label={tWithFallback(t, 'dashboard.welcome.statProjects', 'Projects')}\n value={totalProjects}\n onClick={() => navigate('/workspaces/projects')}\n />\n <StatCard\n icon={<Users className=\"size-4\" />}\n label={tWithFallback(t, 'dashboard.welcome.statGroups', 'Groups')}\n value={totalGroups}\n onClick={() => navigate('/groups')}\n />\n <StatCard\n icon={<Activity className=\"size-4\" />}\n label={tWithFallback(t, 'dashboard.welcome.statRecentActivity', 'Recent Activity')}\n value={activityCount}\n onClick={() => navigate('/workspaces/activity')}\n />\n </div>\n </div>\n </WidgetWrapper>\n );\n}\n"],"mappings":";;;;;;;AAOA,SAAS,EAAS,EAChB,SACA,UACA,UACA,cAMC;AAQD,QACE,kBAAC,GAAD;EACE,WAAU;EACV,WAAW,MAAM,IAAU,mLAAmL;EACrM;EACT,WAAW,KAZQ,MAA2B;AAChD,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,KAAW;MAS0B,KAAA;EACrC,MAAM,IAAU,WAAW,KAAA;EAC3B,UAAU,IAAU,IAAI,KAAA;YAExB,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAuD;IAAW,CAAA,EACjF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;cAAqD;IAAU,CAAA,EAC5E,kBAAC,KAAD;IAAG,WAAU;cAA2B;IAAU,CAAA,CAC9C,EAAA,CAAA,CACF;;EACI,CAAA;;AAIhB,SAAgB,EAAc,EAAE,kBAAe,eAAY,cAAW,eAAyB;CAC7F,IAAM,EAAE,SAAM,GAAS,EACjB,IAAO,GAAY,aACnB,IACJ,GAAM,aACN,GAAM,MAAM,MAAM,IAAI,CAAC,MACvB,EAAc,GAAG,kCAAkC,QAAQ,EACvD,qBAAO,IAAI,MAAM,EAAC,UAAU,EAC5B,IACJ,IAAO,KACH,EAAc,GAAG,qCAAqC,eAAe,GACrE,IAAO,KACL,EAAc,GAAG,uCAAuC,iBAAiB,GACzE,EAAc,GAAG,qCAAqC,eAAe,EAEvE,IAAkB,GAAe,WAAW,SAAS,GACrD,IAAgB,GAAe,SAAS,SAAS,GACjD,IAAc,GAAe,OAAO,cAAc,GAClD,IAAgB,GAAe,iBAAiB,UAAU;AAEhE,QACE,kBAAC,GAAD;EACE,OAAO,EAAc,GAAG,2BAA2B,YAAY;EAC/D,MAAM,kBAAC,GAAD,EAAiB,WAAU,UAAW,CAAA;EAC5C,MAAK;EACM;YAEX,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IAAW,WAAU;IAAQ,MAAA;IAAK,WAAU;cAA5C,CACE,kBAAC,MAAD;KAAI,WAAU;eAAd;MACG;MAAS;MAAE,kBAAC,GAAD,EAAA,UAAe,GAAyB,CAAA;;MACjD;QACL,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,GACA,8BACA,wDACD;KACC,CAAA,CACM;OACZ,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;MACtC,OAAO,EAAc,GAAG,oCAAoC,aAAa;MACzE,OAAO;MACP,eAAe,EAAS,mBAAmB;MAC3C,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA;MACzC,OAAO,EAAc,GAAG,kCAAkC,WAAW;MACrE,OAAO;MACP,eAAe,EAAS,uBAAuB;MAC/C,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;MAClC,OAAO,EAAc,GAAG,gCAAgC,SAAS;MACjE,OAAO;MACP,eAAe,EAAS,UAAU;MAClC,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA;MACrC,OAAO,EAAc,GAAG,wCAAwC,kBAAkB;MAClF,OAAO;MACP,eAAe,EAAS,uBAAuB;MAC/C,CAAA;KACE;MACF;;EACQ,CAAA"}
|