@masterteam/work-center 0.0.62 → 0.0.63

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.
@@ -220,6 +220,13 @@ const WORK_CENTER_LEVEL_NAME_KEY = 'levelDataName';
220
220
  const WORK_CENTER_LEVEL_GROUP_FIELD = '__mtLevelGroup';
221
221
  /** Rendered in place of a missing / restricted LevelData label. */
222
222
  const WORK_CENTER_EMPTY_LABEL = '—';
223
+ /**
224
+ * Bootstrap `group` value meaning "the user turned grouping off". Needed
225
+ * because an absent `group` means "not chosen yet" and falls back to the
226
+ * default grouping — without a distinct value, clearing the group would be
227
+ * undone by the next navigation.
228
+ */
229
+ const WORK_CENTER_GROUP_NONE = 'none';
223
230
 
224
231
  const AREA_COLORS = ['violet', 'blue', 'emerald', 'amber', 'teal'];
225
232
  /** Fallback accent rotation used only when the backend sends no stat color. */
@@ -306,15 +313,23 @@ function parseSortInput(value) {
306
313
  : [];
307
314
  }
308
315
  /**
309
- * Resolves the grouped column key from bootstrap inputs. Mirrors the `sort`
310
- * rules: a present value wins, an explicit `null` clears, `undefined` keeps
311
- * whatever is already in state.
316
+ * Resolves the grouped column key from bootstrap inputs.
317
+ *
318
+ * A missing `group` means "not chosen yet" and keeps whatever state already
319
+ * has — which starts at the area default, so a first visit lands grouped.
320
+ * `WORK_CENTER_GROUP_NONE` is the only way to express "the user turned it
321
+ * off"; a bare `null` can't, because it is also what a host passes for an
322
+ * absent query param.
312
323
  */
313
324
  function resolveGroupByFromInputs(current, group) {
314
- if (hasParamValue(group)) {
315
- return typeof group === 'string' ? group.trim() || null : null;
325
+ if (!hasParamValue(group) || typeof group !== 'string') {
326
+ return current;
327
+ }
328
+ const parsed = group.trim();
329
+ if (!parsed || parsed === WORK_CENTER_GROUP_NONE) {
330
+ return null;
316
331
  }
317
- return group === null ? null : current;
332
+ return parsed;
318
333
  }
319
334
  function parseFiltersInput(value) {
320
335
  const parsed = parseJson(value);
@@ -447,7 +462,13 @@ function buildColumnsFromProperties(properties) {
447
462
  */
448
463
  function withLevelNameGrouping(key) {
449
464
  return key === WORK_CENTER_LEVEL_NAME_KEY
450
- ? { groupable: true, groupKey: WORK_CENTER_LEVEL_GROUP_FIELD }
465
+ ? {
466
+ groupable: true,
467
+ groupKey: WORK_CENTER_LEVEL_GROUP_FIELD,
468
+ // A level name names itself — "Level Name · Project Gamma" would just
469
+ // repeat the column header down every group.
470
+ showGroupColumnLabel: false,
471
+ }
451
472
  : {};
452
473
  }
453
474
  function buildColumns(columnsConfig) {
@@ -543,6 +564,19 @@ function buildKpis(stats, fallbackIcon) {
543
564
  color: stat.color?.trim() || KPI_COLORS[index % KPI_COLORS.length],
544
565
  }));
545
566
  }
567
+ /**
568
+ * Level Name grouping is the default view of the personal work areas — those
569
+ * rows come from all over the hierarchy, so "which project is this?" is the
570
+ * first question a user has. Workspace is already scoped to one record, so
571
+ * grouping it by that record would produce a single group.
572
+ */
573
+ function defaultGroupBy(area) {
574
+ return area === 'Workspace' ? null : WORK_CENTER_LEVEL_NAME_KEY;
575
+ }
576
+ /** The backend sort that renders a grouped column as contiguous rows. */
577
+ function sortForGroup(groupBy) {
578
+ return groupBy ? [{ field: groupBy, dir: 'asc' }] : [];
579
+ }
546
580
  function createDefaultContext(area) {
547
581
  return {
548
582
  area,
@@ -551,7 +585,7 @@ function createDefaultContext(area) {
551
585
  selectedCardKey: null,
552
586
  page: 1,
553
587
  pageSize: WORK_CENTER_LOCAL_FETCH_SIZE,
554
- sort: [],
588
+ sort: sortForGroup(defaultGroupBy(area)),
555
589
  runtimeFilters: [],
556
590
  includeStats: true,
557
591
  };
@@ -561,7 +595,7 @@ function createDefaultAreaState(area) {
561
595
  context: createDefaultContext(area),
562
596
  headerFilters: [],
563
597
  columnFilters: [],
564
- groupBy: null,
598
+ groupBy: defaultGroupBy(area),
565
599
  menuItems: [],
566
600
  rows: [],
567
601
  columns: [],
@@ -611,7 +645,14 @@ function normalizeContext(context) {
611
645
  runtimeFilters: sanitizeFilters(context.runtimeFilters),
612
646
  };
613
647
  }
614
- function buildContextFromInputs(currentContext, area, inputs = {}) {
648
+ function buildContextFromInputs(currentContext, area, inputs = {},
649
+ /**
650
+ * Already-resolved grouped column. Grouping is executed as a backend sort,
651
+ * so when the host supplies no explicit `sort` the grouped column defines
652
+ * it — that is what makes the default grouping reach the very first request
653
+ * instead of costing a second round trip.
654
+ */
655
+ groupBy = null) {
615
656
  const parsedCardKey = toSelectedCardKey(inputs.card);
616
657
  const parsedTemplateId = toOptionalInt(inputs.templateId);
617
658
  const parsedLevelDataId = toOptionalInt(inputs.levelDataId);
@@ -628,9 +669,11 @@ function buildContextFromInputs(currentContext, area, inputs = {}) {
628
669
  levelDataId: parsedLevelDataId ?? currentContext.levelDataId,
629
670
  sort: hasParamValue(inputs.sort)
630
671
  ? parseSortInput(inputs.sort)
631
- : inputs.sort === null
632
- ? []
633
- : currentContext.sort,
672
+ : groupBy
673
+ ? sortForGroup(groupBy)
674
+ : inputs.sort === null
675
+ ? []
676
+ : currentContext.sort,
634
677
  runtimeFilters: hasParamValue(inputs.filters)
635
678
  ? parseFiltersInput(inputs.filters)
636
679
  : inputs.filters === null
@@ -664,8 +707,20 @@ function mapRuntimeResponse(current, response) {
664
707
  const columns = buildColumns(selectedCard?.columnsConfig ?? selectedCardFromList?.columnsConfig);
665
708
  const rows = toDisplayRows(selectedCard?.items ?? [], columns);
666
709
  const kpis = buildKpis(selectedCard?.stats ?? [], selectedMenuItem?.icon ?? null);
710
+ // A card that doesn't advertise the grouped column can't be grouped by it —
711
+ // drop it here rather than letting the table discover the stale column and
712
+ // bounce a second request. The sort goes with it when it was the one the
713
+ // grouping asked for.
714
+ const groupBy = current.groupBy && columns.some((column) => column.key === current.groupBy)
715
+ ? current.groupBy
716
+ : null;
717
+ const droppedGroup = !groupBy && current.groupBy;
718
+ const sort = droppedGroup && isSortForGroup(current.context.sort, current.groupBy)
719
+ ? []
720
+ : current.context.sort;
667
721
  return {
668
722
  ...current,
723
+ groupBy,
669
724
  menuItems,
670
725
  rows,
671
726
  columns,
@@ -679,11 +734,16 @@ function mapRuntimeResponse(current, response) {
679
734
  context: {
680
735
  ...current.context,
681
736
  selectedCardKey,
737
+ sort,
682
738
  page: selectedCard?.pagination.page ?? current.context.page,
683
739
  pageSize: selectedCard?.pagination.pageSize ?? current.context.pageSize,
684
740
  },
685
741
  };
686
742
  }
743
+ /** True when `sort` is exactly the single rule a grouping would produce. */
744
+ function isSortForGroup(sort, groupBy) {
745
+ return !!groupBy && sort.length === 1 && sort[0].field === groupBy;
746
+ }
687
747
 
688
748
  var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
689
749
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -784,8 +844,10 @@ let WorkCenterState = class WorkCenterState {
784
844
  setParams(ctx, action) {
785
845
  const state = ctx.getState();
786
846
  const current = state.byArea[action.area];
787
- const nextContext = buildContextFromInputs(current.context, action.area, action.inputs);
847
+ // Resolve grouping first: with no explicit `sort` input it is what defines
848
+ // the backend sort, so the default grouping reaches the first request.
788
849
  const nextGroupBy = resolveGroupByFromInputs(current.groupBy, action.inputs?.group);
850
+ const nextContext = buildContextFromInputs(current.context, action.area, action.inputs, nextGroupBy);
789
851
  const areaChanged = state.activeArea !== action.area;
790
852
  const contextChanged = !isSameContext(current.context, nextContext);
791
853
  const groupChanged = nextGroupBy !== current.groupBy;
@@ -3189,5 +3251,5 @@ const APP_STATES = [WorkCenterState];
3189
3251
  * Generated bundle index. Do not edit.
3190
3252
  */
3191
3253
 
3192
- export { APP_STATES, ApplyColumnFilters, ApplyHeaderFilters, ClearAllFilters, EnterArea, HydrateFromContext, LoadRuntime, SetGroupBy, SetLookups, SetParams, WORK_CENTER_EMPTY_LABEL, WORK_CENTER_LEVEL_GROUP_FIELD, WORK_CENTER_LEVEL_NAME_KEY, WORK_CENTER_MAX_FILTERS, WORK_CENTER_MAX_PAGE_SIZE, WORK_CENTER_MAX_SORT, WORK_CENTER_QUERY_VERSION, WorkCenterActionKey, WorkCenterClientFormModal, WorkCenterFacade, WorkCenterItemModal, WorkCenterItemModalRoute, WorkCenterPage, WorkCenterState, decodeWorkCenterRouteContextKey, encodeWorkCenterRouteContextKey };
3254
+ export { APP_STATES, ApplyColumnFilters, ApplyHeaderFilters, ClearAllFilters, EnterArea, HydrateFromContext, LoadRuntime, SetGroupBy, SetLookups, SetParams, WORK_CENTER_EMPTY_LABEL, WORK_CENTER_GROUP_NONE, WORK_CENTER_LEVEL_GROUP_FIELD, WORK_CENTER_LEVEL_NAME_KEY, WORK_CENTER_MAX_FILTERS, WORK_CENTER_MAX_PAGE_SIZE, WORK_CENTER_MAX_SORT, WORK_CENTER_QUERY_VERSION, WorkCenterActionKey, WorkCenterClientFormModal, WorkCenterFacade, WorkCenterItemModal, WorkCenterItemModalRoute, WorkCenterPage, WorkCenterState, decodeWorkCenterRouteContextKey, encodeWorkCenterRouteContextKey };
3193
3255
  //# sourceMappingURL=masterteam-work-center.mjs.map