@growthub/cli 0.13.6 → 0.13.7
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/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/metadata-graph/route.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceActivationPanel.jsx +172 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/NangoConnectionPanel.jsx +37 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +170 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +44 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +537 -39
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +8 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +534 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +82 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +3 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/templates/seeded-configs/project-management.config.json +4 -4
- package/package.json +1 -1
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
GitBranch,
|
|
30
30
|
Hash,
|
|
31
31
|
Home,
|
|
32
|
+
Hourglass,
|
|
32
33
|
Import,
|
|
33
34
|
Italic,
|
|
34
35
|
Layers,
|
|
@@ -85,8 +86,10 @@ import {
|
|
|
85
86
|
deriveWidgetDependencyContract
|
|
86
87
|
} from "@/lib/workspace-chart-values";
|
|
87
88
|
import { selectObjectFilterableFields, selectObjectSortableFields } from "@/lib/workspace-metadata-selectors";
|
|
89
|
+
import { deriveWorkspaceActivationState } from "@/lib/workspace-activation";
|
|
88
90
|
import { HelperSidecar } from "./data-model/components/HelperSidecar.jsx";
|
|
89
91
|
import { WorkspaceRail } from "./workspace-rail.jsx";
|
|
92
|
+
import { WorkspaceActivationPanel } from "./components/WorkspaceActivationPanel.jsx";
|
|
90
93
|
|
|
91
94
|
// Workspace Metadata Graph V1 — typed dependency contracts.
|
|
92
95
|
// Used by sidecar dependency summaries; the existing chart hydration path
|
|
@@ -110,6 +113,7 @@ const DATA_MODEL_SOURCE_TYPE = "workspace-data-model";
|
|
|
110
113
|
const LIVE_SOURCE_TYPE = "workspace-source-records";
|
|
111
114
|
const TESTED_SOURCE_STATUSES = new Set(["connected", "approved", "ok", "success"]);
|
|
112
115
|
const HIDDEN_SANDBOX_OBJECT_IDS = new Set(["workspace-helper-sandbox"]);
|
|
116
|
+
const WORKSPACE_UI_CACHE_OBJECT_ID = "workspace-ui-cache";
|
|
113
117
|
|
|
114
118
|
const SOURCE_TYPE_OBJECTS = [
|
|
115
119
|
{
|
|
@@ -173,6 +177,26 @@ const CHART_TYPE_ICONS = {
|
|
|
173
177
|
|
|
174
178
|
const VISIBLE_CHART_TYPES = KNOWN_CHART_TYPES.filter((type) => type !== "line");
|
|
175
179
|
|
|
180
|
+
const TABLE_VIEW_TYPES = ["gantt", "board", "calendar", "timeline"];
|
|
181
|
+
const TABLE_VIEW_LABELS = {
|
|
182
|
+
gantt: "Gantt",
|
|
183
|
+
board: "Board",
|
|
184
|
+
calendar: "Calendar",
|
|
185
|
+
timeline: "Timeline"
|
|
186
|
+
};
|
|
187
|
+
const TABLE_VIEW_HELP = {
|
|
188
|
+
gantt: "Track dependencies and baselines",
|
|
189
|
+
board: "Track work in a Kanban view",
|
|
190
|
+
calendar: "Plan weekly or monthly work",
|
|
191
|
+
timeline: "Schedule work over time"
|
|
192
|
+
};
|
|
193
|
+
const TABLE_VIEW_ICONS = {
|
|
194
|
+
gantt: GitBranch,
|
|
195
|
+
board: Columns3,
|
|
196
|
+
calendar: Calendar,
|
|
197
|
+
timeline: Rows3
|
|
198
|
+
};
|
|
199
|
+
|
|
176
200
|
// User-facing labels for the Twenty-style Y-axis operation dropdown.
|
|
177
201
|
// Keys must stay in sync with `lib/workspace-chart-values.js#KNOWN_AGGREGATIONS`
|
|
178
202
|
// and the validator in `lib/workspace-schema.js`.
|
|
@@ -407,6 +431,44 @@ function getWorkflowSandboxObject(config) {
|
|
|
407
431
|
}) || null;
|
|
408
432
|
}
|
|
409
433
|
|
|
434
|
+
function getWorkspaceUiCache(config) {
|
|
435
|
+
const object = getDataModelObject(config, WORKSPACE_UI_CACHE_OBJECT_ID);
|
|
436
|
+
const row = Array.isArray(object?.rows) ? object.rows.find((entry) => entry?.id === "activation") : null;
|
|
437
|
+
return row && typeof row === "object" ? row : {};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function setWorkspaceUiCacheFlag(config, key, value) {
|
|
441
|
+
const dataModel = config?.dataModel && typeof config.dataModel === "object" ? config.dataModel : {};
|
|
442
|
+
const objects = Array.isArray(dataModel.objects) ? dataModel.objects : [];
|
|
443
|
+
const cacheObject = objects.find((object) => object?.id === WORKSPACE_UI_CACHE_OBJECT_ID) || {
|
|
444
|
+
id: WORKSPACE_UI_CACHE_OBJECT_ID,
|
|
445
|
+
label: "Workspace UI Cache",
|
|
446
|
+
source: "Workspace UI Cache",
|
|
447
|
+
objectType: "custom",
|
|
448
|
+
icon: "Settings",
|
|
449
|
+
columns: ["id", key],
|
|
450
|
+
rows: [],
|
|
451
|
+
binding: { mode: "manual", source: "Workspace UI Cache" }
|
|
452
|
+
};
|
|
453
|
+
const columns = Array.from(new Set([...(Array.isArray(cacheObject.columns) ? cacheObject.columns : ["id"]), key]));
|
|
454
|
+
const rows = Array.isArray(cacheObject.rows) ? cacheObject.rows : [];
|
|
455
|
+
const hasActivationRow = rows.some((row) => row?.id === "activation");
|
|
456
|
+
const nextRows = hasActivationRow
|
|
457
|
+
? rows.map((row) => row?.id === "activation" ? { ...row, [key]: value } : row)
|
|
458
|
+
: [...rows, { id: "activation", [key]: value }];
|
|
459
|
+
const nextCacheObject = { ...cacheObject, columns, rows: nextRows };
|
|
460
|
+
const nextObjects = objects.some((object) => object?.id === WORKSPACE_UI_CACHE_OBJECT_ID)
|
|
461
|
+
? objects.map((object) => object?.id === WORKSPACE_UI_CACHE_OBJECT_ID ? nextCacheObject : object)
|
|
462
|
+
: [...objects, nextCacheObject];
|
|
463
|
+
return {
|
|
464
|
+
...config,
|
|
465
|
+
dataModel: {
|
|
466
|
+
...dataModel,
|
|
467
|
+
objects: nextObjects
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
410
472
|
function listBuilderWorkflowItems(config) {
|
|
411
473
|
const navFolders = getDataModelObject(config, "nav-folders");
|
|
412
474
|
const rows = Array.isArray(navFolders?.rows) ? navFolders.rows : [];
|
|
@@ -476,11 +538,76 @@ function updateWorkflowFolderItemInConfig(config, workflow, updater) {
|
|
|
476
538
|
}
|
|
477
539
|
|
|
478
540
|
function createBlankWorkflowSandboxRow(rowId, nowIso) {
|
|
541
|
+
const registryId = "growthub-workspace-smoke-api";
|
|
479
542
|
const draftGraph = JSON.stringify({
|
|
480
|
-
version: "
|
|
543
|
+
version: "1",
|
|
481
544
|
provider: "growthub-native",
|
|
482
|
-
nodes: [
|
|
483
|
-
|
|
545
|
+
nodes: [
|
|
546
|
+
{
|
|
547
|
+
id: "input",
|
|
548
|
+
type: "input",
|
|
549
|
+
label: "Input",
|
|
550
|
+
subtitle: "Manual or source payload",
|
|
551
|
+
config: { inputMode: "manual", samplePayload: {}, sourceType: "", sourceId: "", entityId: "", filterMode: "and", filters: [] }
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
id: "api-request",
|
|
555
|
+
type: "api-registry-call",
|
|
556
|
+
label: "API Registry",
|
|
557
|
+
subtitle: `${registryId} · GET /api/workspace`,
|
|
558
|
+
config: {
|
|
559
|
+
registryId,
|
|
560
|
+
integrationId: registryId,
|
|
561
|
+
baseUrl: "http://localhost:3000",
|
|
562
|
+
endpoint: "/api/workspace",
|
|
563
|
+
method: "GET",
|
|
564
|
+
authRef: "",
|
|
565
|
+
queryParams: {},
|
|
566
|
+
bodyTemplate: "",
|
|
567
|
+
requestHeadersMetadata: { authHeaderName: "x-api-key", authPrefix: "", contentType: "" },
|
|
568
|
+
timeoutMs: 30000
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
id: "transform",
|
|
573
|
+
type: "transform-filter",
|
|
574
|
+
label: "Transform",
|
|
575
|
+
subtitle: "Map fields and filter rows",
|
|
576
|
+
config: {
|
|
577
|
+
rootPath: "",
|
|
578
|
+
mode: "json",
|
|
579
|
+
responseMode: "json",
|
|
580
|
+
fieldMap: {},
|
|
581
|
+
includeFields: [],
|
|
582
|
+
excludeFields: [],
|
|
583
|
+
computedFields: {},
|
|
584
|
+
filters: [],
|
|
585
|
+
filterMode: "and",
|
|
586
|
+
maxRows: 0
|
|
587
|
+
}
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
id: "result",
|
|
591
|
+
type: "tool-result",
|
|
592
|
+
label: "Result",
|
|
593
|
+
subtitle: "Save status and response",
|
|
594
|
+
config: {
|
|
595
|
+
successStatusCodes: [200],
|
|
596
|
+
writeLastResponse: true,
|
|
597
|
+
writeSourceRecord: true,
|
|
598
|
+
sourceRecordId: "",
|
|
599
|
+
outputMode: "normalized-json",
|
|
600
|
+
previewFields: [],
|
|
601
|
+
statusField: "status",
|
|
602
|
+
lastTestedField: "lastTested"
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
],
|
|
606
|
+
edges: [
|
|
607
|
+
{ from: "input", to: "api-request", passes: "payload, filters, variables" },
|
|
608
|
+
{ from: "api-request", to: "transform", passes: "provider-response" },
|
|
609
|
+
{ from: "transform", to: "result", passes: "normalized-output" }
|
|
610
|
+
]
|
|
484
611
|
}, null, 2);
|
|
485
612
|
return {
|
|
486
613
|
Name: rowId,
|
|
@@ -520,11 +647,77 @@ function createBlankWorkflowSandboxRow(rowId, nowIso) {
|
|
|
520
647
|
};
|
|
521
648
|
}
|
|
522
649
|
|
|
650
|
+
function createWorkflowApiRegistryObject() {
|
|
651
|
+
const preset = OBJECT_TYPE_PRESETS["api-registry"] || {};
|
|
652
|
+
const columns = Array.isArray(preset.columns) ? [...preset.columns] : ["integrationId"];
|
|
653
|
+
return {
|
|
654
|
+
id: "workflow-api-registry",
|
|
655
|
+
label: preset.label || "API Registry",
|
|
656
|
+
source: preset.label || "API Registry",
|
|
657
|
+
objectType: "api-registry",
|
|
658
|
+
icon: preset.icon || "Code2",
|
|
659
|
+
columns,
|
|
660
|
+
rows: [
|
|
661
|
+
{
|
|
662
|
+
integrationId: "growthub-workspace-smoke-api",
|
|
663
|
+
authRef: "",
|
|
664
|
+
baseUrl: "http://localhost:3000",
|
|
665
|
+
endpoint: "/api/workspace",
|
|
666
|
+
method: "GET",
|
|
667
|
+
status: "draft",
|
|
668
|
+
lastTested: "",
|
|
669
|
+
lastResponse: "",
|
|
670
|
+
entityTypes: "workspace",
|
|
671
|
+
description: "Local workspace smoke endpoint for first workflow setup.",
|
|
672
|
+
connectorKind: "custom-http",
|
|
673
|
+
resolverTemplateId: "",
|
|
674
|
+
schemaVersion: "1",
|
|
675
|
+
capabilities: "read",
|
|
676
|
+
executionLane: "sandbox-local"
|
|
677
|
+
}
|
|
678
|
+
],
|
|
679
|
+
binding: { mode: "manual", source: "Data Model" },
|
|
680
|
+
relations: Array.isArray(preset.relations) ? preset.relations.map((relation) => ({ ...relation })) : [],
|
|
681
|
+
fieldSettings: { hidden: [], order: columns }
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function createWorkflowSandboxObject() {
|
|
686
|
+
const preset = OBJECT_TYPE_PRESETS["sandbox-environment"] || {};
|
|
687
|
+
const columns = Array.isArray(preset.columns) ? [...preset.columns] : ["Name"];
|
|
688
|
+
return {
|
|
689
|
+
id: "sandbox-environments",
|
|
690
|
+
label: preset.label || "Sandbox Environments",
|
|
691
|
+
source: preset.label || "Sandbox Environments",
|
|
692
|
+
objectType: "sandbox-environment",
|
|
693
|
+
icon: preset.icon || "Terminal",
|
|
694
|
+
columns,
|
|
695
|
+
rows: [],
|
|
696
|
+
binding: { mode: "manual", source: "Data Model" },
|
|
697
|
+
relations: Array.isArray(preset.relations) ? preset.relations.map((relation) => ({ ...relation })) : [],
|
|
698
|
+
fieldSettings: { hidden: [], order: columns }
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
523
702
|
function addWorkflowFolderShortcut(dataModel, workflow) {
|
|
524
703
|
const objects = Array.isArray(dataModel?.objects) ? dataModel.objects : [];
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
704
|
+
const seededObjects = objects.some((object) => object?.id === "nav-folders")
|
|
705
|
+
? objects
|
|
706
|
+
: [
|
|
707
|
+
...objects,
|
|
708
|
+
{
|
|
709
|
+
id: "nav-folders",
|
|
710
|
+
label: "Custom Folders",
|
|
711
|
+
source: "Custom Folders",
|
|
712
|
+
objectType: "custom",
|
|
713
|
+
icon: "Folder",
|
|
714
|
+
columns: ["name", "order", "collapsed", "items"],
|
|
715
|
+
rows: [],
|
|
716
|
+
binding: { mode: "manual", source: "Custom Folders" }
|
|
717
|
+
}
|
|
718
|
+
];
|
|
719
|
+
const navIndex = seededObjects.findIndex((object) => object?.id === "nav-folders");
|
|
720
|
+
const navObject = seededObjects[navIndex];
|
|
528
721
|
const rows = Array.isArray(navObject.rows) ? navObject.rows : [];
|
|
529
722
|
const folderName = "Builder";
|
|
530
723
|
const existingFolder = rows.find((row) => String(row?.name || "").trim().toLowerCase() === folderName.toLowerCase());
|
|
@@ -562,7 +755,7 @@ function addWorkflowFolderShortcut(dataModel, workflow) {
|
|
|
562
755
|
];
|
|
563
756
|
return {
|
|
564
757
|
...dataModel,
|
|
565
|
-
objects:
|
|
758
|
+
objects: seededObjects.map((object, index) => index === navIndex ? { ...navObject, rows: nextRows } : object)
|
|
566
759
|
};
|
|
567
760
|
}
|
|
568
761
|
|
|
@@ -939,6 +1132,43 @@ function getVisibleColumns(widget) {
|
|
|
939
1132
|
return ordered.filter((name) => !hidden.has(name));
|
|
940
1133
|
}
|
|
941
1134
|
|
|
1135
|
+
function getTableViewSettings(widget) {
|
|
1136
|
+
const settings = widget?.config?.fieldSettings?.tableView;
|
|
1137
|
+
return isPlainConfigObject(settings) ? settings : {};
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
function getTableViewType(widget) {
|
|
1141
|
+
const type = getTableViewSettings(widget).type;
|
|
1142
|
+
return TABLE_VIEW_TYPES.includes(type) ? type : "";
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
function getFieldValue(row, field, fallback = "") {
|
|
1146
|
+
if (!row || !field) return fallback;
|
|
1147
|
+
const value = row[field];
|
|
1148
|
+
if (value === null || value === undefined || value === "") return fallback;
|
|
1149
|
+
return String(value);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
function firstMatchingField(columns, candidates) {
|
|
1153
|
+
const lower = columns.map((column) => [column, String(column).toLowerCase()]);
|
|
1154
|
+
for (const candidate of candidates) {
|
|
1155
|
+
const found = lower.find(([, name]) => name.includes(candidate));
|
|
1156
|
+
if (found) return found[0];
|
|
1157
|
+
}
|
|
1158
|
+
return columns[0] || "";
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
function resolveTableViewFields(widget) {
|
|
1162
|
+
const columns = getVisibleColumns(widget);
|
|
1163
|
+
const settings = getTableViewSettings(widget);
|
|
1164
|
+
return {
|
|
1165
|
+
titleField: settings.titleField || firstMatchingField(columns, ["title", "name", "task", "project"]),
|
|
1166
|
+
statusField: settings.statusField || firstMatchingField(columns, ["status", "stage", "state", "lane"]),
|
|
1167
|
+
startDateField: settings.startDateField || firstMatchingField(columns, ["start", "created", "date"]),
|
|
1168
|
+
endDateField: settings.endDateField || firstMatchingField(columns, ["end", "due", "deadline", "target"]),
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
|
|
942
1172
|
function withFieldSettings(config, patch) {
|
|
943
1173
|
const current = isPlainConfigObject(config?.fieldSettings) ? config.fieldSettings : { hidden: [], order: [] };
|
|
944
1174
|
return {
|
|
@@ -951,6 +1181,21 @@ function withFieldSettings(config, patch) {
|
|
|
951
1181
|
};
|
|
952
1182
|
}
|
|
953
1183
|
|
|
1184
|
+
function withTableViewSettings(config, patch) {
|
|
1185
|
+
const current = isPlainConfigObject(config?.fieldSettings) ? config.fieldSettings : {};
|
|
1186
|
+
const currentTableView = isPlainConfigObject(current.tableView) ? current.tableView : {};
|
|
1187
|
+
return {
|
|
1188
|
+
...config,
|
|
1189
|
+
fieldSettings: {
|
|
1190
|
+
...current,
|
|
1191
|
+
tableView: {
|
|
1192
|
+
...currentTableView,
|
|
1193
|
+
...patch
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
|
|
954
1199
|
function isPlainConfigObject(value) {
|
|
955
1200
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
956
1201
|
}
|
|
@@ -1115,6 +1360,10 @@ function summarizeFields(widget) {
|
|
|
1115
1360
|
return hidden ? `${total - hidden} of ${total} shown` : `${total} shown`;
|
|
1116
1361
|
}
|
|
1117
1362
|
|
|
1363
|
+
function summarizeTableView(widget) {
|
|
1364
|
+
return TABLE_VIEW_LABELS[getTableViewType(widget)] || "Table";
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1118
1367
|
function summarizeSort(widget) {
|
|
1119
1368
|
const sort = getSortClauses(widget);
|
|
1120
1369
|
if (!sort.length) return "›";
|
|
@@ -2633,6 +2882,105 @@ function SortSubPanel({ widget, dataModelTable, onChange, onBack }) {
|
|
|
2633
2882
|
</section>;
|
|
2634
2883
|
}
|
|
2635
2884
|
|
|
2885
|
+
function TableViewConfig({ widget, dataModelTable, onChange, onSubPage }) {
|
|
2886
|
+
const viewWidget = dataModelTable ? resolveViewWidget(widget, [dataModelTable]) : widget;
|
|
2887
|
+
const columns = getVisibleColumns(viewWidget);
|
|
2888
|
+
const tableView = getTableViewSettings(widget);
|
|
2889
|
+
const activeType = getTableViewType(widget);
|
|
2890
|
+
const setTableView = (patch) => onChange(withTableViewSettings(widget.config, patch));
|
|
2891
|
+
const fieldOptions = columns.length ? columns : getColumnList(viewWidget);
|
|
2892
|
+
return (
|
|
2893
|
+
<div className="workspace-twenty-config workspace-table-view-config" role="group" aria-label="Table view widget settings">
|
|
2894
|
+
<p className="workspace-panel-label">Settings</p>
|
|
2895
|
+
<WidgetSettingsRow icon={Table2} label="Layout" value={summarizeTableView(widget)} disabled />
|
|
2896
|
+
<div className="workspace-chart-type-tabs workspace-table-view-tabs" role="tablist" aria-label="Table view type">
|
|
2897
|
+
{TABLE_VIEW_TYPES.map((type) => {
|
|
2898
|
+
const TypeIcon = TABLE_VIEW_ICONS[type];
|
|
2899
|
+
return (
|
|
2900
|
+
<button
|
|
2901
|
+
key={type}
|
|
2902
|
+
type="button"
|
|
2903
|
+
role="tab"
|
|
2904
|
+
aria-selected={activeType === type}
|
|
2905
|
+
className={activeType === type ? "active" : ""}
|
|
2906
|
+
onClick={() => setTableView({ type: activeType === type ? "" : type })}
|
|
2907
|
+
title={TABLE_VIEW_HELP[type]}
|
|
2908
|
+
>
|
|
2909
|
+
<IconGlyph icon={TypeIcon} size={17} />
|
|
2910
|
+
<em>{TABLE_VIEW_LABELS[type]}</em>
|
|
2911
|
+
</button>
|
|
2912
|
+
);
|
|
2913
|
+
})}
|
|
2914
|
+
</div>
|
|
2915
|
+
{activeType ? <p className="workspace-panel-hint">{TABLE_VIEW_HELP[activeType]}</p> : null}
|
|
2916
|
+
|
|
2917
|
+
<WidgetSettingsRow icon={Box} label="Source" value={summarizeSource(widget)} onClick={() => onSubPage("source")} />
|
|
2918
|
+
<WidgetSettingsRow icon={List} label="Fields" value={summarizeFields(viewWidget)} onClick={() => onSubPage("fields")} />
|
|
2919
|
+
<WidgetSettingsRow icon={Filter} label="Filter" value={summarizeFilter(viewWidget)} onClick={() => onSubPage("filter")} />
|
|
2920
|
+
<WidgetSettingsRow icon={SlidersHorizontal} label="Sort" value={summarizeSort(viewWidget)} onClick={() => onSubPage("sort")} />
|
|
2921
|
+
|
|
2922
|
+
{activeType ? (
|
|
2923
|
+
<>
|
|
2924
|
+
<p className="workspace-panel-label">View fields</p>
|
|
2925
|
+
<WidgetSelectRow icon={Type} label="Title">
|
|
2926
|
+
<FieldDropdown
|
|
2927
|
+
fields={fieldOptions}
|
|
2928
|
+
value={tableView.titleField || ""}
|
|
2929
|
+
onChange={(field) => setTableView({ titleField: field })}
|
|
2930
|
+
placeholder="Auto"
|
|
2931
|
+
disabled={!fieldOptions.length}
|
|
2932
|
+
/>
|
|
2933
|
+
</WidgetSelectRow>
|
|
2934
|
+
{activeType === "board" ? (
|
|
2935
|
+
<WidgetSelectRow icon={Columns3} label="Status">
|
|
2936
|
+
<FieldDropdown
|
|
2937
|
+
fields={fieldOptions}
|
|
2938
|
+
value={tableView.statusField || ""}
|
|
2939
|
+
onChange={(field) => setTableView({ statusField: field })}
|
|
2940
|
+
placeholder="Auto"
|
|
2941
|
+
disabled={!fieldOptions.length}
|
|
2942
|
+
/>
|
|
2943
|
+
</WidgetSelectRow>
|
|
2944
|
+
) : null}
|
|
2945
|
+
{activeType === "gantt" || activeType === "timeline" ? (
|
|
2946
|
+
<>
|
|
2947
|
+
<WidgetSelectRow icon={Calendar} label="Start">
|
|
2948
|
+
<FieldDropdown
|
|
2949
|
+
fields={fieldOptions}
|
|
2950
|
+
value={tableView.startDateField || ""}
|
|
2951
|
+
onChange={(field) => setTableView({ startDateField: field })}
|
|
2952
|
+
placeholder="Auto"
|
|
2953
|
+
disabled={!fieldOptions.length}
|
|
2954
|
+
/>
|
|
2955
|
+
</WidgetSelectRow>
|
|
2956
|
+
<WidgetSelectRow icon={Calendar} label="End">
|
|
2957
|
+
<FieldDropdown
|
|
2958
|
+
fields={fieldOptions}
|
|
2959
|
+
value={tableView.endDateField || ""}
|
|
2960
|
+
onChange={(field) => setTableView({ endDateField: field })}
|
|
2961
|
+
placeholder="Auto"
|
|
2962
|
+
disabled={!fieldOptions.length}
|
|
2963
|
+
/>
|
|
2964
|
+
</WidgetSelectRow>
|
|
2965
|
+
</>
|
|
2966
|
+
) : null}
|
|
2967
|
+
{activeType === "calendar" ? (
|
|
2968
|
+
<WidgetSelectRow icon={Calendar} label="Date">
|
|
2969
|
+
<FieldDropdown
|
|
2970
|
+
fields={fieldOptions}
|
|
2971
|
+
value={tableView.startDateField || ""}
|
|
2972
|
+
onChange={(field) => setTableView({ startDateField: field })}
|
|
2973
|
+
placeholder="Auto"
|
|
2974
|
+
disabled={!fieldOptions.length}
|
|
2975
|
+
/>
|
|
2976
|
+
</WidgetSelectRow>
|
|
2977
|
+
) : null}
|
|
2978
|
+
</>
|
|
2979
|
+
) : null}
|
|
2980
|
+
</div>
|
|
2981
|
+
);
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2636
2984
|
function FilterSubPanel({ widget, integrations, dataModelTable, adapterConfig, onRefreshAndSave, onChange, onBack }) {
|
|
2637
2985
|
const viewWidget = dataModelTable ? resolveViewWidget(widget, [dataModelTable]) : widget;
|
|
2638
2986
|
const binding = widget.config?.binding || {};
|
|
@@ -3374,6 +3722,55 @@ function IframePreviewModal({ widget, onClose }) {
|
|
|
3374
3722
|
</div>;
|
|
3375
3723
|
}
|
|
3376
3724
|
|
|
3725
|
+
function TableTransformPreview({ widget, columns, rows }) {
|
|
3726
|
+
const type = getTableViewType(widget);
|
|
3727
|
+
const safeRows = Array.isArray(rows) ? rows : [];
|
|
3728
|
+
if (!type) {
|
|
3729
|
+
return <div
|
|
3730
|
+
className="workspace-view-table"
|
|
3731
|
+
aria-label={`${widget.title} preview`}
|
|
3732
|
+
style={{ "--workspace-view-columns": columns.length }}
|
|
3733
|
+
>
|
|
3734
|
+
<div>{columns.map((column) => <span key={column}>{column}</span>)}</div>
|
|
3735
|
+
{safeRows.slice(0, 6).map((row, rowIndex) => <div key={rowIndex}>
|
|
3736
|
+
{columns.map((column) => <span key={column}>{row?.[column] || ""}</span>)}
|
|
3737
|
+
</div>)}
|
|
3738
|
+
{!columns.length && !safeRows.length ? <div className="workspace-view-empty">Select a source</div> : null}
|
|
3739
|
+
<footer>Calculate</footer>
|
|
3740
|
+
</div>;
|
|
3741
|
+
}
|
|
3742
|
+
const fields = resolveTableViewFields(widget);
|
|
3743
|
+
if (type === "board") {
|
|
3744
|
+
const groups = safeRows.slice(0, 8).reduce((acc, row) => {
|
|
3745
|
+
const status = getFieldValue(row, fields.statusField, "Open");
|
|
3746
|
+
if (!acc[status]) acc[status] = [];
|
|
3747
|
+
acc[status].push(row);
|
|
3748
|
+
return acc;
|
|
3749
|
+
}, {});
|
|
3750
|
+
return <div className="workspace-table-transform-preview is-board">
|
|
3751
|
+
{Object.entries(groups).slice(0, 3).map(([status, groupRows]) => <section key={status}>
|
|
3752
|
+
<strong>{status}</strong>
|
|
3753
|
+
{groupRows.slice(0, 3).map((row, index) => <span key={index}>{getFieldValue(row, fields.titleField, `Row ${index + 1}`)}</span>)}
|
|
3754
|
+
</section>)}
|
|
3755
|
+
</div>;
|
|
3756
|
+
}
|
|
3757
|
+
if (type === "calendar") {
|
|
3758
|
+
return <div className="workspace-table-transform-preview is-calendar">
|
|
3759
|
+
{["Mon", "Tue", "Wed", "Thu", "Fri"].map((day, index) => <section key={day}>
|
|
3760
|
+
<strong>{day}</strong>
|
|
3761
|
+
{safeRows[index] ? <span>{getFieldValue(safeRows[index], fields.titleField, `Row ${index + 1}`)}</span> : null}
|
|
3762
|
+
</section>)}
|
|
3763
|
+
</div>;
|
|
3764
|
+
}
|
|
3765
|
+
return <div className={`workspace-table-transform-preview is-${type}`}>
|
|
3766
|
+
{safeRows.slice(0, 5).map((row, index) => <div key={index}>
|
|
3767
|
+
<span>{getFieldValue(row, fields.titleField, `Row ${index + 1}`)}</span>
|
|
3768
|
+
<i style={{ "--offset": `${(index % 4) * 14}%`, "--width": `${34 + (index % 3) * 12}%` }} />
|
|
3769
|
+
<em>{getFieldValue(row, fields.startDateField, getFieldValue(row, fields.endDateField, ""))}</em>
|
|
3770
|
+
</div>)}
|
|
3771
|
+
</div>;
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3377
3774
|
function WidgetPreview({ widget, branding, selected, onSelect, onMoveStart, onRemove, onResizeStart, onExpandIframe }) {
|
|
3378
3775
|
const fallbackColumns = widget.config?.columns?.length ? widget.config.columns : [];
|
|
3379
3776
|
const visibleColumns = widget.kind === "view" ? getVisibleColumns(widget) : fallbackColumns;
|
|
@@ -3416,18 +3813,7 @@ function WidgetPreview({ widget, branding, selected, onSelect, onMoveStart, onRe
|
|
|
3416
3813
|
type="button"
|
|
3417
3814
|
><X size={13} /></button>
|
|
3418
3815
|
</div>
|
|
3419
|
-
{widget.kind === "view" ? <
|
|
3420
|
-
className="workspace-view-table"
|
|
3421
|
-
aria-label={`${widget.title} preview`}
|
|
3422
|
-
style={{ "--workspace-view-columns": viewColumns.length }}
|
|
3423
|
-
>
|
|
3424
|
-
<div>{viewColumns.map((column) => <span key={column}>{column}</span>)}</div>
|
|
3425
|
-
{viewRows.slice(0, 6).map((row, rowIndex) => <div key={rowIndex}>
|
|
3426
|
-
{viewColumns.map((column) => <span key={column}>{row?.[column] || ""}</span>)}
|
|
3427
|
-
</div>)}
|
|
3428
|
-
{!viewColumns.length && !viewRows.length ? <div className="workspace-view-empty">Select a source</div> : null}
|
|
3429
|
-
<footer>Calculate</footer>
|
|
3430
|
-
</div> : null}
|
|
3816
|
+
{widget.kind === "view" ? <TableTransformPreview widget={widget} columns={viewColumns} rows={viewRows} /> : null}
|
|
3431
3817
|
{widget.kind === "iframe" ? <div className="workspace-iframe-preview">
|
|
3432
3818
|
{isLikelyHttpUrl(widget.config?.url) ? <iframe title={`${widget.title} preview`} src={widget.config.url} /> : <span>Enter a valid http(s) URL</span>}
|
|
3433
3819
|
<button type="button" onClick={(event) => {
|
|
@@ -3842,6 +4228,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
3842
4228
|
const [editingTabId, setEditingTabId] = useState(null);
|
|
3843
4229
|
const [editingTabDraft, setEditingTabDraft] = useState("");
|
|
3844
4230
|
const [workspaceView, setWorkspaceView] = useState("dashboards");
|
|
4231
|
+
const [activationPanelOpen, setActivationPanelOpen] = useState(false);
|
|
3845
4232
|
const [builderListFilter, setBuilderListFilter] = useState({ type: "all", query: "" });
|
|
3846
4233
|
const [builderActionMenuId, setBuilderActionMenuId] = useState(null);
|
|
3847
4234
|
const [builderActionMenuPlacement, setBuilderActionMenuPlacement] = useState(null);
|
|
@@ -3925,6 +4312,19 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
3925
4312
|
? initialSourceRecords
|
|
3926
4313
|
: {})
|
|
3927
4314
|
);
|
|
4315
|
+
const activationState = useMemo(() => deriveWorkspaceActivationState({
|
|
4316
|
+
workspaceConfig: config,
|
|
4317
|
+
workspaceSourceRecords,
|
|
4318
|
+
}), [config, workspaceSourceRecords]);
|
|
4319
|
+
const activationStarted = activationState.completedCount > 0;
|
|
4320
|
+
const activationComplete = Boolean(activationState.complete);
|
|
4321
|
+
const activationUiCache = useMemo(() => getWorkspaceUiCache(config), [config]);
|
|
4322
|
+
const activationButtonHidden = activationUiCache.finishSetupButtonHidden === true
|
|
4323
|
+
|| String(activationUiCache.finishSetupButtonHidden || "") === "true";
|
|
4324
|
+
const showFinishSetupButton = workspaceView === "dashboards" && activationStarted && !activationButtonHidden;
|
|
4325
|
+
const showActivationPanel = workspaceView === "dashboards" && (
|
|
4326
|
+
!activationStarted || activationPanelOpen
|
|
4327
|
+
);
|
|
3928
4328
|
const resizeDragRef = useRef(null);
|
|
3929
4329
|
const moveDragRef = useRef(null);
|
|
3930
4330
|
const importInputRef = useRef(null);
|
|
@@ -3937,6 +4337,24 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
3937
4337
|
() => listWorkspaceDataModelTables(config, { sourceRecords: workspaceSourceRecords }),
|
|
3938
4338
|
[config, workspaceSourceRecords]
|
|
3939
4339
|
);
|
|
4340
|
+
|
|
4341
|
+
const dismissFinishSetupButton = useCallback(async () => {
|
|
4342
|
+
const nextConfig = setWorkspaceUiCacheFlag(config, "finishSetupButtonHidden", true);
|
|
4343
|
+
setActivationPanelOpen(false);
|
|
4344
|
+
setConfig(nextConfig);
|
|
4345
|
+
try {
|
|
4346
|
+
const response = await fetch("/api/workspace", {
|
|
4347
|
+
method: "PATCH",
|
|
4348
|
+
headers: { "content-type": "application/json" },
|
|
4349
|
+
body: JSON.stringify({ dataModel: nextConfig.dataModel })
|
|
4350
|
+
});
|
|
4351
|
+
const payload = await response.json();
|
|
4352
|
+
if (!response.ok || !payload.workspaceConfig) throw new Error(payload.error || "Failed to save workspace preference");
|
|
4353
|
+
setConfig((prev) => ({ ...prev, dataModel: payload.workspaceConfig.dataModel }));
|
|
4354
|
+
} catch (error) {
|
|
4355
|
+
setConfigMessage(error.message || "Failed to save workspace preference");
|
|
4356
|
+
}
|
|
4357
|
+
}, [config]);
|
|
3940
4358
|
const selectedResolvedWidget = selectedWidget ? resolveViewWidget(selectedWidget, dataModelTables) : null;
|
|
3941
4359
|
const branding = config.branding || {};
|
|
3942
4360
|
const occupiedCells = useMemo(() => {
|
|
@@ -4222,11 +4640,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
4222
4640
|
const createWorkflow = useCallback(async () => {
|
|
4223
4641
|
if (saving) return;
|
|
4224
4642
|
const nowIso = new Date().toISOString();
|
|
4225
|
-
const existing = getWorkflowSandboxObject(config);
|
|
4226
|
-
if (!existing) {
|
|
4227
|
-
setConfigMessage("Workflow sandbox object is missing.");
|
|
4228
|
-
return;
|
|
4229
|
-
}
|
|
4643
|
+
const existing = getWorkflowSandboxObject(config) || createWorkflowSandboxObject();
|
|
4230
4644
|
const sandboxObjectId = String(existing.id || "").trim();
|
|
4231
4645
|
const rows = Array.isArray(existing.rows) ? existing.rows : [];
|
|
4232
4646
|
const base = slugifyWorkflowName(`workflow-${rows.length + 1}`);
|
|
@@ -4240,11 +4654,20 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
4240
4654
|
const sandboxRow = createBlankWorkflowSandboxRow(rowId, nowIso);
|
|
4241
4655
|
const nextDataModel = {
|
|
4242
4656
|
...(config.dataModel || {}),
|
|
4243
|
-
objects: (
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4657
|
+
objects: (() => {
|
|
4658
|
+
const objects = Array.isArray(config.dataModel?.objects) ? config.dataModel.objects : [];
|
|
4659
|
+
const hasSandboxObject = objects.some((object) => object?.id === sandboxObjectId);
|
|
4660
|
+
const hasSmokeRegistry = objects.some((object) => object?.id === "workflow-api-registry")
|
|
4661
|
+
|| objects.some((object) =>
|
|
4662
|
+
object?.objectType === "api-registry"
|
|
4663
|
+
&& (Array.isArray(object.rows) ? object.rows : []).some((row) => row?.integrationId === "growthub-workspace-smoke-api")
|
|
4664
|
+
);
|
|
4665
|
+
const nextSandboxObject = { ...existing, rows: [...rows, sandboxRow] };
|
|
4666
|
+
const nextObjects = hasSandboxObject
|
|
4667
|
+
? objects.map((object) => object?.id === sandboxObjectId ? nextSandboxObject : object)
|
|
4668
|
+
: [...objects, nextSandboxObject];
|
|
4669
|
+
return hasSmokeRegistry ? nextObjects : [...nextObjects, createWorkflowApiRegistryObject()];
|
|
4670
|
+
})()
|
|
4248
4671
|
};
|
|
4249
4672
|
const finalDataModel = addWorkflowFolderShortcut(nextDataModel, {
|
|
4250
4673
|
objectId: sandboxObjectId,
|
|
@@ -4307,6 +4730,36 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
4307
4730
|
selectDashboard(targetIndex);
|
|
4308
4731
|
}, [dashboards, resolvedActiveDashboardId, searchParams, selectDashboard, workspaceView]);
|
|
4309
4732
|
|
|
4733
|
+
const openAddWidgetBuilder = useCallback(() => {
|
|
4734
|
+
const targetDashboard = activeDashboard || dashboards[0];
|
|
4735
|
+
if (!targetDashboard) return false;
|
|
4736
|
+
setConfig((prev) => {
|
|
4737
|
+
const synced = syncActiveDashboard(prev, activeDashboardId);
|
|
4738
|
+
const prevDashboards = synced.dashboards || [];
|
|
4739
|
+
const dashboard = prevDashboards.find((item) => item.id === targetDashboard.id) || prevDashboards[0];
|
|
4740
|
+
if (!dashboard) return prev;
|
|
4741
|
+
const normalized = normalizeDashboard(dashboard, dashboard.id === prevDashboards[0]?.id ? synced.canvas : undefined);
|
|
4742
|
+
const nextCanvas = dashboardCanvasFrom(normalized, synced.canvas);
|
|
4743
|
+
const nextActiveWidgets = getTabs(nextCanvas)[0]?.widgets || [];
|
|
4744
|
+
setSelectedWidgetId(null);
|
|
4745
|
+
setPendingSelectedWidgetId(null);
|
|
4746
|
+
setSelectedPosition(findFreePosition(nextActiveWidgets));
|
|
4747
|
+
setDragPreview(null);
|
|
4748
|
+
setActiveDashboardId(normalized.id);
|
|
4749
|
+
setWorkspaceView("builder");
|
|
4750
|
+
setDashboardLiveSnapshot(cloneConfig(normalized));
|
|
4751
|
+
setDashboardDraftMode(true);
|
|
4752
|
+
setPanelOpen(true);
|
|
4753
|
+
setConfigMessage(`Editing draft for ${normalized.name}`);
|
|
4754
|
+
return {
|
|
4755
|
+
...synced,
|
|
4756
|
+
dashboards: prevDashboards.map((item) => item.id === normalized.id ? normalized : item),
|
|
4757
|
+
canvas: nextCanvas
|
|
4758
|
+
};
|
|
4759
|
+
});
|
|
4760
|
+
return true;
|
|
4761
|
+
}, [activeDashboard, activeDashboardId, dashboards]);
|
|
4762
|
+
|
|
4310
4763
|
const enterDashboardTitleEdit = useCallback((dashboard) => {
|
|
4311
4764
|
if (!dashboard) return;
|
|
4312
4765
|
setEditingDashboardId(dashboard.id);
|
|
@@ -5384,6 +5837,33 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5384
5837
|
<button type="button" className="dm-workflow-chip-btn" onClick={exportConfig}><Download size={13} />Export</button>
|
|
5385
5838
|
<button type="button" className="dm-workflow-icon-btn" onClick={() => importInputRef.current?.click()} aria-label="Import"><Import size={14} /></button>
|
|
5386
5839
|
</div> : <div className="workspace-toolbar-actions">
|
|
5840
|
+
{showFinishSetupButton ? (
|
|
5841
|
+
<span className={`workspace-finish-setup-control${activationComplete ? " is-complete" : ""}`}>
|
|
5842
|
+
<button
|
|
5843
|
+
type="button"
|
|
5844
|
+
className="workspace-finish-setup-trigger"
|
|
5845
|
+
onClick={() => {
|
|
5846
|
+
setWorkspaceView("dashboards");
|
|
5847
|
+
setActivationPanelOpen((open) => !open);
|
|
5848
|
+
}}
|
|
5849
|
+
>
|
|
5850
|
+
{activationComplete ? null : <Hourglass size={15} />}
|
|
5851
|
+
<span>{activationComplete ? "Setup Complete" : "Finish Workspace Setup"}</span>
|
|
5852
|
+
</button>
|
|
5853
|
+
{activationComplete ? (
|
|
5854
|
+
<button
|
|
5855
|
+
type="button"
|
|
5856
|
+
className="workspace-finish-setup-dismiss"
|
|
5857
|
+
aria-label="Hide completed workspace setup"
|
|
5858
|
+
title="Hide completed workspace setup"
|
|
5859
|
+
onClick={dismissFinishSetupButton}
|
|
5860
|
+
>
|
|
5861
|
+
<Check className="workspace-finish-setup-dismiss-check" size={15} aria-hidden="true" />
|
|
5862
|
+
<X className="workspace-finish-setup-dismiss-x" size={15} aria-hidden="true" />
|
|
5863
|
+
</button>
|
|
5864
|
+
) : null}
|
|
5865
|
+
</span>
|
|
5866
|
+
) : null}
|
|
5387
5867
|
<button type="button" onClick={addDashboard}><Plus size={15} />New Dashboard</button>
|
|
5388
5868
|
<button type="button" onClick={createWorkflow} disabled={saving}><GitBranch size={15} />New Workflow</button>
|
|
5389
5869
|
<button type="button" onClick={() => importInputRef.current?.click()}><Import size={15} />Import</button>
|
|
@@ -5397,7 +5877,26 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5397
5877
|
/>
|
|
5398
5878
|
</header>
|
|
5399
5879
|
|
|
5400
|
-
{workspaceView === "dashboards" ?
|
|
5880
|
+
{workspaceView === "dashboards" ? <>
|
|
5881
|
+
{showActivationPanel ? <WorkspaceActivationPanel
|
|
5882
|
+
workspaceConfig={config}
|
|
5883
|
+
workspaceSourceRecords={workspaceSourceRecords}
|
|
5884
|
+
onStepAction={(step) => {
|
|
5885
|
+
if (step?.id === "add-widget") return openAddWidgetBuilder();
|
|
5886
|
+
if (step?.id === "create-workflow") {
|
|
5887
|
+
createWorkflow();
|
|
5888
|
+
return true;
|
|
5889
|
+
}
|
|
5890
|
+
return false;
|
|
5891
|
+
}}
|
|
5892
|
+
onOpenHelper={() => {
|
|
5893
|
+
setHelperIntent("explain");
|
|
5894
|
+
setHelperInitialPrompt("Help me finish setting up this workspace.");
|
|
5895
|
+
setHelperInitialThread(null);
|
|
5896
|
+
setHelperOpen(true);
|
|
5897
|
+
}}
|
|
5898
|
+
/> : null}
|
|
5899
|
+
<section className="workspace-table" id="dashboards" aria-label="Builder">
|
|
5401
5900
|
<div className="workspace-table-heading">
|
|
5402
5901
|
<strong>Builder</strong>
|
|
5403
5902
|
<span>{dashboards.length} dashboard{dashboards.length === 1 ? "" : "s"} · {workflows.length} workflow{workflows.length === 1 ? "" : "s"}</span>
|
|
@@ -5564,7 +6063,8 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5564
6063
|
)}
|
|
5565
6064
|
</span>
|
|
5566
6065
|
</div>)}
|
|
5567
|
-
</section>
|
|
6066
|
+
</section>
|
|
6067
|
+
</> : null}
|
|
5568
6068
|
|
|
5569
6069
|
{workspaceView === "builder" ? <section className="workspace-canvas" id="canvas" aria-label="Composable dashboard canvas">
|
|
5570
6070
|
<div className="workspace-tabs">
|
|
@@ -5836,14 +6336,12 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5836
6336
|
</small>
|
|
5837
6337
|
</label> : null}
|
|
5838
6338
|
{selectedWidget.kind === "view" ? <section className="workspace-field-stack">
|
|
5839
|
-
<
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
<WidgetSettingsRow icon={SlidersHorizontal} label="Sort" value={summarizeSort(selectedResolvedWidget || selectedWidget)} onClick={() => setInspectorPath("sort")} />
|
|
5846
|
-
</div>
|
|
6339
|
+
<TableViewConfig
|
|
6340
|
+
widget={selectedWidget}
|
|
6341
|
+
dataModelTable={resolveDataModelTable(dataModelTables, selectedWidget.config?.binding)}
|
|
6342
|
+
onChange={replaceSelectedWidgetConfig}
|
|
6343
|
+
onSubPage={(name) => setInspectorPath(name)}
|
|
6344
|
+
/>
|
|
5847
6345
|
</section> : null}
|
|
5848
6346
|
</section> : null}
|
|
5849
6347
|
{!selectedWidget ? <section>
|