@nextsparkjs/core 0.1.0-beta.54 → 0.1.0-beta.56
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/components/patterns/pages/PatternEditPage.d.ts +8 -0
- package/dist/components/patterns/pages/PatternEditPage.d.ts.map +1 -0
- package/dist/components/patterns/pages/PatternEditPage.js +80 -0
- package/dist/components/patterns/pages/PatternReportsPage.d.ts +9 -0
- package/dist/components/patterns/pages/PatternReportsPage.d.ts.map +1 -0
- package/dist/components/patterns/pages/PatternReportsPage.js +133 -0
- package/dist/components/patterns/pages/PatternsCreatePage.d.ts +8 -0
- package/dist/components/patterns/pages/PatternsCreatePage.d.ts.map +1 -0
- package/dist/components/patterns/pages/PatternsCreatePage.js +49 -0
- package/dist/components/patterns/pages/PatternsListPage.d.ts +8 -0
- package/dist/components/patterns/pages/PatternsListPage.d.ts.map +1 -0
- package/dist/components/patterns/pages/PatternsListPage.js +336 -0
- package/dist/components/patterns/pages/index.d.ts +11 -0
- package/dist/components/patterns/pages/index.d.ts.map +1 -0
- package/dist/components/patterns/pages/index.js +10 -0
- package/dist/lib/api/routes/index.d.ts +8 -0
- package/dist/lib/api/routes/index.d.ts.map +1 -0
- package/dist/lib/api/routes/index.js +4 -0
- package/dist/lib/api/routes/patterns-usages.d.ts +36 -0
- package/dist/lib/api/routes/patterns-usages.d.ts.map +1 -0
- package/dist/lib/api/routes/patterns-usages.js +83 -0
- package/dist/styles/classes.json +5 -2
- package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/patterns.bdd.md +1 -1
- package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/patterns.cy.ts +280 -113
- package/dist/templates/next.config.mjs +7 -0
- package/package.json +9 -1
- package/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/patterns.bdd.md +1 -1
- package/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/patterns.cy.ts +279 -112
- package/templates/next.config.mjs +7 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Patterns Edit Page
|
|
3
|
+
*
|
|
4
|
+
* Wrapper that delegates to the BuilderEditorView for pattern editing.
|
|
5
|
+
* Uses useEntityConfig hook for entity configuration.
|
|
6
|
+
*/
|
|
7
|
+
export default function PatternEditPage(): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
//# sourceMappingURL=PatternEditPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PatternEditPage.d.ts","sourceRoot":"","sources":["../../../../src/components/patterns/pages/PatternEditPage.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH,MAAM,CAAC,OAAO,UAAU,eAAe,4CAwFtC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { notFound, useRouter } from "next/navigation";
|
|
4
|
+
import { useParams } from "next/navigation";
|
|
5
|
+
import { useEffect, useState } from "react";
|
|
6
|
+
import { EntityFormWrapper } from "../../entities/wrappers/EntityFormWrapper.js";
|
|
7
|
+
import { BuilderEditorView } from "../../dashboard/block-editor/builder-editor-view.js";
|
|
8
|
+
import { Alert, AlertDescription } from "../../ui/alert.js";
|
|
9
|
+
import { useEntityConfig } from "../../../hooks/useEntityConfig.js";
|
|
10
|
+
import { getEntityData } from "../../../lib/api/entities.js";
|
|
11
|
+
function PatternEditPage() {
|
|
12
|
+
var _a;
|
|
13
|
+
const params = useParams();
|
|
14
|
+
const router = useRouter();
|
|
15
|
+
const [initialData, setInitialData] = useState(null);
|
|
16
|
+
const [dataLoading, setDataLoading] = useState(true);
|
|
17
|
+
const entitySlug = "patterns";
|
|
18
|
+
const entityId = params.id;
|
|
19
|
+
const { config: entityConfig, isLoading: configLoading } = useEntityConfig(entitySlug);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
async function loadEntityData() {
|
|
22
|
+
var _a2;
|
|
23
|
+
if (!entityId || !entityConfig) {
|
|
24
|
+
setDataLoading(false);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
if (!((_a2 = entityConfig.builder) == null ? void 0 : _a2.enabled)) {
|
|
29
|
+
const data = await getEntityData(entitySlug, entityId, true);
|
|
30
|
+
setInitialData(data);
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error("Error loading pattern:", error);
|
|
34
|
+
setInitialData(null);
|
|
35
|
+
} finally {
|
|
36
|
+
setDataLoading(false);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (entityConfig) {
|
|
40
|
+
loadEntityData();
|
|
41
|
+
}
|
|
42
|
+
}, [entityId, entityConfig]);
|
|
43
|
+
if (configLoading || dataLoading) {
|
|
44
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-h-[400px]", children: /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-primary" }) });
|
|
45
|
+
}
|
|
46
|
+
if (!entityConfig || !entityConfig.enabled) {
|
|
47
|
+
return /* @__PURE__ */ jsx(Alert, { children: /* @__PURE__ */ jsx(AlertDescription, { children: "Patterns entity is not configured or not enabled." }) });
|
|
48
|
+
}
|
|
49
|
+
if ((_a = entityConfig.builder) == null ? void 0 : _a.enabled) {
|
|
50
|
+
return /* @__PURE__ */ jsx(
|
|
51
|
+
BuilderEditorView,
|
|
52
|
+
{
|
|
53
|
+
entitySlug,
|
|
54
|
+
entityConfig,
|
|
55
|
+
id: entityId,
|
|
56
|
+
mode: "edit"
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (!initialData) {
|
|
61
|
+
notFound();
|
|
62
|
+
}
|
|
63
|
+
return /* @__PURE__ */ jsx(
|
|
64
|
+
EntityFormWrapper,
|
|
65
|
+
{
|
|
66
|
+
entityType: entitySlug,
|
|
67
|
+
id: entityId,
|
|
68
|
+
mode: "edit",
|
|
69
|
+
onSuccess: () => {
|
|
70
|
+
router.push(`/dashboard/${entitySlug}/${entityId}`);
|
|
71
|
+
},
|
|
72
|
+
onError: (error) => {
|
|
73
|
+
console.error("Error updating pattern:", error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
PatternEditPage as default
|
|
80
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern Usage Reports Page
|
|
3
|
+
*
|
|
4
|
+
* Shows detailed usage reports for a specific pattern.
|
|
5
|
+
* Displays stats cards and a table of entities using the pattern.
|
|
6
|
+
* Uses useEntityConfig hook for entity configuration.
|
|
7
|
+
*/
|
|
8
|
+
export default function PatternReportsPage(): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=PatternReportsPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PatternReportsPage.d.ts","sourceRoot":"","sources":["../../../../src/components/patterns/pages/PatternReportsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAqBH,MAAM,CAAC,OAAO,UAAU,kBAAkB,4CAqJzC"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useParams, useRouter } from "next/navigation";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import Link from "next/link";
|
|
6
|
+
import { ArrowLeft, Edit, LayoutGrid } from "lucide-react";
|
|
7
|
+
import { PatternUsageReport } from "../PatternUsageReport.js";
|
|
8
|
+
import { Button } from "../../ui/button.js";
|
|
9
|
+
import { Alert, AlertDescription, AlertTitle } from "../../ui/alert.js";
|
|
10
|
+
import { Skeleton } from "../../ui/skeleton.js";
|
|
11
|
+
import { useEntityConfig } from "../../../hooks/useEntityConfig.js";
|
|
12
|
+
import { getEntityData } from "../../../lib/api/entities.js";
|
|
13
|
+
function PatternReportsPage() {
|
|
14
|
+
const params = useParams();
|
|
15
|
+
const router = useRouter();
|
|
16
|
+
const patternId = params.id;
|
|
17
|
+
const [pattern, setPattern] = useState(null);
|
|
18
|
+
const [dataLoading, setDataLoading] = useState(true);
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
const { config: entityConfig, isLoading: configLoading } = useEntityConfig("patterns");
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
async function loadPattern() {
|
|
23
|
+
if (!patternId) {
|
|
24
|
+
setError("Pattern ID is required");
|
|
25
|
+
setDataLoading(false);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!entityConfig) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (!entityConfig.enabled) {
|
|
32
|
+
setError("Patterns entity is not configured or not enabled");
|
|
33
|
+
setDataLoading(false);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const data = await getEntityData("patterns", patternId, false);
|
|
38
|
+
setPattern(data);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error("Error loading pattern:", err);
|
|
41
|
+
setError(err instanceof Error ? err.message : "Failed to load pattern");
|
|
42
|
+
} finally {
|
|
43
|
+
setDataLoading(false);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (entityConfig) {
|
|
47
|
+
loadPattern();
|
|
48
|
+
}
|
|
49
|
+
}, [patternId, entityConfig]);
|
|
50
|
+
if (configLoading || dataLoading) {
|
|
51
|
+
return /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-6", "data-cy": "pattern-reports-loading", children: [
|
|
52
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
|
|
53
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-8" }),
|
|
54
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-64" })
|
|
55
|
+
] }),
|
|
56
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-96" }),
|
|
57
|
+
/* @__PURE__ */ jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-4", children: [...Array(4)].map((_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-24" }, i)) }),
|
|
58
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-64" })
|
|
59
|
+
] });
|
|
60
|
+
}
|
|
61
|
+
if (error) {
|
|
62
|
+
return /* @__PURE__ */ jsxs("div", { className: "p-6", "data-cy": "pattern-reports-error", children: [
|
|
63
|
+
/* @__PURE__ */ jsxs(Alert, { variant: "destructive", children: [
|
|
64
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: "Error" }),
|
|
65
|
+
/* @__PURE__ */ jsx(AlertDescription, { children: error })
|
|
66
|
+
] }),
|
|
67
|
+
/* @__PURE__ */ jsxs(
|
|
68
|
+
Button,
|
|
69
|
+
{
|
|
70
|
+
variant: "outline",
|
|
71
|
+
className: "mt-4",
|
|
72
|
+
onClick: () => router.push("/dashboard/patterns"),
|
|
73
|
+
children: [
|
|
74
|
+
/* @__PURE__ */ jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }),
|
|
75
|
+
"Back to Patterns"
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
] });
|
|
80
|
+
}
|
|
81
|
+
if (!pattern) {
|
|
82
|
+
return /* @__PURE__ */ jsxs("div", { className: "p-6", "data-cy": "pattern-reports-not-found", children: [
|
|
83
|
+
/* @__PURE__ */ jsxs(Alert, { children: [
|
|
84
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: "Pattern Not Found" }),
|
|
85
|
+
/* @__PURE__ */ jsx(AlertDescription, { children: "The pattern you are looking for does not exist or has been deleted." })
|
|
86
|
+
] }),
|
|
87
|
+
/* @__PURE__ */ jsxs(
|
|
88
|
+
Button,
|
|
89
|
+
{
|
|
90
|
+
variant: "outline",
|
|
91
|
+
className: "mt-4",
|
|
92
|
+
onClick: () => router.push("/dashboard/patterns"),
|
|
93
|
+
children: [
|
|
94
|
+
/* @__PURE__ */ jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }),
|
|
95
|
+
"Back to Patterns"
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
] });
|
|
100
|
+
}
|
|
101
|
+
const patternName = pattern.name || pattern.title || pattern.id;
|
|
102
|
+
return /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-6", "data-cy": "pattern-reports-page", children: [
|
|
103
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 md:flex-row md:items-center md:justify-between", children: [
|
|
104
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
|
|
105
|
+
/* @__PURE__ */ jsx(
|
|
106
|
+
Button,
|
|
107
|
+
{
|
|
108
|
+
variant: "ghost",
|
|
109
|
+
size: "icon",
|
|
110
|
+
asChild: true,
|
|
111
|
+
"data-cy": "pattern-reports-back",
|
|
112
|
+
children: /* @__PURE__ */ jsx(Link, { href: "/dashboard/patterns", children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" }) })
|
|
113
|
+
}
|
|
114
|
+
),
|
|
115
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
116
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
117
|
+
/* @__PURE__ */ jsx(LayoutGrid, { className: "h-5 w-5 text-muted-foreground" }),
|
|
118
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold tracking-tight", "data-cy": "pattern-reports-title", children: patternName })
|
|
119
|
+
] }),
|
|
120
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Usage reports and analytics" })
|
|
121
|
+
] })
|
|
122
|
+
] }),
|
|
123
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx(Button, { variant: "outline", asChild: true, "data-cy": "pattern-reports-edit", children: /* @__PURE__ */ jsxs(Link, { href: `/dashboard/patterns/${patternId}/edit`, children: [
|
|
124
|
+
/* @__PURE__ */ jsx(Edit, { className: "mr-2 h-4 w-4" }),
|
|
125
|
+
"Edit Pattern"
|
|
126
|
+
] }) }) })
|
|
127
|
+
] }),
|
|
128
|
+
/* @__PURE__ */ jsx(PatternUsageReport, { patternId, pageSize: 20 })
|
|
129
|
+
] });
|
|
130
|
+
}
|
|
131
|
+
export {
|
|
132
|
+
PatternReportsPage as default
|
|
133
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Patterns Create Page
|
|
3
|
+
*
|
|
4
|
+
* Wrapper that delegates to the generic entity create page.
|
|
5
|
+
* Uses useEntityConfig hook for entity configuration.
|
|
6
|
+
*/
|
|
7
|
+
export default function PatternsCreatePage(): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
//# sourceMappingURL=PatternsCreatePage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PatternsCreatePage.d.ts","sourceRoot":"","sources":["../../../../src/components/patterns/pages/PatternsCreatePage.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,MAAM,CAAC,OAAO,UAAU,kBAAkB,4CAqDzC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useRouter } from "next/navigation";
|
|
4
|
+
import { EntityFormWrapper } from "../../entities/wrappers/EntityFormWrapper.js";
|
|
5
|
+
import { BuilderEditorView } from "../../dashboard/block-editor/builder-editor-view.js";
|
|
6
|
+
import { Alert, AlertDescription } from "../../ui/alert.js";
|
|
7
|
+
import { useEntityConfig } from "../../../hooks/useEntityConfig.js";
|
|
8
|
+
function PatternsCreatePage() {
|
|
9
|
+
var _a;
|
|
10
|
+
const router = useRouter();
|
|
11
|
+
const entitySlug = "patterns";
|
|
12
|
+
const { config: entityConfig, isLoading } = useEntityConfig(entitySlug);
|
|
13
|
+
if (isLoading) {
|
|
14
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-h-[400px]", children: /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-primary" }) });
|
|
15
|
+
}
|
|
16
|
+
if (!entityConfig || !entityConfig.enabled) {
|
|
17
|
+
return /* @__PURE__ */ jsx(Alert, { children: /* @__PURE__ */ jsx(AlertDescription, { children: "Patterns entity is not configured or not enabled." }) });
|
|
18
|
+
}
|
|
19
|
+
if ((_a = entityConfig.builder) == null ? void 0 : _a.enabled) {
|
|
20
|
+
return /* @__PURE__ */ jsx(
|
|
21
|
+
BuilderEditorView,
|
|
22
|
+
{
|
|
23
|
+
entitySlug,
|
|
24
|
+
entityConfig,
|
|
25
|
+
mode: "create"
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return /* @__PURE__ */ jsx(
|
|
30
|
+
EntityFormWrapper,
|
|
31
|
+
{
|
|
32
|
+
entityType: entitySlug,
|
|
33
|
+
mode: "create",
|
|
34
|
+
onSuccess: (createdId) => {
|
|
35
|
+
if (createdId) {
|
|
36
|
+
router.push(`/dashboard/${entitySlug}/${createdId}`);
|
|
37
|
+
} else {
|
|
38
|
+
router.push(`/dashboard/${entitySlug}`);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
onError: (error) => {
|
|
42
|
+
console.error(`Error creating pattern:`, error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
export {
|
|
48
|
+
PatternsCreatePage as default
|
|
49
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Patterns List Page
|
|
3
|
+
*
|
|
4
|
+
* Custom patterns list page with quickAction to view usage reports.
|
|
5
|
+
* Uses EntityTable directly to enable custom quickActions.
|
|
6
|
+
*/
|
|
7
|
+
export default function PatternsListPage(): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
//# sourceMappingURL=PatternsListPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PatternsListPage.d.ts","sourceRoot":"","sources":["../../../../src/components/patterns/pages/PatternsListPage.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkCH,MAAM,CAAC,OAAO,UAAU,gBAAgB,4CAoZvC"}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { useRouter } from "next/navigation";
|
|
6
|
+
import { Plus, Loader2, BarChart3, Pencil, Trash2 } from "lucide-react";
|
|
7
|
+
import { EntityTable } from "../../entities/EntityTable.js";
|
|
8
|
+
import { EntityBulkActions } from "../../entities/EntityBulkActions.js";
|
|
9
|
+
import { Alert, AlertDescription } from "../../ui/alert.js";
|
|
10
|
+
import { Button } from "../../ui/button.js";
|
|
11
|
+
import { SkeletonEntityList } from "../../ui/skeleton-list.js";
|
|
12
|
+
import { SearchInput } from "../../shared/SearchInput.js";
|
|
13
|
+
import { MultiSelectFilter } from "../../shared/MultiSelectFilter.js";
|
|
14
|
+
import { PatternDeleteDialog } from "../PatternDeleteDialog.js";
|
|
15
|
+
import { useEntityConfig } from "../../../hooks/useEntityConfig.js";
|
|
16
|
+
import { useUrlFilters } from "../../../hooks/useUrlFilters.js";
|
|
17
|
+
import { listEntityData, deleteEntityData } from "../../../lib/api/entities.js";
|
|
18
|
+
import { useTeam } from "../../../hooks/useTeam.js";
|
|
19
|
+
import { usePermission } from "../../../lib/permissions/hooks.js";
|
|
20
|
+
import { sel } from "../../../lib/test/index.js";
|
|
21
|
+
import { toast } from "sonner";
|
|
22
|
+
function PatternsListPage() {
|
|
23
|
+
var _a, _b;
|
|
24
|
+
const entityType = "patterns";
|
|
25
|
+
const router = useRouter();
|
|
26
|
+
const { config: entityConfig, isLoading, error: configError, isOverride } = useEntityConfig(entityType);
|
|
27
|
+
const { teamId } = useTeam();
|
|
28
|
+
const canCreate = usePermission(`${entityType}.create`);
|
|
29
|
+
const filterSchema = useMemo(() => {
|
|
30
|
+
var _a2;
|
|
31
|
+
const schema = {
|
|
32
|
+
search: { type: "search", urlParam: "search" }
|
|
33
|
+
};
|
|
34
|
+
(_a2 = entityConfig == null ? void 0 : entityConfig.ui.dashboard.filters) == null ? void 0 : _a2.forEach((filterConfig) => {
|
|
35
|
+
if (filterConfig.type === "multiSelect" || filterConfig.type === "singleSelect") {
|
|
36
|
+
schema[filterConfig.field] = {
|
|
37
|
+
type: filterConfig.type,
|
|
38
|
+
urlParam: filterConfig.urlParam || filterConfig.field
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
return schema;
|
|
43
|
+
}, [entityConfig == null ? void 0 : entityConfig.ui.dashboard.filters]);
|
|
44
|
+
const { filters, setFilter } = useUrlFilters(filterSchema);
|
|
45
|
+
const [data, setData] = useState([]);
|
|
46
|
+
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
|
47
|
+
const [isSearching, setIsSearching] = useState(false);
|
|
48
|
+
const [dataError, setDataError] = useState(null);
|
|
49
|
+
const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
|
|
50
|
+
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
51
|
+
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
|
52
|
+
const [isDeleting, setIsDeleting] = useState(false);
|
|
53
|
+
const buildApiFilters = useCallback(() => {
|
|
54
|
+
var _a2;
|
|
55
|
+
const apiFilters = {};
|
|
56
|
+
if (filters.search && typeof filters.search === "string" && filters.search.trim()) {
|
|
57
|
+
apiFilters.search = filters.search;
|
|
58
|
+
}
|
|
59
|
+
(_a2 = entityConfig == null ? void 0 : entityConfig.ui.dashboard.filters) == null ? void 0 : _a2.forEach((filterConfig) => {
|
|
60
|
+
const value = filters[filterConfig.field];
|
|
61
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
62
|
+
apiFilters[filterConfig.field] = value.join(",");
|
|
63
|
+
} else if (typeof value === "string" && value) {
|
|
64
|
+
apiFilters[filterConfig.field] = value;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return apiFilters;
|
|
68
|
+
}, [filters, entityConfig == null ? void 0 : entityConfig.ui.dashboard.filters]);
|
|
69
|
+
const loadData = useCallback(async (isInitial = false) => {
|
|
70
|
+
if (!entityConfig) return;
|
|
71
|
+
if (!entityConfig.enabled) {
|
|
72
|
+
setDataError(`Entity "${entityType}" is disabled`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
if (isInitial) {
|
|
77
|
+
setIsInitialLoad(true);
|
|
78
|
+
} else {
|
|
79
|
+
setIsSearching(true);
|
|
80
|
+
}
|
|
81
|
+
setDataError(null);
|
|
82
|
+
const apiFilters = buildApiFilters();
|
|
83
|
+
const result = await listEntityData(entityType, {
|
|
84
|
+
limit: 50,
|
|
85
|
+
includeMeta: true,
|
|
86
|
+
...Object.keys(apiFilters).length > 0 && { filters: apiFilters }
|
|
87
|
+
});
|
|
88
|
+
setData(result.data);
|
|
89
|
+
setDataError(null);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error(`[PatternsListPage] Error loading data:`, err);
|
|
92
|
+
setDataError(`Error loading data: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
93
|
+
setData([]);
|
|
94
|
+
} finally {
|
|
95
|
+
setIsInitialLoad(false);
|
|
96
|
+
setIsSearching(false);
|
|
97
|
+
}
|
|
98
|
+
}, [entityConfig, entityType, buildApiFilters]);
|
|
99
|
+
const filtersKey = JSON.stringify(filters);
|
|
100
|
+
const prevFiltersKeyRef = useRef("");
|
|
101
|
+
const hasLoadedRef = useRef(false);
|
|
102
|
+
const schemaKeysForSync = useMemo(
|
|
103
|
+
() => Object.keys(filterSchema).sort().join(","),
|
|
104
|
+
[filterSchema]
|
|
105
|
+
);
|
|
106
|
+
const prevSchemaKeysRef = useRef(schemaKeysForSync);
|
|
107
|
+
const filtersMatchUrl = useMemo(() => {
|
|
108
|
+
if (typeof window === "undefined") return true;
|
|
109
|
+
if (schemaKeysForSync !== prevSchemaKeysRef.current) {
|
|
110
|
+
prevSchemaKeysRef.current = schemaKeysForSync;
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
114
|
+
for (const filterConfig of (entityConfig == null ? void 0 : entityConfig.ui.dashboard.filters) || []) {
|
|
115
|
+
const urlValue = urlParams.get(filterConfig.field);
|
|
116
|
+
const filterValue = filters[filterConfig.field];
|
|
117
|
+
if (urlValue && (!filterValue || Array.isArray(filterValue) && filterValue.length === 0)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}, [filters, entityConfig == null ? void 0 : entityConfig.ui.dashboard.filters, schemaKeysForSync]);
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (!(entityConfig == null ? void 0 : entityConfig.slug)) return;
|
|
125
|
+
if (!filtersMatchUrl) return;
|
|
126
|
+
const isInitial = !hasLoadedRef.current;
|
|
127
|
+
const filtersChanged = prevFiltersKeyRef.current !== filtersKey;
|
|
128
|
+
if (isInitial || filtersChanged) {
|
|
129
|
+
prevFiltersKeyRef.current = filtersKey;
|
|
130
|
+
hasLoadedRef.current = true;
|
|
131
|
+
loadData(isInitial);
|
|
132
|
+
}
|
|
133
|
+
}, [entityConfig == null ? void 0 : entityConfig.slug, filtersKey, filtersMatchUrl, loadData]);
|
|
134
|
+
const handleSearch = useCallback((query) => {
|
|
135
|
+
setFilter("search", query);
|
|
136
|
+
}, [setFilter]);
|
|
137
|
+
const handleDeleteClick = useCallback((item) => {
|
|
138
|
+
setDeleteTarget(item);
|
|
139
|
+
setIsDeleteDialogOpen(true);
|
|
140
|
+
}, []);
|
|
141
|
+
const handleConfirmDelete = useCallback(async () => {
|
|
142
|
+
if (!deleteTarget) return;
|
|
143
|
+
setIsDeleting(true);
|
|
144
|
+
try {
|
|
145
|
+
await deleteEntityData(entityType, deleteTarget.id);
|
|
146
|
+
toast.success(`Pattern deleted successfully`);
|
|
147
|
+
setSelectedIds((prev) => {
|
|
148
|
+
const newSet = new Set(prev);
|
|
149
|
+
newSet.delete(deleteTarget.id);
|
|
150
|
+
return newSet;
|
|
151
|
+
});
|
|
152
|
+
await loadData(false);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
console.error(`[PatternsListPage] Error deleting pattern:`, err);
|
|
155
|
+
toast.error(`Error deleting: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
156
|
+
} finally {
|
|
157
|
+
setIsDeleting(false);
|
|
158
|
+
setIsDeleteDialogOpen(false);
|
|
159
|
+
setDeleteTarget(null);
|
|
160
|
+
}
|
|
161
|
+
}, [deleteTarget, entityType, loadData]);
|
|
162
|
+
const handleBulkDelete = useCallback(async (ids) => {
|
|
163
|
+
try {
|
|
164
|
+
await Promise.all(ids.map((id) => deleteEntityData(entityType, id)));
|
|
165
|
+
toast.success(`${ids.length} pattern${ids.length === 1 ? "" : "s"} deleted successfully`);
|
|
166
|
+
await loadData(false);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
console.error(`[PatternsListPage] Error bulk deleting patterns:`, err);
|
|
169
|
+
toast.error(`Error deleting: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
}, [entityType, loadData]);
|
|
173
|
+
const handleSelectAll = useCallback(() => {
|
|
174
|
+
const allIds = new Set(data.map((item) => String(item.id)));
|
|
175
|
+
setSelectedIds(allIds);
|
|
176
|
+
}, [data]);
|
|
177
|
+
const handleClearSelection = useCallback(() => {
|
|
178
|
+
setSelectedIds(/* @__PURE__ */ new Set());
|
|
179
|
+
}, []);
|
|
180
|
+
const getItemName = useCallback((item) => {
|
|
181
|
+
return String(item.name || item.title || item.id);
|
|
182
|
+
}, []);
|
|
183
|
+
const quickActions = useMemo(
|
|
184
|
+
() => [
|
|
185
|
+
{
|
|
186
|
+
id: "view-usages",
|
|
187
|
+
label: "View Usages",
|
|
188
|
+
icon: /* @__PURE__ */ jsx(BarChart3, { className: "h-4 w-4" }),
|
|
189
|
+
onClick: (item) => {
|
|
190
|
+
router.push(`/dashboard/patterns/${item.id}/reports`);
|
|
191
|
+
},
|
|
192
|
+
dataCySuffix: "usages"
|
|
193
|
+
}
|
|
194
|
+
],
|
|
195
|
+
[router]
|
|
196
|
+
);
|
|
197
|
+
const dropdownActions = useMemo(
|
|
198
|
+
() => [
|
|
199
|
+
{
|
|
200
|
+
id: "edit",
|
|
201
|
+
label: "Edit",
|
|
202
|
+
icon: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" }),
|
|
203
|
+
onClick: (item) => router.push(`/dashboard/patterns/${item.id}/edit`),
|
|
204
|
+
dataCySuffix: "edit"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: "delete",
|
|
208
|
+
label: "Delete",
|
|
209
|
+
icon: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }),
|
|
210
|
+
onClick: handleDeleteClick,
|
|
211
|
+
variant: "destructive",
|
|
212
|
+
separator: true,
|
|
213
|
+
dataCySuffix: "delete"
|
|
214
|
+
}
|
|
215
|
+
],
|
|
216
|
+
[router, handleDeleteClick]
|
|
217
|
+
);
|
|
218
|
+
if (isLoading) {
|
|
219
|
+
return /* @__PURE__ */ jsx(SkeletonEntityList, {});
|
|
220
|
+
}
|
|
221
|
+
if (configError || !entityConfig) {
|
|
222
|
+
return /* @__PURE__ */ jsx(Alert, { children: /* @__PURE__ */ jsx(AlertDescription, { children: configError || `Could not load configuration for entity "${entityType}".` }) });
|
|
223
|
+
}
|
|
224
|
+
if (dataError) {
|
|
225
|
+
return /* @__PURE__ */ jsx(Alert, { children: /* @__PURE__ */ jsx(AlertDescription, { children: dataError }) });
|
|
226
|
+
}
|
|
227
|
+
const bulkOperationsEnabled = entityConfig.ui.features.bulkOperations;
|
|
228
|
+
const enableSearch = entityConfig.ui.features.searchable;
|
|
229
|
+
const enableFilters = entityConfig.ui.features.filterable && ((_a = entityConfig.ui.dashboard.filters) == null ? void 0 : _a.length);
|
|
230
|
+
const searchValue = typeof filters.search === "string" ? filters.search : "";
|
|
231
|
+
return /* @__PURE__ */ jsx("div", { "data-cy": sel("entities.page.container", { slug: entityConfig.slug }), children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-6", "data-cy": sel("entities.list.container", { slug: entityConfig.slug }), children: [
|
|
232
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
233
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
234
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold tracking-tight", "data-cy": sel("entities.header.title", { slug: entityConfig.slug }), children: entityConfig.names.plural }),
|
|
235
|
+
/* @__PURE__ */ jsxs("p", { className: "text-muted-foreground", children: [
|
|
236
|
+
"Manage your ",
|
|
237
|
+
entityConfig.names.plural.toLowerCase()
|
|
238
|
+
] })
|
|
239
|
+
] }),
|
|
240
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: canCreate && /* @__PURE__ */ jsx(Button, { asChild: true, "data-cy": sel("entities.list.addButton", { slug: entityConfig.slug }), children: /* @__PURE__ */ jsxs(Link, { href: `/dashboard/${entityConfig.slug}/create`, children: [
|
|
241
|
+
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 mr-2" }),
|
|
242
|
+
"Add ",
|
|
243
|
+
entityConfig.names.singular
|
|
244
|
+
] }) }) })
|
|
245
|
+
] }),
|
|
246
|
+
(enableSearch || enableFilters) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-3 items-center flex-wrap", children: [
|
|
247
|
+
enableSearch && /* @__PURE__ */ jsx(
|
|
248
|
+
SearchInput,
|
|
249
|
+
{
|
|
250
|
+
placeholder: `Search ${entityConfig.names.plural.toLowerCase()}...`,
|
|
251
|
+
value: searchValue,
|
|
252
|
+
onChange: (e) => handleSearch(e.target.value),
|
|
253
|
+
containerClassName: "flex-1 max-w-md",
|
|
254
|
+
"data-cy": sel("entities.list.search.container", { slug: entityConfig.slug })
|
|
255
|
+
}
|
|
256
|
+
),
|
|
257
|
+
enableFilters && ((_b = entityConfig.ui.dashboard.filters) == null ? void 0 : _b.map((filterConfig) => {
|
|
258
|
+
const field = entityConfig.fields.find((f) => f.name === filterConfig.field);
|
|
259
|
+
if (!(field == null ? void 0 : field.options)) return null;
|
|
260
|
+
const filterValues = filters[filterConfig.field];
|
|
261
|
+
const values = Array.isArray(filterValues) ? filterValues : [];
|
|
262
|
+
return /* @__PURE__ */ jsx(
|
|
263
|
+
MultiSelectFilter,
|
|
264
|
+
{
|
|
265
|
+
label: filterConfig.label || field.display.label,
|
|
266
|
+
options: field.options.map((opt) => ({
|
|
267
|
+
value: String(opt.value),
|
|
268
|
+
label: opt.label
|
|
269
|
+
})),
|
|
270
|
+
values,
|
|
271
|
+
onChange: (newValues) => setFilter(filterConfig.field, newValues),
|
|
272
|
+
"data-cy": sel("entities.list.filters.trigger", { slug: entityConfig.slug, field: filterConfig.field })
|
|
273
|
+
},
|
|
274
|
+
filterConfig.field
|
|
275
|
+
);
|
|
276
|
+
})),
|
|
277
|
+
isSearching && /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" })
|
|
278
|
+
] }),
|
|
279
|
+
/* @__PURE__ */ jsx(
|
|
280
|
+
EntityTable,
|
|
281
|
+
{
|
|
282
|
+
entityConfig,
|
|
283
|
+
data,
|
|
284
|
+
loading: isInitialLoad,
|
|
285
|
+
selectable: bulkOperationsEnabled,
|
|
286
|
+
selectedIds,
|
|
287
|
+
onSelectionChange: setSelectedIds,
|
|
288
|
+
enableSearch: false,
|
|
289
|
+
searchQuery: searchValue,
|
|
290
|
+
onSearch: handleSearch,
|
|
291
|
+
getItemName,
|
|
292
|
+
teamId,
|
|
293
|
+
useDefaultActions: false,
|
|
294
|
+
quickActions,
|
|
295
|
+
dropdownActions,
|
|
296
|
+
showHeader: false,
|
|
297
|
+
pagination: {
|
|
298
|
+
pageSize: 10,
|
|
299
|
+
showPageSizeSelector: true,
|
|
300
|
+
pageSizeOptions: [10, 20, 50, 100]
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
),
|
|
304
|
+
bulkOperationsEnabled && /* @__PURE__ */ jsx(
|
|
305
|
+
EntityBulkActions,
|
|
306
|
+
{
|
|
307
|
+
entitySlug: entityConfig.slug,
|
|
308
|
+
selectedIds,
|
|
309
|
+
onClearSelection: handleClearSelection,
|
|
310
|
+
config: {
|
|
311
|
+
enableSelectAll: true,
|
|
312
|
+
totalItems: data.length,
|
|
313
|
+
onSelectAll: handleSelectAll,
|
|
314
|
+
enableDelete: true,
|
|
315
|
+
onDelete: handleBulkDelete,
|
|
316
|
+
itemLabel: entityConfig.names.singular,
|
|
317
|
+
itemLabelPlural: entityConfig.names.plural
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
),
|
|
321
|
+
deleteTarget && /* @__PURE__ */ jsx(
|
|
322
|
+
PatternDeleteDialog,
|
|
323
|
+
{
|
|
324
|
+
patternId: deleteTarget.id,
|
|
325
|
+
patternTitle: getItemName(deleteTarget),
|
|
326
|
+
onConfirm: handleConfirmDelete,
|
|
327
|
+
isDeleting,
|
|
328
|
+
open: isDeleteDialogOpen,
|
|
329
|
+
onOpenChange: setIsDeleteDialogOpen
|
|
330
|
+
}
|
|
331
|
+
)
|
|
332
|
+
] }) });
|
|
333
|
+
}
|
|
334
|
+
export {
|
|
335
|
+
PatternsListPage as default
|
|
336
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern Page Components
|
|
3
|
+
*
|
|
4
|
+
* Pre-compiled page components for pattern management.
|
|
5
|
+
* Import these instead of templates for proper TypeScript support.
|
|
6
|
+
*/
|
|
7
|
+
export { default as PatternsListPage } from './PatternsListPage';
|
|
8
|
+
export { default as PatternsCreatePage } from './PatternsCreatePage';
|
|
9
|
+
export { default as PatternEditPage } from './PatternEditPage';
|
|
10
|
+
export { default as PatternReportsPage } from './PatternReportsPage';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/patterns/pages/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAChE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACpE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAC9D,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,sBAAsB,CAAA"}
|