@donotdev/ui 0.0.11 → 0.0.13
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/common/FeatureCard.js +1 -1
- package/dist/components/common/RedirectOverlay.js +1 -1
- package/dist/components/cookie-consent/CookieConsent.js +3 -3
- package/dist/components/layout/GameContainer.d.ts +26 -8
- package/dist/components/layout/GameContainer.d.ts.map +1 -1
- package/dist/components/layout/GameContainer.js +21 -2
- package/dist/components/layout/GameFlow.d.ts.map +1 -1
- package/dist/components/layout/GameFlow.js +27 -11
- package/dist/components/layout/PageContainer.d.ts +1 -1
- package/dist/components/layout/PageContainer.d.ts.map +1 -1
- package/dist/components/layout/components/header/CacheSettings.js +1 -1
- package/dist/crud/components/EntityCardList.d.ts.map +1 -1
- package/dist/crud/components/EntityCardList.js +16 -10
- package/dist/crud/components/EntityDisplayRenderer.js +2 -2
- package/dist/crud/components/EntityFormRenderer.d.ts.map +1 -1
- package/dist/crud/components/EntityFormRenderer.js +31 -20
- package/dist/crud/components/EntityList.d.ts +1 -1
- package/dist/crud/components/EntityList.d.ts.map +1 -1
- package/dist/crud/components/EntityList.js +11 -11
- package/dist/crud/components/Form.js +1 -1
- package/dist/dndev.css +128 -83
- package/dist/index.js +4 -4
- package/dist/internal/common/RouteErrorFallback.js +3 -3
- package/dist/internal/devtools/components/ConfigTab.js +1 -1
- package/dist/internal/devtools/components/CookieTab.js +1 -1
- package/dist/internal/devtools/components/DesignTab.js +2 -2
- package/dist/internal/devtools/components/StoresTab.js +2 -2
- package/dist/internal/layout/config/presets/game.d.ts.map +1 -1
- package/dist/internal/layout/config/presets/game.js +10 -5
- package/dist/internal/layout/zones/DnDevFooter.js +1 -1
- package/dist/internal/layout/zones/DnDevMergedBar.js +1 -1
- package/dist/routing/404.js +3 -3
- package/dist/routing/AuthGuardFallback.js +2 -2
- package/dist/styles/index.css +128 -83
- package/package.json +5 -5
|
@@ -56,7 +56,7 @@ const FeatureCard = ({ icon, title, subtitle, content, href, variant, elevated,
|
|
|
56
56
|
margin: 0,
|
|
57
57
|
};
|
|
58
58
|
// Build title with icon if provided
|
|
59
|
-
const titleContent = icon ? (_jsxs(Stack, { direction: "row", align: "center",
|
|
59
|
+
const titleContent = icon ? (_jsxs(Stack, { direction: "row", align: "center", style: { width: '100%' }, children: [_jsx(IconBox, { icon: icon }), _jsx(Text, { as: "div", level: "h3", style: titleStyle, children: title })] })) : (_jsx(Text, { as: "div", level: "h3", style: titleStyle, children: title }));
|
|
60
60
|
const card = (_jsx(Card, { variant: variant, elevated: elevated, onClick: onClick, className: className, "data-clickable": href || onClick ? 'true' : undefined, style: {
|
|
61
61
|
paddingInlineStart: 'var(--gap-md)',
|
|
62
62
|
paddingInlineEnd: 'var(--gap-md)',
|
|
@@ -219,7 +219,7 @@ export function RedirectOverlay() {
|
|
|
219
219
|
textAlign: 'center',
|
|
220
220
|
maxWidth: '400px',
|
|
221
221
|
padding: 'var(--gap-lg)',
|
|
222
|
-
}, children: [_jsxs(Stack, { direction: "row",
|
|
222
|
+
}, children: [_jsxs(Stack, { direction: "row", align: "center", children: [IconComponent && (_jsx(IconComponent, { style: {
|
|
223
223
|
width: '1.5rem',
|
|
224
224
|
height: '1.5rem',
|
|
225
225
|
color: 'var(--primary)',
|
|
@@ -141,14 +141,14 @@ function CookieConsent({ position = 'bottom', showBranding = true, brandingText
|
|
|
141
141
|
declineAll();
|
|
142
142
|
setShowBanner(false);
|
|
143
143
|
};
|
|
144
|
-
const bannerContent = (_jsxs(Stack, {
|
|
144
|
+
const bannerContent = (_jsxs(Stack, { children: [_jsx(Text, { as: "p", variant: "muted", level: "small", children: t('cookieBannerDescription') }), hasOptionalCategories && (_jsx(Accordion, { type: "single", collapsible: true, value: showPreferences ? 'preferences' : '', onValueChange: (value) => setShowPreferences(value === 'preferences'), items: [
|
|
145
145
|
{
|
|
146
146
|
value: 'preferences',
|
|
147
147
|
trigger: (_jsxs(Stack, { direction: "row", align: "center", gap: "tight", children: [_jsx(Settings, { className: "dndev-size-md" }), t('customize')] })),
|
|
148
|
-
content: (_jsx(Stack, {
|
|
148
|
+
content: (_jsx(Stack, { children: categoryDefs.map((cat) => {
|
|
149
149
|
const Icon = cat.icon;
|
|
150
150
|
const enabled = preferenceCategories[cat.id] ?? false;
|
|
151
|
-
return (_jsx(Card, { content: _jsxs(Stack, { direction: "row", align: "center", justify: "between",
|
|
151
|
+
return (_jsx(Card, { content: _jsxs(Stack, { direction: "row", align: "center", justify: "between", children: [_jsxs(Stack, { direction: "row", align: "start", gap: "tight", style: { flex: 1 }, children: [_jsx(Icon, { className: "dndev-size-md", style: {
|
|
152
152
|
color: 'var(--primary)',
|
|
153
153
|
marginTop: '0.125rem',
|
|
154
154
|
} }), _jsxs(Stack, { gap: "tight", children: [_jsx(Text, { as: "span", level: "body", style: { fontWeight: 600 }, children: cat.title }), _jsx(Text, { as: "span", variant: "muted", level: "small", children: cat.description }), cat.examples && (_jsx(Text, { as: "span", variant: "muted", level: "small", style: { fontStyle: 'italic' }, children: cat.examples }))] })] }), cat.locked ? (_jsx(Text, { as: "span", variant: "muted", level: "small", children: t('alwaysOn') })) : (_jsx(Switch, { checked: enabled, onCheckedChange: (val) => setPreferenceCategories((prev) => ({
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* CTA button definition
|
|
4
|
+
*/
|
|
5
|
+
export interface CTAButton {
|
|
6
|
+
label: string;
|
|
7
|
+
onClick: () => void;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
variant?: 'default' | 'outline' | 'ghost' | 'link';
|
|
10
|
+
}
|
|
2
11
|
/**
|
|
3
12
|
* GameContainer props
|
|
4
13
|
*/
|
|
5
14
|
export interface GameContainerProps {
|
|
6
15
|
/** Main content area (scrollable, centered by default) */
|
|
7
16
|
content: ReactNode;
|
|
8
|
-
/** Optional fixed CTA button at bottom */
|
|
9
|
-
cta?:
|
|
10
|
-
label: string;
|
|
11
|
-
onClick: () => void;
|
|
12
|
-
disabled?: boolean;
|
|
13
|
-
variant?: 'default' | 'outline' | 'ghost' | 'link';
|
|
14
|
-
};
|
|
17
|
+
/** Optional fixed CTA button(s) at bottom - single button or array for multiple buttons */
|
|
18
|
+
cta?: CTAButton | CTAButton[];
|
|
15
19
|
/** Alignment variant for content area */
|
|
16
20
|
align?: 'center' | 'start' | 'stretch';
|
|
17
21
|
/** Justify variant for content area */
|
|
18
22
|
justify?: 'center' | 'start' | 'end' | 'between';
|
|
19
23
|
/** Disable ScrollArea (use plain overflow) - for interactive content */
|
|
20
24
|
disableScrollArea?: boolean;
|
|
25
|
+
/** Content width: 'narrow' (default, constrained) or 'full' */
|
|
26
|
+
contentVariant?: 'full' | 'narrow';
|
|
21
27
|
/** Additional className for content wrapper */
|
|
22
28
|
contentClassName?: string;
|
|
23
29
|
/** Additional className for CTA button */
|
|
@@ -66,9 +72,21 @@ export interface GameContainerProps {
|
|
|
66
72
|
* />
|
|
67
73
|
* ```
|
|
68
74
|
*
|
|
75
|
+
* @example
|
|
76
|
+
* Multiple CTA buttons (side by side)
|
|
77
|
+
* ```tsx
|
|
78
|
+
* <GameContainer
|
|
79
|
+
* content={<ChallengeContent />}
|
|
80
|
+
* cta={[
|
|
81
|
+
* { label: 'Accept', onClick: handleAccept, variant: 'default' },
|
|
82
|
+
* { label: 'Decline', onClick: handleDecline, variant: 'outline' }
|
|
83
|
+
* ]}
|
|
84
|
+
* />
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
69
87
|
* @version 0.0.1
|
|
70
88
|
* @since 0.0.1
|
|
71
89
|
* @author AMBROISE PARK Consulting
|
|
72
90
|
*/
|
|
73
|
-
export declare function GameContainer({ content, cta, align, justify, disableScrollArea, contentClassName, ctaClassName, }: GameContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
91
|
+
export declare function GameContainer({ content, cta, align, justify, disableScrollArea, contentVariant, contentClassName, ctaClassName, }: GameContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
74
92
|
//# sourceMappingURL=GameContainer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GameContainer.d.ts","sourceRoot":"","sources":["../../../src/components/layout/GameContainer.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"GameContainer.d.ts","sourceRoot":"","sources":["../../../src/components/layout/GameContainer.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;CACpD;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,OAAO,EAAE,SAAS,CAAC;IAEnB,2FAA2F;IAC3F,GAAG,CAAC,EAAE,SAAS,GAAG,SAAS,EAAE,CAAC;IAE9B,yCAAyC;IACzC,KAAK,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;IAEvC,uCAAuC;IACvC,OAAO,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,KAAK,GAAG,SAAS,CAAC;IAEjD,wEAAwE;IACxE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B,+DAA+D;IAC/D,cAAc,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAEnC,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,GAAG,EACH,KAAgB,EAChB,OAAkB,EAClB,iBAAyB,EACzB,cAAyB,EACzB,gBAAgB,EAChB,YAAY,GACb,EAAE,kBAAkB,2CAoEpB"}
|
|
@@ -53,10 +53,29 @@ import { cn } from '@donotdev/components';
|
|
|
53
53
|
* />
|
|
54
54
|
* ```
|
|
55
55
|
*
|
|
56
|
+
* @example
|
|
57
|
+
* Multiple CTA buttons (side by side)
|
|
58
|
+
* ```tsx
|
|
59
|
+
* <GameContainer
|
|
60
|
+
* content={<ChallengeContent />}
|
|
61
|
+
* cta={[
|
|
62
|
+
* { label: 'Accept', onClick: handleAccept, variant: 'default' },
|
|
63
|
+
* { label: 'Decline', onClick: handleDecline, variant: 'outline' }
|
|
64
|
+
* ]}
|
|
65
|
+
* />
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
56
68
|
* @version 0.0.1
|
|
57
69
|
* @since 0.0.1
|
|
58
70
|
* @author AMBROISE PARK Consulting
|
|
59
71
|
*/
|
|
60
|
-
export function GameContainer({ content, cta, align = 'center', justify = 'center', disableScrollArea = false, contentClassName, ctaClassName, }) {
|
|
61
|
-
|
|
72
|
+
export function GameContainer({ content, cta, align = 'center', justify = 'center', disableScrollArea = false, contentVariant = 'narrow', contentClassName, ctaClassName, }) {
|
|
73
|
+
const narrowWrapper = contentVariant === 'narrow' && (_jsx("div", { className: "dndev-game-container__content-narrow", children: content }));
|
|
74
|
+
const scrollContent = contentVariant === 'narrow' ? narrowWrapper : content;
|
|
75
|
+
const contentInner = disableScrollArea ? (_jsx("div", { style: {
|
|
76
|
+
overflow: 'auto',
|
|
77
|
+
overscrollBehavior: 'contain',
|
|
78
|
+
height: '100%',
|
|
79
|
+
}, children: scrollContent })) : (_jsx(ScrollArea, { className: "dndev-game-container__scroll", children: scrollContent }));
|
|
80
|
+
return (_jsxs("div", { className: "dndev-game-container", children: [_jsx("div", { className: cn('dndev-game-container__content', contentClassName), "data-align": align, "data-justify": justify, "data-content-variant": contentVariant, children: _jsx("div", { className: "dndev-game-container__scroll-wrapper", children: contentInner }) }), cta && (_jsx("div", { className: cn('dndev-game-container__cta', ctaClassName), children: Array.isArray(cta) ? (_jsx("div", { className: "dndev-game-container__cta-buttons", children: cta.map((button, index) => (_jsx(Button, { onClick: button.onClick, disabled: button.disabled ?? false, variant: button.variant ?? 'default', className: "dndev-game-container__cta-button", fullWidth: true, children: button.label }, index))) })) : (_jsx(Button, { onClick: cta.onClick, disabled: cta.disabled ?? false, variant: cta.variant ?? 'default', className: "dndev-game-container__cta-button", children: cta.label })) }))] }));
|
|
62
81
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GameFlow.d.ts","sourceRoot":"","sources":["../../../src/components/layout/GameFlow.tsx"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"GameFlow.d.ts","sourceRoot":"","sources":["../../../src/components/layout/GameFlow.tsx"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CACjC,MAAM,EACN,MAAM,OAAO,CAAC;IAAE,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,CAAA;CAAE,CAAC,CAC/C,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,cAAc,EAAE,cAAc,CAAC;IAC/B,oCAAoC;IACpC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAmCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,QAAQ,CAAC,EACvB,cAAc,EACd,aAAa,EACb,UAAU,EACV,QAAe,GAChB,EAAE,aAAa,kDAgCf"}
|
|
@@ -8,12 +8,34 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
8
8
|
* @since 0.0.1
|
|
9
9
|
* @author AMBROISE PARK Consulting
|
|
10
10
|
*/
|
|
11
|
-
import { useEffect,
|
|
11
|
+
import { useEffect, Suspense, lazy, } from 'react';
|
|
12
12
|
/**
|
|
13
13
|
* Module-level cache for preloaded screens
|
|
14
14
|
* Tracks which screens have been preloaded to avoid duplicate preloading
|
|
15
15
|
*/
|
|
16
16
|
const preloadedScreens = new Set();
|
|
17
|
+
/**
|
|
18
|
+
* Module-level cache for lazy components.
|
|
19
|
+
* CRITICAL: lazy() must NOT be called inside a component (useMemo or otherwise).
|
|
20
|
+
* Doing so creates a new component TYPE on each call, which causes React to
|
|
21
|
+
* unmount/remount the entire subtree and can trigger Rules of Hooks violations.
|
|
22
|
+
* This cache ensures each screen name always resolves to the same lazy component ref.
|
|
23
|
+
*/
|
|
24
|
+
const lazyComponentCache = new Map();
|
|
25
|
+
/**
|
|
26
|
+
* Get or create a cached lazy component for a screen name
|
|
27
|
+
*/
|
|
28
|
+
function getLazyComponent(screenName, registry) {
|
|
29
|
+
const cached = lazyComponentCache.get(screenName);
|
|
30
|
+
if (cached)
|
|
31
|
+
return cached;
|
|
32
|
+
const importer = registry[screenName];
|
|
33
|
+
if (!importer)
|
|
34
|
+
return null;
|
|
35
|
+
const component = lazy(importer);
|
|
36
|
+
lazyComponentCache.set(screenName, component);
|
|
37
|
+
return component;
|
|
38
|
+
}
|
|
17
39
|
/**
|
|
18
40
|
* GameFlow - Screen router for game/session flows
|
|
19
41
|
*
|
|
@@ -59,16 +81,10 @@ export function GameFlow({ screenRegistry, currentScreen, nextScreen, fallback =
|
|
|
59
81
|
}
|
|
60
82
|
}
|
|
61
83
|
}, [nextScreen, screenRegistry]);
|
|
62
|
-
// Get lazy component for currentScreen
|
|
63
|
-
const ScreenComponent =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const importer = screenRegistry[currentScreen];
|
|
67
|
-
if (!importer)
|
|
68
|
-
return null;
|
|
69
|
-
// Create lazy component from registry
|
|
70
|
-
return lazy(importer);
|
|
71
|
-
}, [currentScreen, screenRegistry]);
|
|
84
|
+
// Get cached lazy component for currentScreen (stable reference per screen name)
|
|
85
|
+
const ScreenComponent = currentScreen
|
|
86
|
+
? getLazyComponent(currentScreen, screenRegistry)
|
|
87
|
+
: null;
|
|
72
88
|
// If no currentScreen or no component found, return null
|
|
73
89
|
if (!currentScreen || !ScreenComponent) {
|
|
74
90
|
return null;
|
|
@@ -3,7 +3,7 @@ import type { ComponentType, ReactNode } from 'react';
|
|
|
3
3
|
/**
|
|
4
4
|
* Page container variants - semantic layout patterns
|
|
5
5
|
*/
|
|
6
|
-
export type PageContainerVariant = 'full' | 'standard' | 'docs' | 'fixed';
|
|
6
|
+
export type PageContainerVariant = 'full' | 'standard' | 'docs' | 'narrow' | 'fixed';
|
|
7
7
|
/**
|
|
8
8
|
* PageContainer component props
|
|
9
9
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageContainer.d.ts","sourceRoot":"","sources":["../../../src/components/layout/PageContainer.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAK9C,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEtD;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAC5B,MAAM,GACN,UAAU,GACV,MAAM,GACN,OAAO,CAAC;AAEZ;;GAEG;AACH,UAAU,kBAAkB;IAC1B,4BAA4B;IAC5B,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAE/B,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,oBAAoB;IACpB,QAAQ,EAAE,SAAS,CAAC;IAEpB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,aAAa,EAAE,aAAa,CAAC,kBAAkB,CA4B3D,CAAC"}
|
|
1
|
+
{"version":3,"file":"PageContainer.d.ts","sourceRoot":"","sources":["../../../src/components/layout/PageContainer.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAK9C,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEtD;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAC5B,MAAM,GACN,UAAU,GACV,MAAM,GACN,QAAQ,GACR,OAAO,CAAC;AAEZ;;GAEG;AACH,UAAU,kBAAkB;IAC1B,4BAA4B;IAC5B,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAE/B,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,oBAAoB;IACpB,QAAQ,EAAE,SAAS,CAAC;IAEpB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,aAAa,EAAE,aAAa,CAAC,kBAAkB,CA4B3D,CAAC"}
|
|
@@ -205,7 +205,7 @@ function CacheSettings() {
|
|
|
205
205
|
setIsClearing(false);
|
|
206
206
|
}
|
|
207
207
|
};
|
|
208
|
-
return (_jsxs(Stack, {
|
|
208
|
+
return (_jsxs(Stack, { children: [_jsx(Stack, { children: Object.entries(cacheOptions).map(([key, checked]) => (_jsxs(Stack, { direction: "row", align: "center", gap: "tight", children: [_jsx(Checkbox, { id: key, checked: checked, onCheckedChange: (checked) => setCacheOptions((prev) => ({ ...prev, [key]: !!checked })) }), _jsx("label", { htmlFor: key, style: {
|
|
209
209
|
fontSize: 'var(--font-size-sm)',
|
|
210
210
|
fontWeight: 500,
|
|
211
211
|
lineHeight: 1,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityCardList.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityCardList.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"EntityCardList.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityCardList.tsx"],"names":[],"mappings":"AAyCA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,QAAQ,EACR,OAAO,EACP,IAAmB,EACnB,SAA0B,EAAE,2BAA2B;AACvD,MAAM,EACN,WAAmB,GACpB,EAAE,mBAAmB,2CA6RrB"}
|
|
@@ -13,12 +13,12 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
13
13
|
* @since 0.0.1
|
|
14
14
|
* @author AMBROISE PARK Consulting
|
|
15
15
|
*/
|
|
16
|
-
import { useMemo, useCallback
|
|
16
|
+
import { useMemo, useCallback } from 'react';
|
|
17
17
|
import { Heart } from 'lucide-react';
|
|
18
18
|
import { Grid, Card, Stack, Text, Spinner, Section, Button, } from '@donotdev/components';
|
|
19
19
|
import { useTranslation } from '@donotdev/core';
|
|
20
20
|
import { useNavigate } from '../../routing';
|
|
21
|
-
import { translateFieldLabel, useCrudCardList, EntityFilters, useEntityFavorites, matchesFilter, formatValue, } from '@donotdev/crud';
|
|
21
|
+
import { translateFieldLabel, useCrudCardList, EntityFilters, useEntityFavorites, matchesFilter, formatValue, useCrudFilters, } from '@donotdev/crud';
|
|
22
22
|
/**
|
|
23
23
|
* Entity Card List Component - Card grid view for public/user-facing browsing
|
|
24
24
|
*
|
|
@@ -41,9 +41,15 @@ filter, hideFilters = false, }) {
|
|
|
41
41
|
const { isFavorite, toggleFavorite, favoritesFilter } = useEntityFavorites({
|
|
42
42
|
collection: entity.collection,
|
|
43
43
|
});
|
|
44
|
-
|
|
45
|
-
//
|
|
46
|
-
const
|
|
44
|
+
// Favorites toggle from CrudStore (persists across navigation)
|
|
45
|
+
// Note: EntityFilters now manages its own filters internally via useCrudFilters
|
|
46
|
+
const { showFavoritesOnly, setShowFavoritesOnly } = useCrudFilters({
|
|
47
|
+
collection: entity.collection,
|
|
48
|
+
});
|
|
49
|
+
// Get filters for applying to data (EntityFilters manages its own state)
|
|
50
|
+
const { filters } = useCrudFilters({
|
|
51
|
+
collection: entity.collection,
|
|
52
|
+
});
|
|
47
53
|
// Apply filters from EntityFilters component
|
|
48
54
|
const applyFilters = useCallback((item) => {
|
|
49
55
|
if (Object.keys(filters).length === 0)
|
|
@@ -110,22 +116,22 @@ filter, hideFilters = false, }) {
|
|
|
110
116
|
return (_jsxs(_Fragment, { children: [!hideFilters && (_jsx(Section, { title: tCrud('filters.title', {
|
|
111
117
|
entity: entityName,
|
|
112
118
|
defaultValue: `Browse ${entityName} - Filters`,
|
|
113
|
-
}), collapsible: true, defaultOpen: true, children: _jsxs(Stack, { direction: "column",
|
|
119
|
+
}), collapsible: true, defaultOpen: true, children: _jsxs(Stack, { direction: "column", children: [_jsx(Button, { variant: showFavoritesOnly ? 'primary' : 'outline', icon: _jsx(Heart, { size: 18 }), onClick: () => setShowFavoritesOnly(!showFavoritesOnly), children: showFavoritesOnly
|
|
114
120
|
? tCrud('favorites.showAll', { defaultValue: 'Show All' })
|
|
115
121
|
: tCrud('favorites.showFavorites', {
|
|
116
122
|
defaultValue: 'Show Favorites',
|
|
117
|
-
}) }), _jsx(EntityFilters, { entity: entity, data: rawData,
|
|
123
|
+
}) }), _jsx(EntityFilters, { entity: entity, data: rawData, fieldsToFilter: entity.listCardFields })] }) })), _jsx(Section, { title: loading
|
|
118
124
|
? tCrud('results.title.fetching', { defaultValue: 'Fetching...' })
|
|
119
125
|
: tCrud('results.title.count', {
|
|
120
126
|
count: data.length,
|
|
121
127
|
defaultValue: data.length === 1
|
|
122
128
|
? 'Found 1 occurrence'
|
|
123
129
|
: `Found ${data.length} occurrences`,
|
|
124
|
-
}), collapsible: true, defaultOpen: true, children: loading ? (_jsx(Stack, { align: "center", justify: "center",
|
|
130
|
+
}), collapsible: true, defaultOpen: true, children: loading ? (_jsx(Stack, { align: "center", justify: "center", style: { padding: 'var(--gap-3xl)' }, children: _jsx(Spinner, {}) })) : data.length === 0 ? (_jsxs(Stack, { align: "center", justify: "center", style: { padding: 'var(--gap-3xl)', textAlign: 'center' }, children: [_jsx(Text, { level: "h3", style: { color: 'var(--muted-foreground)' }, children: tCrud('emptyState.title', {
|
|
125
131
|
defaultValue: `No ${entity.name.toLowerCase()} found`,
|
|
126
132
|
}) }), _jsx(Text, { style: { color: 'var(--muted-foreground)' }, children: tCrud('emptyState.description', {
|
|
127
133
|
defaultValue: `No ${entity.name.toLowerCase()} available at this time.`,
|
|
128
|
-
}) })] })) : (_jsx(Grid, { cols: cols,
|
|
134
|
+
}) })] })) : (_jsx(Grid, { cols: cols, children: data.map((item) => {
|
|
129
135
|
const imageValue = imageField ? item[imageField] : null;
|
|
130
136
|
// Backend optimizes picture fields for listCard: returns thumbUrl string directly
|
|
131
137
|
// (or fullUrl if thumbUrl missing, or null if no picture)
|
|
@@ -152,7 +158,7 @@ filter, hideFilters = false, }) {
|
|
|
152
158
|
})
|
|
153
159
|
: tCrud('favorites.add', {
|
|
154
160
|
defaultValue: 'Add to favorites',
|
|
155
|
-
}) }), _jsxs(Stack, { direction: "column",
|
|
161
|
+
}) }), _jsxs(Stack, { direction: "column", children: [imageUrl && (_jsx("div", { style: {
|
|
156
162
|
width: '100%',
|
|
157
163
|
aspectRatio: '16/9',
|
|
158
164
|
borderRadius: 'var(--radius-md)',
|
|
@@ -135,7 +135,7 @@ export function EntityDisplayRenderer({ entity, id, t, className = '', backend =
|
|
|
135
135
|
}
|
|
136
136
|
// Error or not found state
|
|
137
137
|
if (displayError || !displayData) {
|
|
138
|
-
return (_jsx(Stack, { align: "center", justify: "center",
|
|
138
|
+
return (_jsx(Stack, { align: "center", justify: "center", style: {
|
|
139
139
|
padding: 'var(--gap-3xl)',
|
|
140
140
|
textAlign: 'center',
|
|
141
141
|
}, className: className, children: _jsxs(Stack, { direction: "column", gap: "tight", children: [_jsx("h3", { style: { color: 'var(--muted-foreground)' }, children: notFoundMessage ||
|
|
@@ -149,7 +149,7 @@ export function EntityDisplayRenderer({ entity, id, t, className = '', backend =
|
|
|
149
149
|
: String(displayError) }))] }) }));
|
|
150
150
|
}
|
|
151
151
|
// Render all visible fields with values
|
|
152
|
-
return (_jsx(Stack, { direction: "column",
|
|
152
|
+
return (_jsx(Stack, { direction: "column", className: className, children: visibleFields.map(([fieldName, fieldConfig]) => {
|
|
153
153
|
return (_jsx(DisplayFieldRenderer, { name: fieldName, config: fieldConfig, value: displayData[fieldName], t: translate }, fieldName));
|
|
154
154
|
}) }));
|
|
155
155
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityFormRenderer.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityFormRenderer.tsx"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EACV,uBAAuB,EACvB,YAAY,EAEb,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,uBAAuB,EAAE,CAAC;AAExC;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EACxE,MAAM,EACN,QAAQ,EACR,CAAC,EACD,SAAc,EACd,UAAU,EACV,OAAe,EACf,aAAa,EACb,aAAyB,EACzB,mBAAmB,EACnB,sBAAkC,EAClC,iBAAiB,EACjB,UAAU,EACV,SAA6C,EAC7C,QAAiC,EACjC,MAAM,EAAE,cAAc,EACtB,UAAU,EACV,UAAU,EACV,WAAW,EACX,QAAQ,EACR,oBAA2B,EAC3B,qBAAqB,EACrB,kBAA0B,GAC3B,EAAE,uBAAuB,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"EntityFormRenderer.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityFormRenderer.tsx"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EACV,uBAAuB,EACvB,YAAY,EAEb,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,uBAAuB,EAAE,CAAC;AAExC;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EACxE,MAAM,EACN,QAAQ,EACR,CAAC,EACD,SAAc,EACd,UAAU,EACV,OAAe,EACf,aAAa,EACb,aAAyB,EACzB,mBAAmB,EACnB,sBAAkC,EAClC,iBAAiB,EACjB,UAAU,EACV,SAA6C,EAC7C,QAAiC,EACjC,MAAM,EAAE,cAAc,EACtB,UAAU,EACV,UAAU,EACV,WAAW,EACX,QAAQ,EACR,oBAA2B,EAC3B,qBAAqB,EACrB,kBAA0B,GAC3B,EAAE,uBAAuB,CAAC,CAAC,CAAC,2CAic5B;AAED,eAAe,kBAAkB,CAAC"}
|
|
@@ -13,7 +13,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
13
13
|
import { useEffect, useId, useMemo, useState } from 'react';
|
|
14
14
|
import { FormProvider } from 'react-hook-form';
|
|
15
15
|
import { Badge, Button, DropdownMenu, Grid, Stack, Spinner, } from '@donotdev/components';
|
|
16
|
-
import { useTranslation } from '@donotdev/core';
|
|
16
|
+
import { hasRoleAccess, useTranslation } from '@donotdev/core';
|
|
17
17
|
import { useNavigate } from '../../routing';
|
|
18
18
|
import { DisplayFieldRenderer, FormFieldRenderer, UploadProvider, useEntityForm, useUnsavedChangesWarning, useConfirmNavigation, useFormStore, } from '@donotdev/crud';
|
|
19
19
|
/**
|
|
@@ -34,12 +34,19 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
|
|
|
34
34
|
// Generate stable form ID
|
|
35
35
|
const generatedFormId = useId();
|
|
36
36
|
const formId = externalFormId ?? `entity-form-${entity.name}-${generatedFormId}`;
|
|
37
|
-
//
|
|
38
|
-
const { t: translationFn } = useTranslation(entity.namespace);
|
|
37
|
+
// Entity + crud namespaces so translateLabel can resolve crud:status.* etc.
|
|
38
|
+
const { t: translationFn } = useTranslation([entity.namespace, 'crud']);
|
|
39
39
|
const { t: tCrud } = useTranslation('crud');
|
|
40
40
|
const translate = t || translationFn;
|
|
41
|
-
//
|
|
42
|
-
const
|
|
41
|
+
// Visibility badges (developer-facing) are for admins/super only
|
|
42
|
+
const isAdminOrSuper = viewerRole === 'admin' || viewerRole === 'super';
|
|
43
|
+
const showVisibilityBadges = !hideVisibilityInfo && isAdminOrSuper;
|
|
44
|
+
// Preview dropdown (View As) is available for user/admin/super
|
|
45
|
+
const canPreviewRole = viewerRole === 'user' || viewerRole === 'admin' || viewerRole === 'super';
|
|
46
|
+
// Preview role state for View As toggle
|
|
47
|
+
const [previewRole, setPreviewRole] = useState(viewerRole === 'admin' || viewerRole === 'super' || viewerRole === 'user'
|
|
48
|
+
? viewerRole
|
|
49
|
+
: 'guest');
|
|
43
50
|
// Visibility → Badge variant mapping (inline, no separate file needed)
|
|
44
51
|
const visibilityBadgeVariant = {
|
|
45
52
|
guest: 'muted',
|
|
@@ -47,6 +54,7 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
|
|
|
47
54
|
admin: 'warning',
|
|
48
55
|
super: 'destructive',
|
|
49
56
|
technical: 'accent',
|
|
57
|
+
owner: 'secondary',
|
|
50
58
|
};
|
|
51
59
|
// Role hierarchy for filtering (higher index = more access)
|
|
52
60
|
const roleHierarchy = ['guest', 'user', 'admin', 'super'];
|
|
@@ -58,12 +66,14 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
|
|
|
58
66
|
super: 'Super',
|
|
59
67
|
technical: 'System',
|
|
60
68
|
};
|
|
61
|
-
// Preview dropdown menu items
|
|
62
|
-
const previewMenuItems = useMemo(() => roleHierarchy
|
|
69
|
+
// Preview dropdown menu items (roles current viewer can assume)
|
|
70
|
+
const previewMenuItems = useMemo(() => roleHierarchy
|
|
71
|
+
.filter((role) => hasRoleAccess(viewerRole, role))
|
|
72
|
+
.map((role) => ({
|
|
63
73
|
label: roleLabels[role],
|
|
64
74
|
checked: previewRole === role,
|
|
65
75
|
onClick: () => setPreviewRole(role),
|
|
66
|
-
})), [previewRole]);
|
|
76
|
+
})), [previewRole, viewerRole]);
|
|
67
77
|
// useEntityForm handles all orchestration
|
|
68
78
|
const form = useEntityForm(entity, {
|
|
69
79
|
formId,
|
|
@@ -73,10 +83,11 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
|
|
|
73
83
|
t: translate,
|
|
74
84
|
autoSave,
|
|
75
85
|
});
|
|
76
|
-
const { control, handleSubmit, formState: { errors }, fields: rawFields, formStatus, uploadProgress, cleanup, isDirty, resetForm, } = form;
|
|
86
|
+
const { control, handleSubmit, formState: { errors }, fields: rawFields, formStatus, uploadProgress, cleanup, isDirty, hasUserInteracted, resetForm, } = form;
|
|
87
|
+
const isDirtyForBlocking = isDirty && hasUserInteracted;
|
|
77
88
|
// Filter fields based on previewRole when visibility info is shown
|
|
78
89
|
const renderableFields = useMemo(() => {
|
|
79
|
-
if (
|
|
90
|
+
if (!canPreviewRole)
|
|
80
91
|
return rawFields;
|
|
81
92
|
const previewRoleIndex = roleHierarchy.indexOf(previewRole);
|
|
82
93
|
return rawFields.filter(({ config }) => {
|
|
@@ -89,13 +100,13 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
|
|
|
89
100
|
const fieldRoleIndex = roleHierarchy.indexOf(fieldVisibility);
|
|
90
101
|
return fieldRoleIndex <= previewRoleIndex;
|
|
91
102
|
});
|
|
92
|
-
}, [rawFields,
|
|
103
|
+
}, [rawFields, canPreviewRole, previewRole]);
|
|
93
104
|
// Sync isDirty to FormStore (single source of truth)
|
|
94
105
|
useEffect(() => {
|
|
95
106
|
if (formId) {
|
|
96
|
-
useFormStore.getState().setIsDirty(formId,
|
|
107
|
+
useFormStore.getState().setIsDirty(formId, isDirtyForBlocking);
|
|
97
108
|
}
|
|
98
|
-
}, [formId,
|
|
109
|
+
}, [formId, isDirtyForBlocking]);
|
|
99
110
|
// Cleanup on unmount
|
|
100
111
|
useEffect(() => {
|
|
101
112
|
return cleanup;
|
|
@@ -112,15 +123,15 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
|
|
|
112
123
|
// Warn about unsaved changes when navigating away (browser refresh/close)
|
|
113
124
|
// SPA navigation blocking is handled by router hooks via FormStore
|
|
114
125
|
useUnsavedChangesWarning({
|
|
115
|
-
isDirty,
|
|
126
|
+
isDirty: isDirtyForBlocking,
|
|
116
127
|
enabled: warnOnUnsavedChanges,
|
|
117
128
|
message: unsavedChangesLeaveMessage,
|
|
118
129
|
});
|
|
119
130
|
// Use framework helper for cancel confirmation (DRY, SSR-safe)
|
|
120
|
-
const confirmNavigation = useConfirmNavigation(
|
|
131
|
+
const confirmNavigation = useConfirmNavigation(isDirtyForBlocking, unsavedChangesDiscardMessage);
|
|
121
132
|
// Handle cancel with confirmation
|
|
122
133
|
const handleCancel = async () => {
|
|
123
|
-
if (
|
|
134
|
+
if (isDirtyForBlocking) {
|
|
124
135
|
const confirmed = await confirmNavigation();
|
|
125
136
|
if (!confirmed)
|
|
126
137
|
return;
|
|
@@ -212,8 +223,8 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
|
|
|
212
223
|
gridColumn: '1 / -1',
|
|
213
224
|
display: 'contents',
|
|
214
225
|
}, children: [isLoading && _jsx(Spinner, { overlay: true }), _jsxs("form", { onSubmit: handleSubmit(handleFormSubmit), noValidate: true, className: className, style: { width: '100%', gridColumn: '1 / -1', display: 'contents' }, children: [renderableFields.map(({ name, config, editable }) => {
|
|
215
|
-
// Show visibility badge for non-guest fields when visibility
|
|
216
|
-
const showBadge =
|
|
226
|
+
// Show visibility badge for non-guest fields when visibility badges are enabled (admins/super only)
|
|
227
|
+
const showBadge = showVisibilityBadges && config.visibility !== 'guest';
|
|
217
228
|
const badgeVariant = config.visibility !== 'hidden'
|
|
218
229
|
? visibilityBadgeVariant[config.visibility]
|
|
219
230
|
: undefined;
|
|
@@ -239,8 +250,8 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
|
|
|
239
250
|
if (showCancelButton && cancelButtonText) {
|
|
240
251
|
buttons.push(_jsx(Button, { type: "button", onClick: handleCancel, disabled: buttonState.loading, variant: "outline", className: "dndev-w-full", children: cancelButtonText }, "cancel"));
|
|
241
252
|
}
|
|
242
|
-
// Preview dropdown
|
|
243
|
-
if (
|
|
253
|
+
// Preview dropdown (user/admin/super only)
|
|
254
|
+
if (canPreviewRole) {
|
|
244
255
|
const previewVariant = visibilityBadgeVariant[previewRole] || 'muted';
|
|
245
256
|
buttons.push(_jsx(DropdownMenu, { trigger: _jsxs(Button, { type: "button", variant: previewVariant, disabled: buttonState.loading, className: "dndev-w-full", children: [tCrud('visibility.preview', {
|
|
246
257
|
defaultValue: 'Preview',
|
|
@@ -10,5 +10,5 @@ export type { EntityListProps };
|
|
|
10
10
|
* - Edit and Delete actions (admin only)
|
|
11
11
|
* - Auto-routing when handlers not provided
|
|
12
12
|
*/
|
|
13
|
-
export declare function EntityList({ entity, userRole, basePath, onClick, hideFilters, pagination, pageSize: pageSizeProp, queryOptions, }: EntityListProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export declare function EntityList({ entity, userRole, basePath, onClick, hideFilters, pagination, pageSize: pageSizeProp, queryOptions, exportable, }: EntityListProps): import("react/jsx-runtime").JSX.Element;
|
|
14
14
|
//# sourceMappingURL=EntityList.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityList.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityList.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"EntityList.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityList.tsx"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,QAAkB,EAClB,QAAQ,EACR,OAAO,EACP,WAAmB,EACnB,UAAqB,EACrB,QAAQ,EAAE,YAAY,EACtB,YAAY,EACZ,UAAiB,GAClB,EAAE,eAAe,2CAkTjB"}
|
|
@@ -18,7 +18,7 @@ import { useMemo, useCallback, useState } from 'react';
|
|
|
18
18
|
import { DataTable, Button, Stack, ActionButton, Section, Input, } from '@donotdev/components';
|
|
19
19
|
import { useTranslation } from '@donotdev/core';
|
|
20
20
|
import { useNavigate } from '../../routing';
|
|
21
|
-
import { translateFieldLabel, useCrud, useCrudList, EntityFilters, matchesFilter, formatValue, } from '@donotdev/crud';
|
|
21
|
+
import { translateFieldLabel, useCrud, useCrudList, EntityFilters, matchesFilter, formatValue, useCrudFilters, } from '@donotdev/crud';
|
|
22
22
|
/**
|
|
23
23
|
* Entity List Component - Table view for admin/internal operations
|
|
24
24
|
*
|
|
@@ -29,7 +29,7 @@ import { translateFieldLabel, useCrud, useCrudList, EntityFilters, matchesFilter
|
|
|
29
29
|
* - Edit and Delete actions (admin only)
|
|
30
30
|
* - Auto-routing when handlers not provided
|
|
31
31
|
*/
|
|
32
|
-
export function EntityList({ entity, userRole = 'guest', basePath, onClick, hideFilters = false, pagination = 'client', pageSize: pageSizeProp, queryOptions, }) {
|
|
32
|
+
export function EntityList({ entity, userRole = 'guest', basePath, onClick, hideFilters = false, pagination = 'client', pageSize: pageSizeProp, queryOptions, exportable = true, }) {
|
|
33
33
|
const navigate = useNavigate();
|
|
34
34
|
const base = basePath ?? `/${entity.collection}`;
|
|
35
35
|
// Server-side pagination state (only used when pagination='server')
|
|
@@ -54,8 +54,10 @@ export function EntityList({ entity, userRole = 'guest', basePath, onClick, hide
|
|
|
54
54
|
const data = listData?.items || [];
|
|
55
55
|
// Entity + crud namespaces so formatValue can resolve crud:price.* etc.
|
|
56
56
|
const { t } = useTranslation([entity.namespace, 'crud']);
|
|
57
|
-
//
|
|
58
|
-
const
|
|
57
|
+
// Get filters for applying to data (EntityFilters manages its own state)
|
|
58
|
+
const { filters } = useCrudFilters({
|
|
59
|
+
collection: entity.collection,
|
|
60
|
+
});
|
|
59
61
|
const [searchQuery, setSearchQuery] = useState('');
|
|
60
62
|
// Refresh handler - triggers manual refetch in useList
|
|
61
63
|
const handleRefresh = useCallback(async () => {
|
|
@@ -82,10 +84,6 @@ export function EntityList({ entity, userRole = 'guest', basePath, onClick, hide
|
|
|
82
84
|
const handleDelete = useCallback(async (itemId) => {
|
|
83
85
|
await deleteItem(itemId);
|
|
84
86
|
}, [deleteItem]);
|
|
85
|
-
// Update filters (for EntityFilters component)
|
|
86
|
-
const handleFiltersChange = useCallback((newFilters) => {
|
|
87
|
-
setFilters(newFilters);
|
|
88
|
-
}, []);
|
|
89
87
|
// Apply search and filters to data
|
|
90
88
|
// @todo Server-side filtering: Currently only handles client-side filtering.
|
|
91
89
|
// When pagination='server', filters should be sent to the server via useCrudList options.
|
|
@@ -170,9 +168,9 @@ export function EntityList({ entity, userRole = 'guest', basePath, onClick, hide
|
|
|
170
168
|
return (_jsxs(_Fragment, { children: [_jsx(Section, { title: tCrud('filters.title', {
|
|
171
169
|
entity: entityName,
|
|
172
170
|
defaultValue: `Browse ${entityName} - Filters`,
|
|
173
|
-
}), collapsible: true, defaultOpen: true, children: _jsxs(Stack, {
|
|
171
|
+
}), collapsible: true, defaultOpen: true, children: _jsxs(Stack, { children: [_jsxs(Stack, { direction: "row", gap: "tight", align: "center", className: "dndev-w-full", style: { display: 'grid', gridTemplateColumns: '1fr auto auto' }, children: [_jsx(Input, { placeholder: tCrud('search.placeholder', {
|
|
174
172
|
defaultValue: 'Search...',
|
|
175
|
-
}), value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), icon: Search, className: "dndev-w-full" }), _jsx(Button, { icon: RefreshCw, variant: "outline", onClick: handleRefresh, disabled: loading, display: "compact", "aria-label": tCrud('refresh', { defaultValue: 'Refresh' }) }), _jsx(Button, { icon: Plus, onClick: handleCreate, display: "compact", children: tCrud('addNew', { defaultValue: 'Add New' }) })] }), !hideFilters && (_jsx(EntityFilters, { entity: entity, data: data,
|
|
173
|
+
}), value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), icon: Search, className: "dndev-w-full" }), _jsx(Button, { icon: RefreshCw, variant: "outline", onClick: handleRefresh, disabled: loading, display: "compact", "aria-label": tCrud('refresh', { defaultValue: 'Refresh' }) }), _jsx(Button, { icon: Plus, onClick: handleCreate, display: "compact", children: tCrud('addNew', { defaultValue: 'Add New' }) })] }), !hideFilters && (_jsx(EntityFilters, { entity: entity, data: data, fieldsToFilter: entity.listFields }))] }) }), _jsx(Section, { title: loading
|
|
176
174
|
? tCrud('results.title.fetching', {
|
|
177
175
|
defaultValue: 'Fetching...',
|
|
178
176
|
})
|
|
@@ -197,5 +195,7 @@ export function EntityList({ entity, userRole = 'guest', basePath, onClick, hide
|
|
|
197
195
|
total: listData?.total,
|
|
198
196
|
onPageChange: setCurrentPage,
|
|
199
197
|
onPageSizeChange: setServerPageSize,
|
|
200
|
-
})
|
|
198
|
+
}),
|
|
199
|
+
// Export functionality
|
|
200
|
+
exportable: exportable, exportFilename: `${entity.collection}-export`, exportLabel: tCrud('export', { defaultValue: 'Export' }) }) })] }));
|
|
201
201
|
}
|
|
@@ -64,7 +64,7 @@ const FormItemContext = createContext({});
|
|
|
64
64
|
*/
|
|
65
65
|
const FormItem = ({ className, ...props }) => {
|
|
66
66
|
const id = useId();
|
|
67
|
-
return (_jsx(FormItemContext.Provider, { value: { id }, children: _jsx(Stack, {
|
|
67
|
+
return (_jsx(FormItemContext.Provider, { value: { id }, children: _jsx(Stack, { className: className, ...props }) }));
|
|
68
68
|
};
|
|
69
69
|
/**
|
|
70
70
|
* Hook for accessing form field context
|