@asteby/metacore-runtime-react 19.0.0 → 20.1.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/CHANGELOG.md +44 -0
- package/dist/dynamic-kanban.d.ts +2 -2
- package/dist/dynamic-kanban.d.ts.map +1 -1
- package/dist/dynamic-kanban.js +8 -3
- package/dist/dynamic-view.d.ts +25 -1
- package/dist/dynamic-view.d.ts.map +1 -1
- package/dist/dynamic-view.js +35 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -5
- package/src/__tests__/dynamic-view.test.tsx +62 -0
- package/src/dynamic-kanban.tsx +11 -6
- package/src/dynamic-view.tsx +51 -4
- package/src/index.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 20.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8de09a9: DynamicKanban: traduce el label de cada etapa via i18n (`t(stage.label)` con fallback al valor crudo) — antes mostraba la key cruda (ej. `integration_github.stage.backlog`) en vez de "Backlog". Y da min-height a las lanes para que el scroll horizontal del board quede abajo en vez de flotar cuando las columnas están vacías.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 8de09a9: fix(kanban): el drag-to-move ya no duplica `/me` en el PUT (causaba 404 "No se pudo mover la tarjeta"); el board respeta el ancho del padre (`min-w-0`) y deja de desbordarse horizontalmente.
|
|
12
|
+
|
|
13
|
+
## 20.0.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- dcd95c3: DynamicKanban: traduce el label de cada etapa via i18n (`t(stage.label)` con fallback al valor crudo) — antes mostraba la key cruda (ej. `integration_github.stage.backlog`) en vez de "Backlog". Y da min-height a las lanes para que el scroll horizontal del board quede abajo en vez de flotar cuando las columnas están vacías.
|
|
18
|
+
- 3f41073: Sidebar nav: exact, view-aware active-state so sibling navs over the same model light up one at a time
|
|
19
|
+
|
|
20
|
+
The `NavGroup` active-state matcher (`checkIsActive`) now treats `view`/`group_by`
|
|
21
|
+
query params as the _identity_ of a view-style nav item. Two navs over the same
|
|
22
|
+
model that differ only by their view — e.g. a "Board" (`?view=kanban&group_by=stage`)
|
|
23
|
+
and an "Issues" (`?view=list`, or a query-less default list) — are mutually
|
|
24
|
+
exclusive: only the item whose view identity equals `currentHref` stays active,
|
|
25
|
+
fixing the bug where both lit up at once.
|
|
26
|
+
- `@asteby/metacore-ui`: the matcher is extracted into a pure, React-free
|
|
27
|
+
`layout/nav-active` module (`checkIsActive`, `splitHref`, `declaredFiltersMatch`,
|
|
28
|
+
`VIEW_PARAMS`) and re-exported from `@asteby/metacore-ui/layout` for hosts and
|
|
29
|
+
unit tests. `f_` filter and transient (page/sort/search) highlight behaviour is
|
|
30
|
+
unchanged — a query-less link still highlights under filters/pagination, and
|
|
31
|
+
per-status entries still light up one at a time.
|
|
32
|
+
- `@asteby/metacore-starter-core`: the scaffold's `nav-group` matcher gains the
|
|
33
|
+
same view/query/filter-aware logic.
|
|
34
|
+
- `@asteby/metacore-runtime-react`: `DynamicView` now reads the active view from
|
|
35
|
+
the per-nav signal — an explicit `view` prop (host router) or the `?view=`
|
|
36
|
+
query — and prefers it over the model-level `metadata.view_type`, so the same
|
|
37
|
+
model can route `?view=kanban` to `DynamicKanban` and `?view=list` to
|
|
38
|
+
`DynamicTable` with no per-model metadata change. New pure helpers
|
|
39
|
+
`readViewFromSearch` / `resolveActiveView` are exported.
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- Updated dependencies [3f41073]
|
|
44
|
+
- @asteby/metacore-ui@2.6.0
|
|
45
|
+
|
|
3
46
|
## 19.0.0
|
|
4
47
|
|
|
5
48
|
### Major Changes
|
|
@@ -9,6 +52,7 @@
|
|
|
9
52
|
Drag-to-move (via `@dnd-kit/core`) is **optimistic**: dropping a card into another lane mutates local state immediately and fires `PUT /data/:model/me/:id { <group_by>: <dest> }`; on failure the move reverts and a toast surfaces — sidestepping the "refetch loses scroll/selection" gap. When the metadata declares `transitions[]`, a card may only drop into a stage reachable from its current one (disallowed lanes dim and reject the drop; the kernel still validates server-side).
|
|
10
53
|
|
|
11
54
|
Also adds `DynamicView`, a metadata-driven dispatcher that routes `view_type === 'kanban'` → `DynamicKanban`, else → `DynamicTable`, plus the pure helpers `deriveStages`, `groupByStage`, `isTransitionAllowed`, `applyOptimisticMove`, `selectCardColumns`, `resolveViewRenderer`. New `TableMetadata` fields (`view_type`, `group_by`, `stages`, `transitions`) and `StageMeta`/`StageTransition` types are purely additive. New deps: `@dnd-kit/core`, `@dnd-kit/sortable`, `@dnd-kit/utilities`.
|
|
55
|
+
|
|
12
56
|
## 18.28.3
|
|
13
57
|
|
|
14
58
|
### Patch Changes
|
package/dist/dynamic-kanban.d.ts
CHANGED
|
@@ -44,8 +44,8 @@ export interface DynamicKanbanProps {
|
|
|
44
44
|
/** Model key as registered on the backend (e.g. "issue"). */
|
|
45
45
|
model: string;
|
|
46
46
|
/**
|
|
47
|
-
* Data endpoint base
|
|
48
|
-
* PUTs to `<base
|
|
47
|
+
* Data endpoint base — the org-scoped LIST endpoint (e.g.
|
|
48
|
+
* `/data/<model>/me`). The optimistic update PUTs to `<base>/<id>`.
|
|
49
49
|
*/
|
|
50
50
|
endpoint?: string;
|
|
51
51
|
/** Bump to force a metadata + records refetch (same contract as DynamicTable). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-kanban.d.ts","sourceRoot":"","sources":["../src/dynamic-kanban.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAqC9B,OAAO,KAAK,EACR,aAAa,EACb,gBAAgB,EAGhB,SAAS,EACT,eAAe,EAClB,MAAM,SAAS,CAAA;AAMhB;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS,EAAE,CAejE;AAQD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,mBAAmB,CAAA;AAE/C,wBAAgB,YAAY,CACxB,OAAO,EAAE,GAAG,EAAE,EACd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,SAAS,EAAE,GACpB,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAepB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAC/B,WAAW,EAAE,eAAe,EAAE,GAAG,SAAS,EAC1C,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACX,OAAO,CAOT;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAC/B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,EAC3B,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACnB,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAYpB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,aAAa,EACvB,SAAS,SAAI,GACd;IAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAkBhE;AA8BD,MAAM,WAAW,kBAAkB;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kFAAkF;IAClF,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAChC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,aAAa,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,cAAc,EACd,WAAW,EACX,QAAc,EACd,QAAQ,EACR,QAAQ,GACX,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"dynamic-kanban.d.ts","sourceRoot":"","sources":["../src/dynamic-kanban.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAqC9B,OAAO,KAAK,EACR,aAAa,EACb,gBAAgB,EAGhB,SAAS,EACT,eAAe,EAClB,MAAM,SAAS,CAAA;AAMhB;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS,EAAE,CAejE;AAQD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,mBAAmB,CAAA;AAE/C,wBAAgB,YAAY,CACxB,OAAO,EAAE,GAAG,EAAE,EACd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,SAAS,EAAE,GACpB,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAepB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAC/B,WAAW,EAAE,eAAe,EAAE,GAAG,SAAS,EAC1C,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACX,OAAO,CAOT;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAC/B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,EAC3B,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACnB,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAYpB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,aAAa,EACvB,SAAS,SAAI,GACd;IAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAkBhE;AA8BD,MAAM,WAAW,kBAAkB;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kFAAkF;IAClF,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAChC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,aAAa,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,cAAc,EACd,WAAW,EACX,QAAc,EACd,QAAQ,EACR,QAAQ,GACX,EAAE,kBAAkB,qBAmSpB"}
|
package/dist/dynamic-kanban.js
CHANGED
|
@@ -261,7 +261,11 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pa
|
|
|
261
261
|
setRecords((rs) => rs.map((r) => String(r.id) === cardId ? { ...r, [groupByKey]: destStage } : r));
|
|
262
262
|
try {
|
|
263
263
|
const base = endpoint || `/data/${model}`;
|
|
264
|
-
|
|
264
|
+
// `base` is the org-scoped list endpoint (e.g. `/data/<model>/me`),
|
|
265
|
+
// so the per-record update is just `<base>/<id>` — same convention
|
|
266
|
+
// as DynamicTable/DynamicRelation. Appending an extra `/me` here
|
|
267
|
+
// produced `/data/<model>/me/me/<id>` → 404 on drag-to-move.
|
|
268
|
+
const res = (await api.put(`${base}/${cardId}`, {
|
|
265
269
|
[groupByKey]: destStage,
|
|
266
270
|
}));
|
|
267
271
|
if (res?.data && res.data.success === false) {
|
|
@@ -298,7 +302,7 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pa
|
|
|
298
302
|
order: Number.MAX_SAFE_INTEGER,
|
|
299
303
|
});
|
|
300
304
|
}
|
|
301
|
-
return (_jsxs(DndContext, { sensors: sensors, onDragStart: onDragStart, onDragEnd: onDragEnd, children: [_jsx("div", { className: "flex gap-4 overflow-x-auto p-1", "data-testid": "kanban-board", children: lanes.map((stage) => {
|
|
305
|
+
return (_jsxs(DndContext, { sensors: sensors, onDragStart: onDragStart, onDragEnd: onDragEnd, children: [_jsx("div", { className: "flex min-w-0 gap-4 overflow-x-auto p-1", "data-testid": "kanban-board", children: lanes.map((stage) => {
|
|
302
306
|
const cards = grouped.get(stage.key) ?? [];
|
|
303
307
|
const droppableAllowed = !activeId ||
|
|
304
308
|
stage.key === activeStage ||
|
|
@@ -313,6 +317,7 @@ export function DynamicKanban({ model, endpoint, refreshTrigger, onCardClick, pa
|
|
|
313
317
|
} }))] }));
|
|
314
318
|
}
|
|
315
319
|
function KanbanLane({ stage, count, isDark, dimmed, disabled, children }) {
|
|
320
|
+
const { t } = useTranslation();
|
|
316
321
|
const { setNodeRef, isOver } = useDroppable({ id: stage.key, disabled });
|
|
317
322
|
const headerStyle = generateBadgeStyles(stage.color || optionColor(stage.key), {
|
|
318
323
|
isDark,
|
|
@@ -321,7 +326,7 @@ function KanbanLane({ stage, count, isDark, dimmed, disabled, children }) {
|
|
|
321
326
|
opacity: dimmed ? 0.45 : 1,
|
|
322
327
|
outline: isOver && !disabled ? '2px solid var(--ring, #3b82f6)' : 'none',
|
|
323
328
|
outlineOffset: 2,
|
|
324
|
-
}, "data-stage": stage.key, "data-disabled": disabled || undefined, children: [_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2.5", children: [_jsx(Badge, { variant: "outline", className: "border-0 text-xs font-semibold", style: headerStyle, children: stage.label }), _jsx("span", { className: "text-xs font-medium tabular-nums text-muted-foreground", children: count })] }), _jsx(ScrollArea, { className: "max-h-[70vh]", children: _jsx("div", { className: "flex flex-col gap-2 px-2 pb-3", children: children }) })] }));
|
|
329
|
+
}, "data-stage": stage.key, "data-disabled": disabled || undefined, children: [_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2.5", children: [_jsx(Badge, { variant: "outline", className: "border-0 text-xs font-semibold", style: headerStyle, children: t(stage.label, { defaultValue: stage.label }) }), _jsx("span", { className: "text-xs font-medium tabular-nums text-muted-foreground", children: count })] }), _jsx(ScrollArea, { className: "min-h-[55vh] max-h-[70vh]", children: _jsx("div", { className: "flex flex-col gap-2 px-2 pb-3", children: children }) })] }));
|
|
325
330
|
}
|
|
326
331
|
function KanbanCard({ card, titleCol, fieldCols, actions, locale, timeZone, currency, onClick, onAction, }) {
|
|
327
332
|
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
package/dist/dynamic-view.d.ts
CHANGED
|
@@ -5,7 +5,31 @@ import { type DynamicKanbanProps } from './dynamic-kanban';
|
|
|
5
5
|
* host that resolves metadata itself can branch without mounting this wrapper.
|
|
6
6
|
*/
|
|
7
7
|
export declare function resolveViewRenderer(viewType: string | undefined): 'kanban' | 'table';
|
|
8
|
+
/**
|
|
9
|
+
* Reads the `view` selector out of a URL search string (`?view=kanban`, a bare
|
|
10
|
+
* `view=kanban`, or a full href). Returns `undefined` when absent. The query is
|
|
11
|
+
* the per-NAV signal: the same model exposes a "Board" nav (`?view=kanban`) and
|
|
12
|
+
* an "Issues" nav (`?view=list`), so the query — not the model-level
|
|
13
|
+
* `metadata.view_type` — decides which surface to paint. SSR-safe.
|
|
14
|
+
*/
|
|
15
|
+
export declare function readViewFromSearch(search?: string): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Resolves the effective view selector with the right precedence:
|
|
18
|
+
* 1. an explicit `view` prop the host passes (it owns the router), then
|
|
19
|
+
* 2. the `view` query param, then
|
|
20
|
+
* 3. the model-level `metadata.view_type` default.
|
|
21
|
+
* Exported pure for unit tests and host reuse.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveActiveView(explicit: string | undefined, search: string | undefined, metadataViewType: string | undefined): string | undefined;
|
|
8
24
|
export interface DynamicViewProps extends DynamicTableProps {
|
|
25
|
+
/**
|
|
26
|
+
* Explicit view selector from the host's router (e.g. the `view` search
|
|
27
|
+
* param resolved by tanstack-router). Takes precedence over the query string
|
|
28
|
+
* and the model's `metadata.view_type`. Pass this when the host owns routing
|
|
29
|
+
* so the same model can show `?view=kanban` (board) or `?view=list` (table)
|
|
30
|
+
* with no per-model metadata change.
|
|
31
|
+
*/
|
|
32
|
+
view?: string;
|
|
9
33
|
/**
|
|
10
34
|
* Props forwarded to <DynamicKanban> when the model resolves to a kanban
|
|
11
35
|
* view. `model`/`endpoint`/`refreshTrigger`/`timeZone`/`currency` are shared
|
|
@@ -14,5 +38,5 @@ export interface DynamicViewProps extends DynamicTableProps {
|
|
|
14
38
|
*/
|
|
15
39
|
kanbanProps?: Partial<Omit<DynamicKanbanProps, 'model' | 'endpoint'>>;
|
|
16
40
|
}
|
|
17
|
-
export declare function DynamicView({ kanbanProps, ...tableProps }: DynamicViewProps): import("react").JSX.Element | null;
|
|
41
|
+
export declare function DynamicView({ view, kanbanProps, ...tableProps }: DynamicViewProps): import("react").JSX.Element | null;
|
|
18
42
|
//# sourceMappingURL=dynamic-view.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-view.d.ts","sourceRoot":"","sources":["../src/dynamic-view.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACtE,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAGzE;;;GAGG;AACH,wBAAgB,mBAAmB,CAC/B,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC7B,QAAQ,GAAG,OAAO,CAEpB;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACvD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,OAAO,GAAG,UAAU,CAAC,CAAC,CAAA;CACxE;AAED,wBAAgB,WAAW,CAAC,EAAE,WAAW,EAAE,GAAG,UAAU,EAAE,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"dynamic-view.d.ts","sourceRoot":"","sources":["../src/dynamic-view.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACtE,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAGzE;;;GAGG;AACH,wBAAgB,mBAAmB,CAC/B,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC7B,QAAQ,GAAG,OAAO,CAEpB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMtE;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC7B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,gBAAgB,EAAE,MAAM,GAAG,SAAS,GACrC,MAAM,GAAG,SAAS,CAEpB;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACvD;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,OAAO,GAAG,UAAU,CAAC,CAAC,CAAA;CACxE;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,UAAU,EAAE,EAAE,gBAAgB,sCAiEjF"}
|
package/dist/dynamic-view.js
CHANGED
|
@@ -26,7 +26,32 @@ import { DynamicKanban } from './dynamic-kanban';
|
|
|
26
26
|
export function resolveViewRenderer(viewType) {
|
|
27
27
|
return viewType === 'kanban' ? 'kanban' : 'table';
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Reads the `view` selector out of a URL search string (`?view=kanban`, a bare
|
|
31
|
+
* `view=kanban`, or a full href). Returns `undefined` when absent. The query is
|
|
32
|
+
* the per-NAV signal: the same model exposes a "Board" nav (`?view=kanban`) and
|
|
33
|
+
* an "Issues" nav (`?view=list`), so the query — not the model-level
|
|
34
|
+
* `metadata.view_type` — decides which surface to paint. SSR-safe.
|
|
35
|
+
*/
|
|
36
|
+
export function readViewFromSearch(search) {
|
|
37
|
+
if (!search)
|
|
38
|
+
return undefined;
|
|
39
|
+
const qIndex = search.indexOf('?');
|
|
40
|
+
const qs = qIndex === -1 ? search : search.slice(qIndex + 1);
|
|
41
|
+
const v = new URLSearchParams(qs).get('view');
|
|
42
|
+
return v ?? undefined;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the effective view selector with the right precedence:
|
|
46
|
+
* 1. an explicit `view` prop the host passes (it owns the router), then
|
|
47
|
+
* 2. the `view` query param, then
|
|
48
|
+
* 3. the model-level `metadata.view_type` default.
|
|
49
|
+
* Exported pure for unit tests and host reuse.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveActiveView(explicit, search, metadataViewType) {
|
|
52
|
+
return explicit ?? readViewFromSearch(search) ?? metadataViewType;
|
|
53
|
+
}
|
|
54
|
+
export function DynamicView({ view, kanbanProps, ...tableProps }) {
|
|
30
55
|
const { model, endpoint, refreshTrigger, timeZone, currency } = tableProps;
|
|
31
56
|
const api = useApi();
|
|
32
57
|
const cached = useMetadataCache((s) => s.getMetadata(model));
|
|
@@ -64,11 +89,17 @@ export function DynamicView({ kanbanProps, ...tableProps }) {
|
|
|
64
89
|
};
|
|
65
90
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
66
91
|
}, [model]);
|
|
92
|
+
// The per-nav `view` (explicit prop or `?view=` query) wins over the
|
|
93
|
+
// model-level metadata default so two navs on the same model route to
|
|
94
|
+
// different surfaces.
|
|
95
|
+
const search = typeof window !== 'undefined' ? window.location.search : undefined;
|
|
96
|
+
const effectiveView = resolveActiveView(view, search, viewType);
|
|
67
97
|
// Until we know the view type, render nothing transient-heavy: default to the
|
|
68
|
-
// table renderer only once resolved to avoid a table→kanban flash.
|
|
69
|
-
|
|
98
|
+
// table renderer only once resolved to avoid a table→kanban flash. An
|
|
99
|
+
// explicit/query view short-circuits the wait (we already know the surface).
|
|
100
|
+
if (!resolved && !cached && view === undefined && !readViewFromSearch(search))
|
|
70
101
|
return null;
|
|
71
|
-
if (resolveViewRenderer(
|
|
102
|
+
if (resolveViewRenderer(effectiveView) === 'kanban') {
|
|
72
103
|
return (_jsx(DynamicKanban, { model: model, endpoint: endpoint, refreshTrigger: refreshTrigger, timeZone: timeZone, currency: currency, ...kanbanProps }));
|
|
73
104
|
}
|
|
74
105
|
return _jsx(DynamicTable, { ...tableProps });
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export * from './types';
|
|
|
2
2
|
export * from './options-context';
|
|
3
3
|
export * from './dynamic-table';
|
|
4
4
|
export { DynamicKanban, type DynamicKanbanProps, deriveStages, groupByStage, isTransitionAllowed, applyOptimisticMove, selectCardColumns, UNASSIGNED_LANE, } from './dynamic-kanban';
|
|
5
|
-
export { DynamicView, resolveViewRenderer, type DynamicViewProps, } from './dynamic-view';
|
|
5
|
+
export { DynamicView, resolveViewRenderer, readViewFromSearch, resolveActiveView, type DynamicViewProps, } from './dynamic-view';
|
|
6
6
|
export * from './dynamic-form';
|
|
7
7
|
export { ActionModalDispatcher, type ActionModalProps, } from './action-modal-dispatcher';
|
|
8
8
|
export { ModelActionToolbar, useModelActions, type ModelActionToolbarProps, type ActionPlacement, } from './model-action-toolbar';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export * from './types';
|
|
|
7
7
|
export * from './options-context';
|
|
8
8
|
export * from './dynamic-table';
|
|
9
9
|
export { DynamicKanban, deriveStages, groupByStage, isTransitionAllowed, applyOptimisticMove, selectCardColumns, UNASSIGNED_LANE, } from './dynamic-kanban';
|
|
10
|
-
export { DynamicView, resolveViewRenderer, } from './dynamic-view';
|
|
10
|
+
export { DynamicView, resolveViewRenderer, readViewFromSearch, resolveActiveView, } from './dynamic-view';
|
|
11
11
|
export * from './dynamic-form';
|
|
12
12
|
export { ActionModalDispatcher, } from './action-modal-dispatcher';
|
|
13
13
|
export { ModelActionToolbar, useModelActions, } from './model-action-toolbar';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asteby/metacore-runtime-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "20.1.0",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"react-i18next": ">=13",
|
|
38
38
|
"sonner": ">=1.7",
|
|
39
39
|
"zustand": ">=5",
|
|
40
|
-
"@asteby/metacore-
|
|
41
|
-
"@asteby/metacore-
|
|
40
|
+
"@asteby/metacore-sdk": "^3.2.0",
|
|
41
|
+
"@asteby/metacore-ui": "^2.6.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"@tanstack/react-router": {
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"typescript": "^6.0.0",
|
|
68
68
|
"vitest": "^4.0.0",
|
|
69
69
|
"zustand": "^5.0.0",
|
|
70
|
-
"@asteby/metacore-
|
|
71
|
-
"@asteby/metacore-
|
|
70
|
+
"@asteby/metacore-sdk": "3.2.0",
|
|
71
|
+
"@asteby/metacore-ui": "2.6.0"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
74
|
"build": "tsc -p tsconfig.json",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// DynamicView routing decision: the same model exposes a "Board" nav
|
|
2
|
+
// (`?view=kanban`) and an "Issues" nav (`?view=list`), so the per-nav `view`
|
|
3
|
+
// signal — explicit prop or `?view=` query — must win over the model-level
|
|
4
|
+
// `metadata.view_type`. These are the pure helpers behind that decision.
|
|
5
|
+
import { describe, expect, it } from 'vitest'
|
|
6
|
+
import {
|
|
7
|
+
resolveViewRenderer,
|
|
8
|
+
readViewFromSearch,
|
|
9
|
+
resolveActiveView,
|
|
10
|
+
} from '../dynamic-view'
|
|
11
|
+
|
|
12
|
+
describe('resolveViewRenderer', () => {
|
|
13
|
+
it('maps kanban → kanban and everything else → table', () => {
|
|
14
|
+
expect(resolveViewRenderer('kanban')).toBe('kanban')
|
|
15
|
+
expect(resolveViewRenderer('list')).toBe('table')
|
|
16
|
+
expect(resolveViewRenderer('table')).toBe('table')
|
|
17
|
+
expect(resolveViewRenderer(undefined)).toBe('table')
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
describe('readViewFromSearch', () => {
|
|
22
|
+
it('reads view from a leading-? search string', () => {
|
|
23
|
+
expect(readViewFromSearch('?view=kanban&group_by=stage')).toBe('kanban')
|
|
24
|
+
})
|
|
25
|
+
it('reads view from a bare query string', () => {
|
|
26
|
+
expect(readViewFromSearch('view=list')).toBe('list')
|
|
27
|
+
})
|
|
28
|
+
it('reads view from a full href', () => {
|
|
29
|
+
expect(readViewFromSearch('/m/github_issues?view=kanban')).toBe('kanban')
|
|
30
|
+
})
|
|
31
|
+
it('returns undefined when absent or empty', () => {
|
|
32
|
+
expect(readViewFromSearch('?page=2')).toBeUndefined()
|
|
33
|
+
expect(readViewFromSearch('')).toBeUndefined()
|
|
34
|
+
expect(readViewFromSearch(undefined)).toBeUndefined()
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('resolveActiveView precedence', () => {
|
|
39
|
+
it('explicit prop wins over query and metadata', () => {
|
|
40
|
+
expect(resolveActiveView('kanban', '?view=list', 'table')).toBe('kanban')
|
|
41
|
+
})
|
|
42
|
+
it('query wins over metadata when no explicit prop', () => {
|
|
43
|
+
expect(resolveActiveView(undefined, '?view=kanban', 'table')).toBe(
|
|
44
|
+
'kanban',
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
it('falls back to metadata view_type when neither prop nor query present', () => {
|
|
48
|
+
expect(resolveActiveView(undefined, '?page=2', 'kanban')).toBe('kanban')
|
|
49
|
+
expect(resolveActiveView(undefined, undefined, 'table')).toBe('table')
|
|
50
|
+
})
|
|
51
|
+
it('Board vs Issues on the same model route to different surfaces', () => {
|
|
52
|
+
// model metadata default could be either; the nav query decides.
|
|
53
|
+
const boardView = resolveActiveView(
|
|
54
|
+
undefined,
|
|
55
|
+
'?view=kanban&group_by=stage',
|
|
56
|
+
'list',
|
|
57
|
+
)
|
|
58
|
+
const issuesView = resolveActiveView(undefined, '?view=list', 'list')
|
|
59
|
+
expect(resolveViewRenderer(boardView)).toBe('kanban')
|
|
60
|
+
expect(resolveViewRenderer(issuesView)).toBe('table')
|
|
61
|
+
})
|
|
62
|
+
})
|
package/src/dynamic-kanban.tsx
CHANGED
|
@@ -238,8 +238,8 @@ export interface DynamicKanbanProps {
|
|
|
238
238
|
/** Model key as registered on the backend (e.g. "issue"). */
|
|
239
239
|
model: string
|
|
240
240
|
/**
|
|
241
|
-
* Data endpoint base
|
|
242
|
-
* PUTs to `<base
|
|
241
|
+
* Data endpoint base — the org-scoped LIST endpoint (e.g.
|
|
242
|
+
* `/data/<model>/me`). The optimistic update PUTs to `<base>/<id>`.
|
|
243
243
|
*/
|
|
244
244
|
endpoint?: string
|
|
245
245
|
/** Bump to force a metadata + records refetch (same contract as DynamicTable). */
|
|
@@ -411,7 +411,11 @@ export function DynamicKanban({
|
|
|
411
411
|
|
|
412
412
|
try {
|
|
413
413
|
const base = endpoint || `/data/${model}`
|
|
414
|
-
|
|
414
|
+
// `base` is the org-scoped list endpoint (e.g. `/data/<model>/me`),
|
|
415
|
+
// so the per-record update is just `<base>/<id>` — same convention
|
|
416
|
+
// as DynamicTable/DynamicRelation. Appending an extra `/me` here
|
|
417
|
+
// produced `/data/<model>/me/me/<id>` → 404 on drag-to-move.
|
|
418
|
+
const res = (await api.put(`${base}/${cardId}`, {
|
|
415
419
|
[groupByKey]: destStage,
|
|
416
420
|
})) as { data?: ApiResponse<any> }
|
|
417
421
|
if (res?.data && res.data.success === false) {
|
|
@@ -473,7 +477,7 @@ export function DynamicKanban({
|
|
|
473
477
|
|
|
474
478
|
return (
|
|
475
479
|
<DndContext sensors={sensors} onDragStart={onDragStart} onDragEnd={onDragEnd}>
|
|
476
|
-
<div className="flex gap-4 overflow-x-auto p-1" data-testid="kanban-board">
|
|
480
|
+
<div className="flex min-w-0 gap-4 overflow-x-auto p-1" data-testid="kanban-board">
|
|
477
481
|
{lanes.map((stage) => {
|
|
478
482
|
const cards = grouped.get(stage.key) ?? []
|
|
479
483
|
const droppableAllowed =
|
|
@@ -568,6 +572,7 @@ interface KanbanLaneProps {
|
|
|
568
572
|
}
|
|
569
573
|
|
|
570
574
|
function KanbanLane({ stage, count, isDark, dimmed, disabled, children }: KanbanLaneProps) {
|
|
575
|
+
const { t } = useTranslation()
|
|
571
576
|
const { setNodeRef, isOver } = useDroppable({ id: stage.key, disabled })
|
|
572
577
|
const headerStyle = generateBadgeStyles(stage.color || optionColor(stage.key), {
|
|
573
578
|
isDark,
|
|
@@ -590,13 +595,13 @@ function KanbanLane({ stage, count, isDark, dimmed, disabled, children }: Kanban
|
|
|
590
595
|
className="border-0 text-xs font-semibold"
|
|
591
596
|
style={headerStyle}
|
|
592
597
|
>
|
|
593
|
-
{stage.label}
|
|
598
|
+
{t(stage.label, { defaultValue: stage.label })}
|
|
594
599
|
</Badge>
|
|
595
600
|
<span className="text-xs font-medium tabular-nums text-muted-foreground">
|
|
596
601
|
{count}
|
|
597
602
|
</span>
|
|
598
603
|
</div>
|
|
599
|
-
<ScrollArea className="max-h-[70vh]">
|
|
604
|
+
<ScrollArea className="min-h-[55vh] max-h-[70vh]">
|
|
600
605
|
<div className="flex flex-col gap-2 px-2 pb-3">{children}</div>
|
|
601
606
|
</ScrollArea>
|
|
602
607
|
</div>
|
package/src/dynamic-view.tsx
CHANGED
|
@@ -30,7 +30,45 @@ export function resolveViewRenderer(
|
|
|
30
30
|
return viewType === 'kanban' ? 'kanban' : 'table'
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Reads the `view` selector out of a URL search string (`?view=kanban`, a bare
|
|
35
|
+
* `view=kanban`, or a full href). Returns `undefined` when absent. The query is
|
|
36
|
+
* the per-NAV signal: the same model exposes a "Board" nav (`?view=kanban`) and
|
|
37
|
+
* an "Issues" nav (`?view=list`), so the query — not the model-level
|
|
38
|
+
* `metadata.view_type` — decides which surface to paint. SSR-safe.
|
|
39
|
+
*/
|
|
40
|
+
export function readViewFromSearch(search?: string): string | undefined {
|
|
41
|
+
if (!search) return undefined
|
|
42
|
+
const qIndex = search.indexOf('?')
|
|
43
|
+
const qs = qIndex === -1 ? search : search.slice(qIndex + 1)
|
|
44
|
+
const v = new URLSearchParams(qs).get('view')
|
|
45
|
+
return v ?? undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolves the effective view selector with the right precedence:
|
|
50
|
+
* 1. an explicit `view` prop the host passes (it owns the router), then
|
|
51
|
+
* 2. the `view` query param, then
|
|
52
|
+
* 3. the model-level `metadata.view_type` default.
|
|
53
|
+
* Exported pure for unit tests and host reuse.
|
|
54
|
+
*/
|
|
55
|
+
export function resolveActiveView(
|
|
56
|
+
explicit: string | undefined,
|
|
57
|
+
search: string | undefined,
|
|
58
|
+
metadataViewType: string | undefined,
|
|
59
|
+
): string | undefined {
|
|
60
|
+
return explicit ?? readViewFromSearch(search) ?? metadataViewType
|
|
61
|
+
}
|
|
62
|
+
|
|
33
63
|
export interface DynamicViewProps extends DynamicTableProps {
|
|
64
|
+
/**
|
|
65
|
+
* Explicit view selector from the host's router (e.g. the `view` search
|
|
66
|
+
* param resolved by tanstack-router). Takes precedence over the query string
|
|
67
|
+
* and the model's `metadata.view_type`. Pass this when the host owns routing
|
|
68
|
+
* so the same model can show `?view=kanban` (board) or `?view=list` (table)
|
|
69
|
+
* with no per-model metadata change.
|
|
70
|
+
*/
|
|
71
|
+
view?: string
|
|
34
72
|
/**
|
|
35
73
|
* Props forwarded to <DynamicKanban> when the model resolves to a kanban
|
|
36
74
|
* view. `model`/`endpoint`/`refreshTrigger`/`timeZone`/`currency` are shared
|
|
@@ -40,7 +78,7 @@ export interface DynamicViewProps extends DynamicTableProps {
|
|
|
40
78
|
kanbanProps?: Partial<Omit<DynamicKanbanProps, 'model' | 'endpoint'>>
|
|
41
79
|
}
|
|
42
80
|
|
|
43
|
-
export function DynamicView({ kanbanProps, ...tableProps }: DynamicViewProps) {
|
|
81
|
+
export function DynamicView({ view, kanbanProps, ...tableProps }: DynamicViewProps) {
|
|
44
82
|
const { model, endpoint, refreshTrigger, timeZone, currency } = tableProps
|
|
45
83
|
const api = useApi()
|
|
46
84
|
const cached = useMetadataCache((s) => s.getMetadata(model))
|
|
@@ -78,11 +116,20 @@ export function DynamicView({ kanbanProps, ...tableProps }: DynamicViewProps) {
|
|
|
78
116
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
79
117
|
}, [model])
|
|
80
118
|
|
|
119
|
+
// The per-nav `view` (explicit prop or `?view=` query) wins over the
|
|
120
|
+
// model-level metadata default so two navs on the same model route to
|
|
121
|
+
// different surfaces.
|
|
122
|
+
const search =
|
|
123
|
+
typeof window !== 'undefined' ? window.location.search : undefined
|
|
124
|
+
const effectiveView = resolveActiveView(view, search, viewType)
|
|
125
|
+
|
|
81
126
|
// Until we know the view type, render nothing transient-heavy: default to the
|
|
82
|
-
// table renderer only once resolved to avoid a table→kanban flash.
|
|
83
|
-
|
|
127
|
+
// table renderer only once resolved to avoid a table→kanban flash. An
|
|
128
|
+
// explicit/query view short-circuits the wait (we already know the surface).
|
|
129
|
+
if (!resolved && !cached && view === undefined && !readViewFromSearch(search))
|
|
130
|
+
return null
|
|
84
131
|
|
|
85
|
-
if (resolveViewRenderer(
|
|
132
|
+
if (resolveViewRenderer(effectiveView) === 'kanban') {
|
|
86
133
|
return (
|
|
87
134
|
<DynamicKanban
|
|
88
135
|
model={model}
|