@btst/stack 1.10.0 → 1.12.0
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/node_modules/.pnpm/@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/@dnd-kit/core/dist/core.esm.cjs +1 -1
- package/dist/node_modules/.pnpm/@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/@dnd-kit/core/dist/core.esm.mjs +1 -1
- package/dist/node_modules/.pnpm/@dnd-kit_sortable@10.0.0_@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0__react@19.2.0/node_modules/@dnd-kit/sortable/dist/sortable.esm.cjs +77 -0
- package/dist/node_modules/.pnpm/@dnd-kit_sortable@10.0.0_@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0__react@19.2.0/node_modules/@dnd-kit/sortable/dist/sortable.esm.mjs +79 -3
- package/dist/node_modules/.pnpm/@radix-ui_react-avatar@1.1.11_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react_850cfbef1935a6e49a6ad6c93c7ca70d/node_modules/@radix-ui/react-avatar/dist/index.cjs +140 -0
- package/dist/node_modules/.pnpm/@radix-ui_react-avatar@1.1.11_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react_850cfbef1935a6e49a6ad6c93c7ca70d/node_modules/@radix-ui/react-avatar/dist/index.mjs +119 -0
- package/dist/node_modules/.pnpm/@radix-ui_react-context@1.1.3_@types_react@19.2.6_react@19.2.0/node_modules/@radix-ui/react-context/dist/index.cjs +80 -0
- package/dist/node_modules/.pnpm/@radix-ui_react-context@1.1.3_@types_react@19.2.6_react@19.2.0/node_modules/@radix-ui/react-context/dist/index.mjs +64 -0
- package/dist/node_modules/.pnpm/@radix-ui_react-use-is-hydrated@0.1.0_@types_react@19.2.6_react@19.2.0/node_modules/@radix-ui/react-use-is-hydrated/dist/index.cjs +18 -0
- package/dist/node_modules/.pnpm/@radix-ui_react-use-is-hydrated@0.1.0_@types_react@19.2.6_react@19.2.0/node_modules/@radix-ui/react-use-is-hydrated/dist/index.mjs +16 -0
- package/dist/packages/better-stack/src/plugins/kanban/api/plugin.cjs +846 -0
- package/dist/packages/better-stack/src/plugins/kanban/api/plugin.mjs +844 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/board-form.cjs +85 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/board-form.mjs +83 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/column-form.cjs +72 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/column-form.mjs +70 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/task-form.cjs +200 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/task-form.mjs +198 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/loading/board-skeleton.cjs +47 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/loading/board-skeleton.mjs +45 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/loading/boards-list-skeleton.cjs +30 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/loading/boards-list-skeleton.mjs +28 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/404-page.cjs +27 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/404-page.mjs +25 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/board-page.cjs +31 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/board-page.internal.cjs +458 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/board-page.internal.mjs +456 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/board-page.mjs +29 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/boards-list-page.cjs +30 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/boards-list-page.internal.cjs +72 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/boards-list-page.internal.mjs +70 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/boards-list-page.mjs +28 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/new-board-page.cjs +30 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/new-board-page.internal.cjs +51 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/new-board-page.internal.mjs +49 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/new-board-page.mjs +28 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/column-content.cjs +76 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/column-content.mjs +74 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/default-error.cjs +27 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/default-error.mjs +25 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/empty-state.cjs +32 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/empty-state.mjs +30 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/kanban-board.cjs +78 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/kanban-board.mjs +76 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/page-wrapper.cjs +15 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/page-wrapper.mjs +13 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/task-card.cjs +68 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/task-card.mjs +66 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/user-avatar.cjs +32 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/user-avatar.mjs +30 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/hooks/kanban-hooks.cjs +391 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/hooks/kanban-hooks.mjs +381 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/plugin.cjs +290 -0
- package/dist/packages/better-stack/src/plugins/kanban/client/plugin.mjs +288 -0
- package/dist/packages/better-stack/src/plugins/kanban/db.cjs +125 -0
- package/dist/packages/better-stack/src/plugins/kanban/db.mjs +123 -0
- package/dist/packages/better-stack/src/plugins/kanban/schemas.cjs +117 -0
- package/dist/packages/better-stack/src/plugins/kanban/schemas.mjs +102 -0
- package/dist/packages/better-stack/src/plugins/kanban/utils.cjs +49 -0
- package/dist/packages/better-stack/src/plugins/kanban/utils.mjs +45 -0
- package/dist/packages/ui/src/components/avatar.cjs +58 -0
- package/dist/packages/ui/src/components/avatar.mjs +54 -0
- package/dist/packages/ui/src/components/command.cjs +3 -3
- package/dist/packages/ui/src/components/command.mjs +3 -3
- package/dist/packages/ui/src/components/form-builder/index.mjs +2 -2
- package/dist/packages/ui/src/components/kanban.cjs +835 -0
- package/dist/packages/ui/src/components/kanban.mjs +805 -0
- package/dist/packages/ui/src/components/minimal-tiptap/utils.cjs +15 -11
- package/dist/packages/ui/src/components/minimal-tiptap/utils.mjs +15 -11
- package/dist/packages/ui/src/components/popover.cjs +8 -3
- package/dist/packages/ui/src/components/popover.mjs +9 -4
- package/dist/packages/ui/src/components/search-select.cjs +75 -0
- package/dist/packages/ui/src/components/search-select.mjs +73 -0
- package/dist/packages/ui/src/components/ui-builder/index.cjs +9 -7
- package/dist/packages/ui/src/components/ui-builder/index.mjs +9 -7
- package/dist/packages/ui/src/components/ui-builder/internal/canvas/auto-frame.cjs +6 -3
- package/dist/packages/ui/src/components/ui-builder/internal/canvas/auto-frame.mjs +6 -3
- package/dist/packages/ui/src/components/ui-builder/internal/components/add-component-popover.cjs +228 -48
- package/dist/packages/ui/src/components/ui-builder/internal/components/add-component-popover.mjs +228 -48
- package/dist/packages/ui/src/components/ui-builder/internal/components/element-selector.cjs +1 -1
- package/dist/packages/ui/src/components/ui-builder/internal/components/element-selector.mjs +1 -1
- package/dist/packages/ui/src/components/ui-builder/internal/components/error-fallback.cjs +4 -2
- package/dist/packages/ui/src/components/ui-builder/internal/components/error-fallback.mjs +4 -2
- package/dist/packages/ui/src/components/ui-builder/internal/components/multi-select.cjs +6 -3
- package/dist/packages/ui/src/components/ui-builder/internal/components/multi-select.mjs +6 -3
- package/dist/packages/ui/src/components/ui-builder/internal/dnd/draggable-new-component.cjs +67 -0
- package/dist/packages/ui/src/components/ui-builder/internal/dnd/draggable-new-component.mjs +62 -0
- package/dist/packages/ui/src/components/ui-builder/internal/dnd/drop-zone.cjs +181 -37
- package/dist/packages/ui/src/components/ui-builder/internal/dnd/drop-zone.mjs +181 -38
- package/dist/packages/ui/src/components/ui-builder/internal/editor-panel.cjs +1 -1
- package/dist/packages/ui/src/components/ui-builder/internal/editor-panel.mjs +1 -1
- package/dist/packages/ui/src/components/ui-builder/internal/form-fields/classname-control/classname-group-control.cjs +1 -1
- package/dist/packages/ui/src/components/ui-builder/internal/form-fields/classname-control/classname-group-control.mjs +1 -1
- package/dist/packages/ui/src/components/ui-builder/internal/form-fields/classname-control/classname-item-control.cjs +9 -2
- package/dist/packages/ui/src/components/ui-builder/internal/form-fields/classname-control/classname-item-control.mjs +9 -2
- package/dist/packages/ui/src/components/ui-builder/internal/form-fields/iconname-field.cjs +3 -2
- package/dist/packages/ui/src/components/ui-builder/internal/form-fields/iconname-field.mjs +3 -2
- package/dist/packages/ui/src/components/ui-builder/internal/layers-panel.cjs +1 -1
- package/dist/packages/ui/src/components/ui-builder/internal/layers-panel.mjs +1 -1
- package/dist/packages/ui/src/components/ui-builder/internal/props-panel.cjs +17 -5
- package/dist/packages/ui/src/components/ui-builder/internal/props-panel.mjs +17 -5
- package/dist/packages/ui/src/components/ui-builder/internal/utils/render-utils.cjs +70 -16
- package/dist/packages/ui/src/components/ui-builder/internal/utils/render-utils.mjs +73 -20
- package/dist/packages/ui/src/lib/compose-refs.cjs +56 -0
- package/dist/packages/ui/src/lib/compose-refs.mjs +39 -0
- package/dist/packages/ui/src/lib/ui-builder/context/dnd-context-colission-utils.cjs +14 -9
- package/dist/packages/ui/src/lib/ui-builder/context/dnd-context-colission-utils.mjs +14 -9
- package/dist/packages/ui/src/lib/ui-builder/context/dnd-context.cjs +38 -10
- package/dist/packages/ui/src/lib/ui-builder/context/dnd-context.mjs +35 -11
- package/dist/packages/ui/src/lib/ui-builder/context/dnd-contexts.cjs +1 -0
- package/dist/packages/ui/src/lib/ui-builder/context/dnd-contexts.mjs +1 -0
- package/dist/packages/ui/src/lib/ui-builder/context/drag-overlay.cjs +7 -4
- package/dist/packages/ui/src/lib/ui-builder/context/drag-overlay.mjs +7 -4
- package/dist/packages/ui/src/lib/ui-builder/hooks/use-auto-scroll.cjs +4 -4
- package/dist/packages/ui/src/lib/ui-builder/hooks/use-auto-scroll.mjs +4 -4
- package/dist/packages/ui/src/lib/ui-builder/hooks/use-dnd-event-handlers.cjs +53 -16
- package/dist/packages/ui/src/lib/ui-builder/hooks/use-dnd-event-handlers.mjs +53 -16
- package/dist/packages/ui/src/lib/ui-builder/hooks/use-drop-validation.cjs +23 -7
- package/dist/packages/ui/src/lib/ui-builder/hooks/use-drop-validation.mjs +23 -7
- package/dist/packages/ui/src/lib/ui-builder/registry/form-field-overrides.cjs +110 -11
- package/dist/packages/ui/src/lib/ui-builder/registry/form-field-overrides.mjs +111 -13
- package/dist/packages/ui/src/lib/ui-builder/store/editor-store.cjs +3 -2
- package/dist/packages/ui/src/lib/ui-builder/store/editor-store.mjs +3 -2
- package/dist/packages/ui/src/lib/ui-builder/store/layer-store.cjs +53 -7
- package/dist/packages/ui/src/lib/ui-builder/store/layer-store.mjs +54 -8
- package/dist/packages/ui/src/lib/ui-builder/store/layer-utils.cjs +4 -3
- package/dist/packages/ui/src/lib/ui-builder/store/layer-utils.mjs +4 -3
- package/dist/packages/ui/src/lib/ui-builder/utils/variable-resolver.cjs +12 -0
- package/dist/packages/ui/src/lib/ui-builder/utils/variable-resolver.mjs +12 -1
- package/dist/plugins/blog/api/index.d.cts +1 -1
- package/dist/plugins/blog/api/index.d.mts +1 -1
- package/dist/plugins/blog/api/index.d.ts +1 -1
- package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
- package/dist/plugins/blog/client/index.d.cts +1 -1
- package/dist/plugins/blog/client/index.d.mts +1 -1
- package/dist/plugins/blog/client/index.d.ts +1 -1
- package/dist/plugins/blog/query-keys.d.cts +2 -2
- package/dist/plugins/blog/query-keys.d.mts +2 -2
- package/dist/plugins/blog/query-keys.d.ts +2 -2
- package/dist/plugins/kanban/api/index.cjs +7 -0
- package/dist/plugins/kanban/api/index.d.cts +403 -0
- package/dist/plugins/kanban/api/index.d.mts +403 -0
- package/dist/plugins/kanban/api/index.d.ts +403 -0
- package/dist/plugins/kanban/api/index.mjs +1 -0
- package/dist/plugins/kanban/client/components/index.cjs +35 -0
- package/dist/plugins/kanban/client/components/index.d.cts +102 -0
- package/dist/plugins/kanban/client/components/index.d.mts +102 -0
- package/dist/plugins/kanban/client/components/index.d.ts +102 -0
- package/dist/plugins/kanban/client/components/index.mjs +15 -0
- package/dist/plugins/kanban/client/hooks/index.cjs +15 -0
- package/dist/plugins/kanban/client/hooks/index.d.cts +143 -0
- package/dist/plugins/kanban/client/hooks/index.d.mts +143 -0
- package/dist/plugins/kanban/client/hooks/index.d.ts +143 -0
- package/dist/plugins/kanban/client/hooks/index.mjs +1 -0
- package/dist/plugins/kanban/client/index.cjs +7 -0
- package/dist/plugins/kanban/client/index.d.cts +196 -0
- package/dist/plugins/kanban/client/index.d.mts +196 -0
- package/dist/plugins/kanban/client/index.d.ts +196 -0
- package/dist/plugins/kanban/client/index.mjs +1 -0
- package/dist/plugins/kanban/client.css +68 -0
- package/dist/plugins/kanban/query-keys.cjs +105 -0
- package/dist/plugins/kanban/query-keys.d.cts +59 -0
- package/dist/plugins/kanban/query-keys.d.mts +59 -0
- package/dist/plugins/kanban/query-keys.d.ts +59 -0
- package/dist/plugins/kanban/query-keys.mjs +103 -0
- package/dist/plugins/kanban/style.css +7 -0
- package/dist/plugins/ui-builder/client/components/index.d.cts +1 -1
- package/dist/plugins/ui-builder/client/components/index.d.mts +1 -1
- package/dist/plugins/ui-builder/client/components/index.d.ts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.cts +2 -2
- package/dist/plugins/ui-builder/client/hooks/index.d.mts +2 -2
- package/dist/plugins/ui-builder/client/hooks/index.d.ts +2 -2
- package/dist/plugins/ui-builder/client/index.d.cts +17 -7
- package/dist/plugins/ui-builder/client/index.d.mts +17 -7
- package/dist/plugins/ui-builder/client/index.d.ts +17 -7
- package/dist/plugins/ui-builder/index.d.cts +2 -2
- package/dist/plugins/ui-builder/index.d.mts +2 -2
- package/dist/plugins/ui-builder/index.d.ts +2 -2
- package/dist/plugins/ui-builder/style.css +6 -0
- package/dist/shared/{stack.BSM2cgoq.d.cts → stack.BYysGdHl.d.cts} +1 -1
- package/dist/shared/{stack.CqfZWfjJ.d.cts → stack.BdJFrdyt.d.cts} +8 -2
- package/dist/shared/{stack.e1FN86dE.d.mts → stack.ChVuHi5e.d.mts} +8 -2
- package/dist/shared/stack.DKDMI-QO.d.cts +70 -0
- package/dist/shared/stack.DKDMI-QO.d.mts +70 -0
- package/dist/shared/stack.DKDMI-QO.d.ts +70 -0
- package/dist/shared/{stack.CLtOoAqF.d.mts → stack.DYCFcnkL.d.mts} +1 -1
- package/dist/shared/{stack.MMntCVZZ.d.ts → stack.EhM4pmtN.d.ts} +8 -2
- package/dist/shared/stack.FeaWkglm.d.cts +190 -0
- package/dist/shared/stack.FeaWkglm.d.mts +190 -0
- package/dist/shared/stack.FeaWkglm.d.ts +190 -0
- package/dist/shared/{stack.BD1m-4yB.d.ts → stack.kFbDspnF.d.ts} +1 -1
- package/package.json +56 -2
- package/src/plugins/kanban/api/index.ts +6 -0
- package/src/plugins/kanban/api/plugin.ts +1245 -0
- package/src/plugins/kanban/client/components/forms/board-form.tsx +108 -0
- package/src/plugins/kanban/client/components/forms/column-form.tsx +97 -0
- package/src/plugins/kanban/client/components/forms/task-form.tsx +274 -0
- package/src/plugins/kanban/client/components/index.tsx +21 -0
- package/src/plugins/kanban/client/components/loading/board-skeleton.tsx +49 -0
- package/src/plugins/kanban/client/components/loading/boards-list-skeleton.tsx +34 -0
- package/src/plugins/kanban/client/components/loading/index.tsx +2 -0
- package/src/plugins/kanban/client/components/pages/404-page.tsx +28 -0
- package/src/plugins/kanban/client/components/pages/board-page.internal.tsx +575 -0
- package/src/plugins/kanban/client/components/pages/board-page.tsx +31 -0
- package/src/plugins/kanban/client/components/pages/boards-list-page.internal.tsx +101 -0
- package/src/plugins/kanban/client/components/pages/boards-list-page.tsx +26 -0
- package/src/plugins/kanban/client/components/pages/new-board-page.internal.tsx +65 -0
- package/src/plugins/kanban/client/components/pages/new-board-page.tsx +26 -0
- package/src/plugins/kanban/client/components/shared/column-content.tsx +108 -0
- package/src/plugins/kanban/client/components/shared/default-error.tsx +32 -0
- package/src/plugins/kanban/client/components/shared/empty-state.tsx +37 -0
- package/src/plugins/kanban/client/components/shared/kanban-board.tsx +87 -0
- package/src/plugins/kanban/client/components/shared/page-wrapper.tsx +20 -0
- package/src/plugins/kanban/client/components/shared/task-card.tsx +79 -0
- package/src/plugins/kanban/client/components/shared/user-avatar.tsx +63 -0
- package/src/plugins/kanban/client/hooks/index.tsx +11 -0
- package/src/plugins/kanban/client/hooks/kanban-hooks.tsx +560 -0
- package/src/plugins/kanban/client/index.ts +8 -0
- package/src/plugins/kanban/client/localization/index.ts +28 -0
- package/src/plugins/kanban/client/localization/kanban-common.ts +69 -0
- package/src/plugins/kanban/client/localization/kanban-forms.ts +70 -0
- package/src/plugins/kanban/client/localization/kanban-list.ts +36 -0
- package/src/plugins/kanban/client/overrides.ts +145 -0
- package/src/plugins/kanban/client/plugin.tsx +463 -0
- package/src/plugins/kanban/client.css +68 -0
- package/src/plugins/kanban/db.ts +125 -0
- package/src/plugins/kanban/query-keys.ts +154 -0
- package/src/plugins/kanban/schemas.ts +143 -0
- package/src/plugins/kanban/style.css +7 -0
- package/src/plugins/kanban/types.ts +106 -0
- package/src/plugins/kanban/utils.ts +107 -0
- package/src/plugins/ui-builder/style.css +6 -0
- package/dist/shared/{stack.DLhzx1-D.d.mts → stack.CcI4sYJP.d.cts} +1 -1
- package/dist/shared/{stack.DLhzx1-D.d.ts → stack.CcI4sYJP.d.mts} +1 -1
- package/dist/shared/{stack.DLhzx1-D.d.cts → stack.CcI4sYJP.d.ts} +1 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/* Kanban Plugin Client Styles */
|
|
2
|
+
|
|
3
|
+
/* Kanban board container */
|
|
4
|
+
[data-slot="kanban-board"] {
|
|
5
|
+
min-height: 400px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* Column styling */
|
|
9
|
+
[data-slot="kanban-column"] {
|
|
10
|
+
min-width: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Item dragging state */
|
|
14
|
+
[data-slot="kanban-item"][data-dragging] {
|
|
15
|
+
opacity: 0.5;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Column dragging state */
|
|
19
|
+
[data-slot="kanban-column"][data-dragging] {
|
|
20
|
+
opacity: 0.5;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Handle cursor */
|
|
24
|
+
[data-slot="kanban-column-handle"],
|
|
25
|
+
[data-slot="kanban-item-handle"] {
|
|
26
|
+
cursor: grab;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
[data-slot="kanban-column-handle"]:active,
|
|
30
|
+
[data-slot="kanban-item-handle"]:active {
|
|
31
|
+
cursor: grabbing;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Task card hover effect */
|
|
35
|
+
.kanban-task-card {
|
|
36
|
+
transition: box-shadow 0.2s ease;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.kanban-task-card:hover {
|
|
40
|
+
box-shadow:
|
|
41
|
+
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
|
42
|
+
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Priority badge colors */
|
|
46
|
+
.kanban-priority-urgent {
|
|
47
|
+
background-color: var(--destructive) !important;
|
|
48
|
+
color: var(--destructive-foreground) !important;
|
|
49
|
+
border-color: var(--destructive) !important;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.kanban-priority-high {
|
|
53
|
+
background-color: rgb(249, 115, 22) !important;
|
|
54
|
+
color: white !important;
|
|
55
|
+
border-color: rgb(249, 115, 22) !important;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.kanban-priority-medium {
|
|
59
|
+
background-color: var(--primary) !important;
|
|
60
|
+
color: var(--primary-foreground) !important;
|
|
61
|
+
border-color: var(--primary) !important;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.kanban-priority-low {
|
|
65
|
+
background-color: var(--secondary) !important;
|
|
66
|
+
color: var(--secondary-foreground) !important;
|
|
67
|
+
border-color: var(--secondary) !important;
|
|
68
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { createDbPlugin } from "@btst/db";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Kanban plugin schema
|
|
5
|
+
* Defines the database tables for kanban boards, columns, and tasks
|
|
6
|
+
*/
|
|
7
|
+
export const kanbanSchema = createDbPlugin("kanban", {
|
|
8
|
+
board: {
|
|
9
|
+
modelName: "kanbanBoard",
|
|
10
|
+
fields: {
|
|
11
|
+
name: {
|
|
12
|
+
type: "string",
|
|
13
|
+
required: true,
|
|
14
|
+
},
|
|
15
|
+
slug: {
|
|
16
|
+
type: "string",
|
|
17
|
+
required: true,
|
|
18
|
+
unique: true,
|
|
19
|
+
},
|
|
20
|
+
description: {
|
|
21
|
+
type: "string",
|
|
22
|
+
required: false,
|
|
23
|
+
},
|
|
24
|
+
ownerId: {
|
|
25
|
+
type: "string",
|
|
26
|
+
required: false,
|
|
27
|
+
},
|
|
28
|
+
organizationId: {
|
|
29
|
+
type: "string",
|
|
30
|
+
required: false,
|
|
31
|
+
},
|
|
32
|
+
createdAt: {
|
|
33
|
+
type: "date",
|
|
34
|
+
defaultValue: () => new Date(),
|
|
35
|
+
},
|
|
36
|
+
updatedAt: {
|
|
37
|
+
type: "date",
|
|
38
|
+
defaultValue: () => new Date(),
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
column: {
|
|
43
|
+
modelName: "kanbanColumn",
|
|
44
|
+
fields: {
|
|
45
|
+
title: {
|
|
46
|
+
type: "string",
|
|
47
|
+
required: true,
|
|
48
|
+
},
|
|
49
|
+
order: {
|
|
50
|
+
type: "number",
|
|
51
|
+
required: true,
|
|
52
|
+
defaultValue: 0,
|
|
53
|
+
},
|
|
54
|
+
boardId: {
|
|
55
|
+
type: "string",
|
|
56
|
+
required: true,
|
|
57
|
+
references: {
|
|
58
|
+
model: "kanbanBoard",
|
|
59
|
+
field: "id",
|
|
60
|
+
onDelete: "cascade",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
createdAt: {
|
|
64
|
+
type: "date",
|
|
65
|
+
defaultValue: () => new Date(),
|
|
66
|
+
},
|
|
67
|
+
updatedAt: {
|
|
68
|
+
type: "date",
|
|
69
|
+
defaultValue: () => new Date(),
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
task: {
|
|
74
|
+
modelName: "kanbanTask",
|
|
75
|
+
fields: {
|
|
76
|
+
title: {
|
|
77
|
+
type: "string",
|
|
78
|
+
required: true,
|
|
79
|
+
},
|
|
80
|
+
description: {
|
|
81
|
+
type: "string",
|
|
82
|
+
required: false,
|
|
83
|
+
},
|
|
84
|
+
priority: {
|
|
85
|
+
type: "string",
|
|
86
|
+
required: true,
|
|
87
|
+
defaultValue: "MEDIUM",
|
|
88
|
+
},
|
|
89
|
+
order: {
|
|
90
|
+
type: "number",
|
|
91
|
+
required: true,
|
|
92
|
+
defaultValue: 0,
|
|
93
|
+
},
|
|
94
|
+
columnId: {
|
|
95
|
+
type: "string",
|
|
96
|
+
required: true,
|
|
97
|
+
references: {
|
|
98
|
+
model: "kanbanColumn",
|
|
99
|
+
field: "id",
|
|
100
|
+
onDelete: "cascade",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
assigneeId: {
|
|
104
|
+
type: "string",
|
|
105
|
+
required: false,
|
|
106
|
+
},
|
|
107
|
+
completedAt: {
|
|
108
|
+
type: "date",
|
|
109
|
+
required: false,
|
|
110
|
+
},
|
|
111
|
+
isArchived: {
|
|
112
|
+
type: "boolean",
|
|
113
|
+
defaultValue: false,
|
|
114
|
+
},
|
|
115
|
+
createdAt: {
|
|
116
|
+
type: "date",
|
|
117
|
+
defaultValue: () => new Date(),
|
|
118
|
+
},
|
|
119
|
+
updatedAt: {
|
|
120
|
+
type: "date",
|
|
121
|
+
defaultValue: () => new Date(),
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mergeQueryKeys,
|
|
3
|
+
createQueryKeys,
|
|
4
|
+
} from "@lukemorales/query-key-factory";
|
|
5
|
+
import type { KanbanApiRouter } from "./api";
|
|
6
|
+
import { createApiClient } from "@btst/stack/plugins/client";
|
|
7
|
+
import type { SerializedBoardWithColumns } from "./types";
|
|
8
|
+
|
|
9
|
+
interface BoardsListParams {
|
|
10
|
+
slug?: string;
|
|
11
|
+
ownerId?: string;
|
|
12
|
+
organizationId?: string;
|
|
13
|
+
limit?: number;
|
|
14
|
+
offset?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Type guard for better-call error responses
|
|
18
|
+
function isErrorResponse(
|
|
19
|
+
response: unknown,
|
|
20
|
+
): response is { error: unknown; data?: never } {
|
|
21
|
+
return (
|
|
22
|
+
typeof response === "object" &&
|
|
23
|
+
response !== null &&
|
|
24
|
+
"error" in response &&
|
|
25
|
+
response.error !== null &&
|
|
26
|
+
response.error !== undefined
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Helper to convert error to a proper Error object with meaningful message
|
|
31
|
+
function toError(error: unknown): Error {
|
|
32
|
+
if (error instanceof Error) {
|
|
33
|
+
return error;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (typeof error === "object" && error !== null) {
|
|
37
|
+
const errorObj = error as Record<string, unknown>;
|
|
38
|
+
const message =
|
|
39
|
+
(typeof errorObj.message === "string" ? errorObj.message : null) ||
|
|
40
|
+
(typeof errorObj.error === "string" ? errorObj.error : null) ||
|
|
41
|
+
JSON.stringify(error);
|
|
42
|
+
|
|
43
|
+
const err = new Error(message);
|
|
44
|
+
Object.assign(err, error);
|
|
45
|
+
return err;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return new Error(String(error));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function createKanbanQueryKeys(
|
|
52
|
+
client: ReturnType<typeof createApiClient<KanbanApiRouter>>,
|
|
53
|
+
headers?: HeadersInit,
|
|
54
|
+
) {
|
|
55
|
+
const boards = createBoardsQueries(client, headers);
|
|
56
|
+
|
|
57
|
+
return mergeQueryKeys(boards);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createBoardsQueries(
|
|
61
|
+
client: ReturnType<typeof createApiClient<KanbanApiRouter>>,
|
|
62
|
+
headers?: HeadersInit,
|
|
63
|
+
) {
|
|
64
|
+
return createQueryKeys("boards", {
|
|
65
|
+
list: (params?: BoardsListParams) => ({
|
|
66
|
+
queryKey: [
|
|
67
|
+
{
|
|
68
|
+
slug: params?.slug,
|
|
69
|
+
ownerId: params?.ownerId,
|
|
70
|
+
organizationId: params?.organizationId,
|
|
71
|
+
limit: params?.limit ?? 50,
|
|
72
|
+
offset: params?.offset ?? 0,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
queryFn: async () => {
|
|
76
|
+
try {
|
|
77
|
+
const response = await client("/boards", {
|
|
78
|
+
method: "GET",
|
|
79
|
+
query: {
|
|
80
|
+
slug: params?.slug,
|
|
81
|
+
ownerId: params?.ownerId,
|
|
82
|
+
organizationId: params?.organizationId,
|
|
83
|
+
limit: params?.limit ?? 50,
|
|
84
|
+
offset: params?.offset ?? 0,
|
|
85
|
+
},
|
|
86
|
+
headers,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (isErrorResponse(response)) {
|
|
90
|
+
const errorResponse = response as { error: unknown };
|
|
91
|
+
throw toError(errorResponse.error);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return ((response as { data?: unknown }).data ??
|
|
95
|
+
[]) as unknown as SerializedBoardWithColumns[];
|
|
96
|
+
} catch (error) {
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
|
|
102
|
+
detail: (boardId: string) => ({
|
|
103
|
+
queryKey: [boardId],
|
|
104
|
+
queryFn: async () => {
|
|
105
|
+
if (!boardId) return null;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const response = await client("/boards/:id", {
|
|
109
|
+
method: "GET",
|
|
110
|
+
params: { id: boardId },
|
|
111
|
+
headers,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (isErrorResponse(response)) {
|
|
115
|
+
const errorResponse = response as { error: unknown };
|
|
116
|
+
throw toError(errorResponse.error);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return ((response as { data?: unknown }).data ??
|
|
120
|
+
null) as unknown as SerializedBoardWithColumns | null;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
}),
|
|
126
|
+
|
|
127
|
+
// Get board by slug
|
|
128
|
+
bySlug: (slug: string) => ({
|
|
129
|
+
queryKey: ["slug", slug],
|
|
130
|
+
queryFn: async () => {
|
|
131
|
+
if (!slug) return null;
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const response = await client("/boards", {
|
|
135
|
+
method: "GET",
|
|
136
|
+
query: { slug, limit: 1 },
|
|
137
|
+
headers,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (isErrorResponse(response)) {
|
|
141
|
+
const errorResponse = response as { error: unknown };
|
|
142
|
+
throw toError(errorResponse.error);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const boards = ((response as { data?: unknown }).data ??
|
|
146
|
+
[]) as unknown as SerializedBoardWithColumns[];
|
|
147
|
+
return boards[0] ?? null;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
}),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Priority enum schema
|
|
5
|
+
*/
|
|
6
|
+
export const PrioritySchema = z.enum(["LOW", "MEDIUM", "HIGH", "URGENT"]);
|
|
7
|
+
|
|
8
|
+
// ============ Board Schemas ============
|
|
9
|
+
|
|
10
|
+
const boardDateFields = {
|
|
11
|
+
createdAt: z.coerce.date().optional(),
|
|
12
|
+
updatedAt: z.coerce.date().optional(),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const boardCoreFields = {
|
|
16
|
+
name: z.string().min(1, "Name is required"),
|
|
17
|
+
slug: z.string().min(1, "Slug is required"),
|
|
18
|
+
description: z.string().optional(),
|
|
19
|
+
ownerId: z.string().optional(),
|
|
20
|
+
organizationId: z.string().optional(),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const BoardDomainSchema = z.object({
|
|
24
|
+
id: z.string().optional(),
|
|
25
|
+
...boardCoreFields,
|
|
26
|
+
...boardDateFields,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const createBoardSchema = BoardDomainSchema.extend({
|
|
30
|
+
slug: BoardDomainSchema.shape.slug.optional(),
|
|
31
|
+
}).omit({ id: true });
|
|
32
|
+
|
|
33
|
+
export const updateBoardSchema = BoardDomainSchema.extend({
|
|
34
|
+
id: z.string(),
|
|
35
|
+
})
|
|
36
|
+
.partial()
|
|
37
|
+
.required({ id: true });
|
|
38
|
+
|
|
39
|
+
// ============ Column Schemas ============
|
|
40
|
+
|
|
41
|
+
const columnDateFields = {
|
|
42
|
+
createdAt: z.coerce.date().optional(),
|
|
43
|
+
updatedAt: z.coerce.date().optional(),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const columnCoreFields = {
|
|
47
|
+
title: z.string().min(1, "Title is required"),
|
|
48
|
+
order: z.number().int().min(0).optional().default(0),
|
|
49
|
+
boardId: z.string().min(1, "Board ID is required"),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const ColumnDomainSchema = z.object({
|
|
53
|
+
id: z.string().optional(),
|
|
54
|
+
...columnCoreFields,
|
|
55
|
+
...columnDateFields,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const createColumnSchema = ColumnDomainSchema.omit({ id: true });
|
|
59
|
+
|
|
60
|
+
export const updateColumnSchema = ColumnDomainSchema.extend({
|
|
61
|
+
id: z.string(),
|
|
62
|
+
})
|
|
63
|
+
.partial()
|
|
64
|
+
.required({ id: true });
|
|
65
|
+
|
|
66
|
+
// ============ Task Schemas ============
|
|
67
|
+
|
|
68
|
+
const taskDateFields = {
|
|
69
|
+
completedAt: z.coerce.date().optional(),
|
|
70
|
+
createdAt: z.coerce.date().optional(),
|
|
71
|
+
updatedAt: z.coerce.date().optional(),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const taskCoreFields = {
|
|
75
|
+
title: z.string().min(1, "Title is required"),
|
|
76
|
+
description: z.string().optional(),
|
|
77
|
+
priority: PrioritySchema.optional().default("MEDIUM"),
|
|
78
|
+
order: z.number().int().min(0).optional().default(0),
|
|
79
|
+
columnId: z.string().min(1, "Column ID is required"),
|
|
80
|
+
assigneeId: z.string().optional().nullable(),
|
|
81
|
+
isArchived: z.boolean().optional().default(false),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const TaskDomainSchema = z.object({
|
|
85
|
+
id: z.string().optional(),
|
|
86
|
+
...taskCoreFields,
|
|
87
|
+
...taskDateFields,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export const createTaskSchema = TaskDomainSchema.omit({ id: true });
|
|
91
|
+
|
|
92
|
+
export const updateTaskSchema = TaskDomainSchema.extend({
|
|
93
|
+
id: z.string(),
|
|
94
|
+
})
|
|
95
|
+
.partial()
|
|
96
|
+
.required({ id: true });
|
|
97
|
+
|
|
98
|
+
// ============ Query Schemas ============
|
|
99
|
+
|
|
100
|
+
export const BoardListQuerySchema = z.object({
|
|
101
|
+
slug: z.string().optional(),
|
|
102
|
+
ownerId: z.string().optional(),
|
|
103
|
+
organizationId: z.string().optional(),
|
|
104
|
+
offset: z.coerce.number().int().min(0).optional(),
|
|
105
|
+
limit: z.coerce.number().int().min(1).max(100).optional(),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export const ColumnListQuerySchema = z.object({
|
|
109
|
+
boardId: z.string().optional(),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export const TaskListQuerySchema = z.object({
|
|
113
|
+
columnId: z.string().optional(),
|
|
114
|
+
assigneeId: z.string().optional(),
|
|
115
|
+
priority: PrioritySchema.optional(),
|
|
116
|
+
isArchived: z
|
|
117
|
+
.string()
|
|
118
|
+
.optional()
|
|
119
|
+
.transform((val) => {
|
|
120
|
+
if (val === undefined) return undefined;
|
|
121
|
+
if (val === "true") return true;
|
|
122
|
+
if (val === "false") return false;
|
|
123
|
+
return undefined;
|
|
124
|
+
}),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ============ Batch Update Schemas ============
|
|
128
|
+
|
|
129
|
+
export const reorderColumnsSchema = z.object({
|
|
130
|
+
boardId: z.string().min(1, "Board ID is required"),
|
|
131
|
+
columnIds: z.array(z.string()).min(1, "Column IDs are required"),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
export const reorderTasksSchema = z.object({
|
|
135
|
+
columnId: z.string().min(1, "Column ID is required"),
|
|
136
|
+
taskIds: z.array(z.string()).min(1, "Task IDs are required"),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
export const moveTaskSchema = z.object({
|
|
140
|
+
taskId: z.string().min(1, "Task ID is required"),
|
|
141
|
+
targetColumnId: z.string().min(1, "Target column ID is required"),
|
|
142
|
+
targetOrder: z.number().int().min(0),
|
|
143
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/* Kanban Plugin Full Styles */
|
|
2
|
+
/* This file includes Tailwind directives for consumers who need the full stylesheet */
|
|
3
|
+
|
|
4
|
+
@import "./client.css";
|
|
5
|
+
|
|
6
|
+
/* Import minimal-tiptap styles for rich text editor in task forms */
|
|
7
|
+
@import "@workspace/ui/components/minimal-tiptap/styles.css";
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Priority levels for tasks
|
|
3
|
+
*/
|
|
4
|
+
export type Priority = "LOW" | "MEDIUM" | "HIGH" | "URGENT";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Kanban Board
|
|
8
|
+
*/
|
|
9
|
+
export type Board = {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
slug: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
ownerId?: string;
|
|
15
|
+
organizationId?: string;
|
|
16
|
+
createdAt: Date;
|
|
17
|
+
updatedAt: Date;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Kanban Column
|
|
22
|
+
*/
|
|
23
|
+
export type Column = {
|
|
24
|
+
id: string;
|
|
25
|
+
title: string;
|
|
26
|
+
order: number;
|
|
27
|
+
boardId: string;
|
|
28
|
+
createdAt: Date;
|
|
29
|
+
updatedAt: Date;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Kanban Task
|
|
34
|
+
*/
|
|
35
|
+
export type Task = {
|
|
36
|
+
id: string;
|
|
37
|
+
title: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
priority: Priority;
|
|
40
|
+
order: number;
|
|
41
|
+
columnId: string;
|
|
42
|
+
assigneeId?: string;
|
|
43
|
+
completedAt?: Date;
|
|
44
|
+
isArchived: boolean;
|
|
45
|
+
createdAt: Date;
|
|
46
|
+
updatedAt: Date;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Column with its tasks
|
|
51
|
+
*/
|
|
52
|
+
export type ColumnWithTasks = Column & {
|
|
53
|
+
tasks: Task[];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Board with columns and tasks
|
|
58
|
+
*/
|
|
59
|
+
export type BoardWithColumns = Board & {
|
|
60
|
+
columns: ColumnWithTasks[];
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Board with joined column relationships from the database
|
|
65
|
+
* Note: The adapter returns joined data under the schema key name ("column"),
|
|
66
|
+
* not the model name ("kanbanColumn")
|
|
67
|
+
*/
|
|
68
|
+
export type BoardWithKanbanColumn = Board & {
|
|
69
|
+
column?: Column[];
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Column with joined task relationships from the database
|
|
74
|
+
* Note: The adapter returns joined data under the schema key name ("task"),
|
|
75
|
+
* not the model name ("kanbanTask")
|
|
76
|
+
*/
|
|
77
|
+
export type ColumnWithKanbanTask = Column & {
|
|
78
|
+
task?: Task[];
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Serialized types for API responses (dates as strings)
|
|
82
|
+
|
|
83
|
+
export interface SerializedTask
|
|
84
|
+
extends Omit<Task, "createdAt" | "updatedAt" | "completedAt"> {
|
|
85
|
+
completedAt?: string;
|
|
86
|
+
createdAt: string;
|
|
87
|
+
updatedAt: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface SerializedColumn
|
|
91
|
+
extends Omit<Column, "createdAt" | "updatedAt"> {
|
|
92
|
+
createdAt: string;
|
|
93
|
+
updatedAt: string;
|
|
94
|
+
tasks?: SerializedTask[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface SerializedBoard
|
|
98
|
+
extends Omit<Board, "createdAt" | "updatedAt"> {
|
|
99
|
+
createdAt: string;
|
|
100
|
+
updatedAt: string;
|
|
101
|
+
columns?: SerializedColumn[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface SerializedBoardWithColumns extends SerializedBoard {
|
|
105
|
+
columns: SerializedColumn[];
|
|
106
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slugify a string for use in URLs
|
|
3
|
+
* @param text - The text to slugify
|
|
4
|
+
* @returns A URL-safe slug
|
|
5
|
+
*/
|
|
6
|
+
export function slugify(text: string): string {
|
|
7
|
+
return text
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.trim()
|
|
10
|
+
.replace(/[^\w\s-]/g, "") // Remove non-word characters
|
|
11
|
+
.replace(/[\s_-]+/g, "-") // Replace spaces and underscores with hyphens
|
|
12
|
+
.replace(/^-+|-+$/g, ""); // Remove leading/trailing hyphens
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate a unique slug by appending a random suffix
|
|
17
|
+
* @param baseSlug - The base slug
|
|
18
|
+
* @returns A unique slug with random suffix
|
|
19
|
+
*/
|
|
20
|
+
export function generateUniqueSlug(baseSlug: string): string {
|
|
21
|
+
const randomSuffix = Math.random().toString(36).substring(2, 8);
|
|
22
|
+
return `${baseSlug}-${randomSuffix}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Calculate the next order value for a list of items
|
|
27
|
+
* @param items - Array of items with order property
|
|
28
|
+
* @returns The next order value
|
|
29
|
+
*/
|
|
30
|
+
export function getNextOrder(items: { order: number }[]): number {
|
|
31
|
+
if (items.length === 0) return 0;
|
|
32
|
+
return Math.max(...items.map((item) => item.order)) + 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Reorder items by swapping positions
|
|
37
|
+
* @param items - Array of items to reorder
|
|
38
|
+
* @param fromIndex - Original index
|
|
39
|
+
* @param toIndex - Target index
|
|
40
|
+
* @returns Reordered array with updated order values
|
|
41
|
+
*/
|
|
42
|
+
export function reorderItems<T extends { id: string; order: number }>(
|
|
43
|
+
items: T[],
|
|
44
|
+
fromIndex: number,
|
|
45
|
+
toIndex: number,
|
|
46
|
+
): T[] {
|
|
47
|
+
const result = [...items];
|
|
48
|
+
const [removed] = result.splice(fromIndex, 1);
|
|
49
|
+
if (removed) {
|
|
50
|
+
result.splice(toIndex, 0, removed);
|
|
51
|
+
}
|
|
52
|
+
return result.map((item, index) => ({ ...item, order: index }));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get priority display configuration
|
|
57
|
+
* @param priority - Priority level
|
|
58
|
+
* @returns Display configuration for the priority
|
|
59
|
+
*/
|
|
60
|
+
export function getPriorityConfig(priority: string): {
|
|
61
|
+
label: string;
|
|
62
|
+
variant: "default" | "secondary" | "destructive" | "outline";
|
|
63
|
+
className: string;
|
|
64
|
+
} {
|
|
65
|
+
switch (priority) {
|
|
66
|
+
case "URGENT":
|
|
67
|
+
return {
|
|
68
|
+
label: "Urgent",
|
|
69
|
+
variant: "destructive",
|
|
70
|
+
className: "kanban-priority-urgent",
|
|
71
|
+
};
|
|
72
|
+
case "HIGH":
|
|
73
|
+
return {
|
|
74
|
+
label: "High",
|
|
75
|
+
variant: "outline",
|
|
76
|
+
className: "kanban-priority-high",
|
|
77
|
+
};
|
|
78
|
+
case "MEDIUM":
|
|
79
|
+
return {
|
|
80
|
+
label: "Medium",
|
|
81
|
+
variant: "default",
|
|
82
|
+
className: "kanban-priority-medium",
|
|
83
|
+
};
|
|
84
|
+
case "LOW":
|
|
85
|
+
return {
|
|
86
|
+
label: "Low",
|
|
87
|
+
variant: "secondary",
|
|
88
|
+
className: "kanban-priority-low",
|
|
89
|
+
};
|
|
90
|
+
default:
|
|
91
|
+
return {
|
|
92
|
+
label: "Medium",
|
|
93
|
+
variant: "default",
|
|
94
|
+
className: "kanban-priority-medium",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Priority options for forms
|
|
101
|
+
*/
|
|
102
|
+
export const PRIORITY_OPTIONS = [
|
|
103
|
+
{ value: "LOW", label: "Low" },
|
|
104
|
+
{ value: "MEDIUM", label: "Medium" },
|
|
105
|
+
{ value: "HIGH", label: "High" },
|
|
106
|
+
{ value: "URGENT", label: "Urgent" },
|
|
107
|
+
] as const;
|