@donotdev/ui 0.0.12 → 0.0.14
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/auth/AuthMenu.d.ts.map +1 -1
- package/dist/components/auth/AuthMenu.js +19 -20
- package/dist/components/common/FeatureCard.d.ts +3 -1
- package/dist/components/common/FeatureCard.d.ts.map +1 -1
- package/dist/components/common/FeatureCard.js +3 -3
- package/dist/components/common/ProgressBar.js +2 -2
- package/dist/components/common/RedirectOverlay.js +1 -1
- package/dist/components/common/TechBento.d.ts +14 -2
- package/dist/components/common/TechBento.d.ts.map +1 -1
- package/dist/components/common/TechBento.js +8 -9
- package/dist/components/cookie-consent/CookieConsent.d.ts.map +1 -1
- package/dist/components/cookie-consent/CookieConsent.js +6 -7
- package/dist/components/layout/GameContainer.d.ts +24 -8
- package/dist/components/layout/GameContainer.d.ts.map +1 -1
- package/dist/components/layout/GameContainer.js +21 -3
- package/dist/components/layout/GameFlow.d.ts.map +1 -1
- package/dist/components/layout/GameFlow.js +27 -11
- package/dist/components/layout/components/DropdownNavigation.d.ts.map +1 -1
- package/dist/components/layout/components/DropdownNavigation.js +3 -12
- package/dist/components/layout/components/FloatingLanguageSwitcher.js +1 -1
- package/dist/components/layout/components/Notifications.d.ts +1 -3
- package/dist/components/layout/components/Notifications.d.ts.map +1 -1
- package/dist/components/layout/components/Notifications.js +4 -2
- package/dist/components/layout/components/header/AppBranding.d.ts.map +1 -1
- package/dist/components/layout/components/header/AppBranding.js +2 -1
- package/dist/components/layout/components/header/AppIcon.d.ts.map +1 -1
- package/dist/components/layout/components/header/AppIcon.js +5 -2
- package/dist/components/layout/components/header/CacheSettings.d.ts.map +1 -1
- package/dist/components/layout/components/header/CacheSettings.js +4 -2
- package/dist/components/layout/components/header/HeaderNavigation.d.ts +6 -0
- package/dist/components/layout/components/header/HeaderNavigation.d.ts.map +1 -1
- package/dist/components/layout/components/header/HeaderNavigation.js +12 -2
- package/dist/components/license/LicenseWatermark.d.ts.map +1 -1
- package/dist/components/license/LicenseWatermark.js +3 -1
- package/dist/crud/components/CrudCardLink.d.ts +17 -0
- package/dist/crud/components/CrudCardLink.d.ts.map +1 -0
- package/dist/crud/components/CrudCardLink.js +17 -0
- package/dist/crud/components/EntityCardList.d.ts.map +1 -1
- package/dist/crud/components/EntityCardList.js +32 -81
- package/dist/crud/components/EntityDisplayRenderer.d.ts +1 -1
- package/dist/crud/components/EntityDisplayRenderer.d.ts.map +1 -1
- package/dist/crud/components/EntityDisplayRenderer.js +8 -4
- package/dist/crud/components/EntityFormRenderer.d.ts +1 -1
- package/dist/crud/components/EntityFormRenderer.d.ts.map +1 -1
- package/dist/crud/components/EntityFormRenderer.js +29 -18
- 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 +8 -10
- package/dist/crud/components/EntityRecommendations.d.ts +28 -0
- package/dist/crud/components/EntityRecommendations.d.ts.map +1 -0
- package/dist/crud/components/EntityRecommendations.js +31 -0
- package/dist/crud/components/Form.js +1 -1
- package/dist/crud/components/index.d.ts +2 -1
- package/dist/crud/components/index.d.ts.map +1 -1
- package/dist/crud/components/index.js +1 -0
- package/dist/index.js +4 -4
- package/dist/internal/common/RouteErrorFallback.d.ts.map +1 -1
- package/dist/internal/common/RouteErrorFallback.js +3 -3
- package/dist/internal/devtools/components/AuthDebugButton.js +1 -1
- package/dist/internal/devtools/components/ConfigTab.js +1 -1
- package/dist/internal/devtools/components/CookieTab.js +1 -1
- package/dist/internal/devtools/components/DesignTab.d.ts.map +1 -1
- package/dist/internal/devtools/components/DesignTab.js +5 -4
- package/dist/internal/devtools/components/LayoutReset.d.ts.map +1 -1
- package/dist/internal/devtools/components/LayoutReset.js +2 -0
- package/dist/internal/devtools/components/StoresTab.d.ts.map +1 -1
- package/dist/internal/devtools/components/StoresTab.js +5 -2
- package/dist/internal/devtools/utils/envVarDiscovery.d.ts +1 -0
- package/dist/internal/devtools/utils/envVarDiscovery.d.ts.map +1 -1
- package/dist/internal/devtools/utils/envVarDiscovery.js +5 -0
- package/dist/internal/devtools/utils/virtualModuleInspector.d.ts.map +1 -1
- package/dist/internal/devtools/utils/virtualModuleInspector.js +27 -21
- package/dist/internal/initializers/BaseStoresInitializer.d.ts.map +1 -1
- package/dist/internal/initializers/BaseStoresInitializer.js +30 -6
- package/dist/internal/layout/components/AutoMetaTags.d.ts.map +1 -1
- package/dist/internal/layout/components/AutoMetaTags.js +10 -8
- package/dist/internal/layout/components/FontPreloadLinks.d.ts +16 -0
- package/dist/internal/layout/components/FontPreloadLinks.d.ts.map +1 -0
- package/dist/internal/layout/components/FontPreloadLinks.js +32 -0
- package/dist/internal/layout/components/PerformanceHints.d.ts +7 -12
- package/dist/internal/layout/components/PerformanceHints.d.ts.map +1 -1
- package/dist/internal/layout/components/PerformanceHints.js +8 -12
- package/dist/internal/layout/components/footer/useLegalLinks.d.ts +6 -5
- package/dist/internal/layout/components/footer/useLegalLinks.d.ts.map +1 -1
- package/dist/internal/layout/components/footer/useLegalLinks.js +6 -2
- package/dist/internal/layout/zones/DnDevFooter.d.ts +6 -0
- package/dist/internal/layout/zones/DnDevFooter.d.ts.map +1 -1
- package/dist/internal/layout/zones/DnDevFooter.js +10 -4
- package/dist/internal/layout/zones/DnDevHeader.d.ts +7 -0
- package/dist/internal/layout/zones/DnDevHeader.d.ts.map +1 -1
- package/dist/internal/layout/zones/DnDevHeader.js +7 -0
- package/dist/internal/layout/zones/DnDevMergedBar.d.ts +7 -0
- package/dist/internal/layout/zones/DnDevMergedBar.d.ts.map +1 -1
- package/dist/internal/layout/zones/DnDevMergedBar.js +10 -1
- package/dist/internal/layout/zones/DnDevSidebar.d.ts +4 -0
- package/dist/internal/layout/zones/DnDevSidebar.d.ts.map +1 -1
- package/dist/internal/layout/zones/DnDevSidebar.js +13 -1
- package/dist/next.d.ts +1 -0
- package/dist/next.d.ts.map +1 -1
- package/dist/next.js +1 -0
- package/dist/routing/404.js +3 -3
- package/dist/routing/AuthGuard.d.ts +1 -1
- package/dist/routing/AuthGuard.d.ts.map +1 -1
- package/dist/routing/AuthGuard.js +3 -1
- package/dist/routing/AuthGuardFallback.js +2 -2
- package/dist/routing/GoTo.d.ts.map +1 -1
- package/dist/routing/GoTo.js +3 -1
- package/dist/routing/GoToDialog.d.ts.map +1 -1
- package/dist/routing/GoToDialog.js +2 -7
- package/dist/routing/GoToInput.d.ts +0 -3
- package/dist/routing/GoToInput.d.ts.map +1 -1
- package/dist/routing/GoToInput.js +4 -2
- package/dist/routing/Link.js +1 -1
- package/dist/routing/NavigationItem.d.ts +29 -7
- package/dist/routing/NavigationItem.d.ts.map +1 -1
- package/dist/routing/NavigationItem.js +22 -6
- package/dist/routing/hooks/hooks.next.js +1 -1
- package/dist/routing/hooks/hooks.vite.js +1 -1
- package/dist/routing/hooks/useRedirectGuard.next.d.ts.map +1 -1
- package/dist/routing/hooks/useRedirectGuard.next.js +9 -8
- package/dist/routing/hooks/useRedirectGuard.vite.d.ts.map +1 -1
- package/dist/routing/hooks/useRedirectGuard.vite.js +9 -8
- package/dist/routing/hooks/useSearchParams.next.d.ts +18 -1
- package/dist/routing/hooks/useSearchParams.next.d.ts.map +1 -1
- package/dist/routing/hooks/useSearchParams.next.js +16 -0
- package/dist/routing/hooks/useSearchParams.vite.d.ts +16 -0
- package/dist/routing/hooks/useSearchParams.vite.d.ts.map +1 -1
- package/dist/routing/hooks/useSearchParams.vite.js +17 -1
- package/dist/routing/index.d.ts.map +1 -1
- package/dist/routing/index.js +2 -0
- package/dist/routing/useNavigation.d.ts +30 -0
- package/dist/routing/useNavigation.d.ts.map +1 -1
- package/dist/routing/useNavigation.js +40 -3
- package/dist/routing/useRouteDiscovery.d.ts +2 -2
- package/dist/routing/useRouteDiscovery.d.ts.map +1 -1
- package/dist/routing/useRouteDiscovery.js +10 -4
- package/dist/styles/index.css +366 -120
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/sanitizeSvg.d.ts +13 -0
- package/dist/utils/sanitizeSvg.d.ts.map +1 -0
- package/dist/utils/sanitizeSvg.js +47 -0
- package/dist/utils/useBillingVisibility.d.ts.map +1 -1
- package/dist/utils/useBillingVisibility.js +0 -7
- package/dist/utils/useCrudSafe.d.ts +0 -2
- package/dist/utils/useCrudSafe.d.ts.map +1 -1
- package/dist/utils/useFormStoreSafe.d.ts +3 -1
- package/dist/utils/useFormStoreSafe.d.ts.map +1 -1
- package/dist/utils/useFormStoreSafe.js +4 -5
- package/dist/vite-routing/AppRoutes.d.ts +19 -8
- package/dist/vite-routing/AppRoutes.d.ts.map +1 -1
- package/dist/vite-routing/AppRoutes.js +0 -3
- package/package.json +15 -11
- package/assets/fonts/fonts.css +0 -206
- package/dist/dndev.css +0 -10673
- package/dist/routing/Navigate.d.ts +0 -10
- package/dist/routing/Navigate.d.ts.map +0 -1
- package/dist/routing/Navigate.js +0 -10
|
@@ -97,7 +97,9 @@ function CacheSettings() {
|
|
|
97
97
|
tx.executeSql('SELECT name FROM sqlite_master WHERE type="table"', [], (tx, results) => {
|
|
98
98
|
for (let i = 0; i < results.rows.length; i++) {
|
|
99
99
|
const tableName = results.rows.item(i).name;
|
|
100
|
-
|
|
100
|
+
if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
|
|
101
|
+
tx.executeSql(`DROP TABLE IF EXISTS ${tableName}`);
|
|
102
|
+
}
|
|
101
103
|
}
|
|
102
104
|
});
|
|
103
105
|
});
|
|
@@ -205,7 +207,7 @@ function CacheSettings() {
|
|
|
205
207
|
setIsClearing(false);
|
|
206
208
|
}
|
|
207
209
|
};
|
|
208
|
-
return (_jsxs(Stack, {
|
|
210
|
+
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
211
|
fontSize: 'var(--font-size-sm)',
|
|
210
212
|
fontWeight: 500,
|
|
211
213
|
lineHeight: 1,
|
|
@@ -13,6 +13,12 @@ export interface HeaderNavigationProps {
|
|
|
13
13
|
* @default 'auto'
|
|
14
14
|
*/
|
|
15
15
|
display?: (typeof DISPLAY)[keyof typeof DISPLAY];
|
|
16
|
+
/**
|
|
17
|
+
* Single-item mode: render one route as a standalone Button.
|
|
18
|
+
* Resolves label, icon, and active state from the navigation store.
|
|
19
|
+
* Renders null if the path is not found or inaccessible.
|
|
20
|
+
*/
|
|
21
|
+
path?: string;
|
|
16
22
|
}
|
|
17
23
|
/**
|
|
18
24
|
* HeaderNavigation - DISPLAY-aware adaptive navigation component
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HeaderNavigation.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/components/header/HeaderNavigation.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HeaderNavigation.d.ts","sourceRoot":"","sources":["../../../../../src/components/layout/components/header/HeaderNavigation.tsx"],"names":[],"mappings":"AAsBA,OAAO,EACL,OAAO,EAIR,MAAM,sBAAsB,CAAC;AAU9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C,MAAM,WAAW,qBAAqB;IACpC,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,OAAO,OAAO,CAAC,CAAC;IACjD;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;GAWG;AACH,QAAA,MAAM,gBAAgB,EAAE,aAAa,CAAC,qBAAqB,CAwE1D,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
|
|
@@ -16,12 +16,15 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
16
16
|
* @author AMBROISE PARK Consulting
|
|
17
17
|
*/
|
|
18
18
|
import { MoreHorizontal } from 'lucide-react';
|
|
19
|
+
import { Link as LinkIcon } from 'lucide-react';
|
|
19
20
|
import { DISPLAY, DropdownMenu, Button, } from '@donotdev/components';
|
|
20
21
|
import { cn } from '@donotdev/components';
|
|
21
22
|
import { useBreakpoint, useTranslation } from '@donotdev/core';
|
|
23
|
+
import { Link } from '../../../../routing/Link';
|
|
24
|
+
import { Icon } from '../../../common/icon';
|
|
22
25
|
import { DnDevNavigationMenu } from '../../../../routing/DnDevNavigationMenu';
|
|
23
26
|
import { NavigationItemComponent } from '../../../../routing/NavigationItem';
|
|
24
|
-
import { useNavigationItems } from '../../../../routing/useNavigation';
|
|
27
|
+
import { useNavigationItems, useNavigationRoute } from '../../../../routing/useNavigation';
|
|
25
28
|
/**
|
|
26
29
|
* HeaderNavigation - DISPLAY-aware adaptive navigation component
|
|
27
30
|
*
|
|
@@ -34,11 +37,18 @@ import { useNavigationItems } from '../../../../routing/useNavigation';
|
|
|
34
37
|
* @since 0.0.1
|
|
35
38
|
* @author AMBROISE PARK Consulting
|
|
36
39
|
*/
|
|
37
|
-
const HeaderNavigation = ({ className = '', showIcons = true, display = DISPLAY.AUTO, }) => {
|
|
40
|
+
const HeaderNavigation = ({ className = '', showIcons = true, display = DISPLAY.AUTO, path, }) => {
|
|
38
41
|
const { t } = useTranslation('dndev');
|
|
39
42
|
const isMobile = useBreakpoint('isMobile');
|
|
40
43
|
const isTablet = useBreakpoint('isTablet');
|
|
41
44
|
const navigationItems = useNavigationItems();
|
|
45
|
+
const singleRoute = useNavigationRoute(path ?? '');
|
|
46
|
+
// Single-item mode — render one route as a Button link.
|
|
47
|
+
if (path) {
|
|
48
|
+
if (!singleRoute)
|
|
49
|
+
return null;
|
|
50
|
+
return (_jsx(Button, { variant: "ghost", display: display, icon: showIcons ? _jsx(Icon, { icon: singleRoute.icon, fallback: LinkIcon }) : undefined, className: className, render: ({ children, ...props }) => (_jsx(Link, { path: singleRoute.path, ...props, children: children })), children: singleRoute.label }));
|
|
51
|
+
}
|
|
42
52
|
const effectiveDisplay = display === DISPLAY.AUTO
|
|
43
53
|
? isMobile || isTablet
|
|
44
54
|
? DISPLAY.COMPACT
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LicenseWatermark.d.ts","sourceRoot":"","sources":["../../../src/components/license/LicenseWatermark.tsx"],"names":[],"mappings":"AAsBA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,gBAAgB,
|
|
1
|
+
{"version":3,"file":"LicenseWatermark.d.ts","sourceRoot":"","sources":["../../../src/components/license/LicenseWatermark.tsx"],"names":[],"mappings":"AAsBA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,gBAAgB,mDAwG/B"}
|
|
@@ -41,6 +41,8 @@ import { watermarkSvg } from './watermarkData';
|
|
|
41
41
|
*/
|
|
42
42
|
export function LicenseWatermark() {
|
|
43
43
|
const license = checkLicense();
|
|
44
|
+
/** Production console.warn is intentional — alerts unlicensed deployments.
|
|
45
|
+
* This is a license enforcement mechanism, not a debug log. */
|
|
44
46
|
useEffect(() => {
|
|
45
47
|
if (!license.isValid) {
|
|
46
48
|
// Console warnings
|
|
@@ -61,7 +63,7 @@ export function LicenseWatermark() {
|
|
|
61
63
|
}
|
|
62
64
|
return (_jsxs("a", { href: "https://donotdev.com/pricing", target: "_blank", rel: "noopener noreferrer", style: {
|
|
63
65
|
position: 'fixed',
|
|
64
|
-
|
|
66
|
+
insetInlineEnd: '20px',
|
|
65
67
|
top: '50%',
|
|
66
68
|
transform: 'translateY(-50%)',
|
|
67
69
|
zIndex: 9999,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview UI CrudCard wrapper
|
|
3
|
+
* @description Thin wrapper around @donotdev/crud CrudCard that adds Link routing.
|
|
4
|
+
* CrudCard (crud) is platform-agnostic; this wrapper adds web navigation via Link.
|
|
5
|
+
*
|
|
6
|
+
* @version 0.2.0
|
|
7
|
+
* @since 0.0.1
|
|
8
|
+
* @author AMBROISE PARK Consulting
|
|
9
|
+
*/
|
|
10
|
+
import type { CrudCardProps } from '@donotdev/core';
|
|
11
|
+
/**
|
|
12
|
+
* CrudCard with Link wrapping for web navigation.
|
|
13
|
+
* When detailHref is provided, wraps the card in a Link for a11y/SEO.
|
|
14
|
+
*/
|
|
15
|
+
export declare function CrudCard({ detailHref, onClick, ...rest }: CrudCardProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export default CrudCard;
|
|
17
|
+
//# sourceMappingURL=CrudCardLink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CrudCardLink.d.ts","sourceRoot":"","sources":["../../../src/crud/components/CrudCardLink.tsx"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAIpD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,EACvB,UAAU,EACV,OAAO,EACP,GAAG,IAAI,EACR,EAAE,aAAa,2CAqBf;AAED,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { CrudCard as BaseCrudCard } from '@donotdev/crud';
|
|
4
|
+
import { Link } from '../../routing';
|
|
5
|
+
/**
|
|
6
|
+
* CrudCard with Link wrapping for web navigation.
|
|
7
|
+
* When detailHref is provided, wraps the card in a Link for a11y/SEO.
|
|
8
|
+
*/
|
|
9
|
+
export function CrudCard({ detailHref, onClick, ...rest }) {
|
|
10
|
+
// When detailHref is set, Link handles navigation — don't pass onClick to inner card
|
|
11
|
+
const card = (_jsx(BaseCrudCard, { ...rest, onClick: detailHref ? undefined : onClick }));
|
|
12
|
+
if (detailHref) {
|
|
13
|
+
return (_jsx(Link, { path: detailHref, "aria-label": rest.item?.id ? `View ${rest.item.id}` : undefined, children: card }));
|
|
14
|
+
}
|
|
15
|
+
return card;
|
|
16
|
+
}
|
|
17
|
+
export default CrudCard;
|
|
@@ -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":"AAsCA,OAAO,KAAK,EAAE,mBAAmB,EAAgB,MAAM,gBAAgB,CAAC;AAIxE,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,2CAqMrB"}
|
|
@@ -13,12 +13,13 @@ 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
|
-
import { Grid,
|
|
19
|
-
import { useTranslation } from '@donotdev/core';
|
|
18
|
+
import { Grid, Stack, Text, Spinner, Section, Button, } from '@donotdev/components';
|
|
19
|
+
import { useTranslation, getListCardFieldNames } from '@donotdev/core';
|
|
20
20
|
import { useNavigate } from '../../routing';
|
|
21
|
-
import {
|
|
21
|
+
import { useCrudCardList, EntityFilters, useEntityFavorites, matchesFilter, useCrudFilters, } from '@donotdev/crud';
|
|
22
|
+
import { CrudCard } from './CrudCardLink';
|
|
22
23
|
/**
|
|
23
24
|
* Entity Card List Component - Card grid view for public/user-facing browsing
|
|
24
25
|
*
|
|
@@ -41,9 +42,11 @@ filter, hideFilters = false, }) {
|
|
|
41
42
|
const { isFavorite, toggleFavorite, favoritesFilter } = useEntityFavorites({
|
|
42
43
|
collection: entity.collection,
|
|
43
44
|
});
|
|
44
|
-
|
|
45
|
-
//
|
|
46
|
-
const
|
|
45
|
+
// Favorites toggle from CrudStore (persists across navigation)
|
|
46
|
+
// Note: EntityFilters now manages its own filters internally via useCrudFilters
|
|
47
|
+
const { showFavoritesOnly, setShowFavoritesOnly, filters } = useCrudFilters({
|
|
48
|
+
collection: entity.collection,
|
|
49
|
+
});
|
|
47
50
|
// Apply filters from EntityFilters component
|
|
48
51
|
const applyFilters = useCallback((item) => {
|
|
49
52
|
if (Object.keys(filters).length === 0)
|
|
@@ -80,96 +83,44 @@ filter, hideFilters = false, }) {
|
|
|
80
83
|
navigate(`${base}/${id}`);
|
|
81
84
|
}
|
|
82
85
|
}, [base, navigate, onClick]);
|
|
83
|
-
//
|
|
84
|
-
const
|
|
85
|
-
const cardFields = entity.listCardFields ?? entity.listFields;
|
|
86
|
-
if (cardFields && cardFields.length > 0)
|
|
87
|
-
return cardFields;
|
|
88
|
-
return Object.keys(entity.fields).slice(0, 4);
|
|
89
|
-
}, [entity.listCardFields, entity.listFields, entity.fields]);
|
|
90
|
-
// Find image field
|
|
91
|
-
const imageField = useMemo(() => {
|
|
92
|
-
const imageFieldsInList = fieldsToShow.filter((fieldName) => {
|
|
93
|
-
const fieldConfig = entity.fields[fieldName];
|
|
94
|
-
return fieldConfig?.type === 'image' || fieldConfig?.type === 'images';
|
|
95
|
-
});
|
|
96
|
-
if (imageFieldsInList.length > 0)
|
|
97
|
-
return imageFieldsInList[0];
|
|
98
|
-
// Fallback: search all entity fields
|
|
99
|
-
const allImageFields = Object.keys(entity.fields).filter((fieldName) => {
|
|
100
|
-
const fieldConfig = entity.fields[fieldName];
|
|
101
|
-
return fieldConfig?.type === 'image' || fieldConfig?.type === 'images';
|
|
102
|
-
});
|
|
103
|
-
return allImageFields[0] || null;
|
|
104
|
-
}, [fieldsToShow, entity.fields]);
|
|
105
|
-
// Get other fields (non-image)
|
|
106
|
-
const otherFields = useMemo(() => {
|
|
107
|
-
return fieldsToShow.filter((fieldName) => fieldName !== imageField);
|
|
108
|
-
}, [fieldsToShow, imageField]);
|
|
86
|
+
// Flat field names for filters (works with both string[] and ListCardLayout)
|
|
87
|
+
const fieldsToFilter = useMemo(() => getListCardFieldNames(entity), [entity]);
|
|
109
88
|
const entityName = t('name', { defaultValue: entity.name });
|
|
110
89
|
return (_jsxs(_Fragment, { children: [!hideFilters && (_jsx(Section, { title: tCrud('filters.title', {
|
|
111
90
|
entity: entityName,
|
|
112
91
|
defaultValue: `Browse ${entityName} - Filters`,
|
|
113
|
-
}), collapsible: true, defaultOpen: true, children: _jsxs(Stack, { direction: "column",
|
|
92
|
+
}), 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
93
|
? tCrud('favorites.showAll', { defaultValue: 'Show All' })
|
|
115
94
|
: tCrud('favorites.showFavorites', {
|
|
116
95
|
defaultValue: 'Show Favorites',
|
|
117
|
-
}) }), _jsx(EntityFilters, { entity: entity, data: rawData,
|
|
96
|
+
}) }), _jsx(EntityFilters, { entity: entity, data: rawData, fieldsToFilter: fieldsToFilter })] }) })), _jsx(Section, { title: loading
|
|
118
97
|
? tCrud('results.title.fetching', { defaultValue: 'Fetching...' })
|
|
119
98
|
: tCrud('results.title.count', {
|
|
120
99
|
count: data.length,
|
|
121
100
|
defaultValue: data.length === 1
|
|
122
101
|
? 'Found 1 occurrence'
|
|
123
102
|
: `Found ${data.length} occurrences`,
|
|
124
|
-
}), collapsible: true, defaultOpen: true, children: loading ? (_jsx(Stack, { align: "center", justify: "center",
|
|
103
|
+
}), 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
104
|
defaultValue: `No ${entity.name.toLowerCase()} found`,
|
|
126
105
|
}) }), _jsx(Text, { style: { color: 'var(--muted-foreground)' }, children: tCrud('emptyState.description', {
|
|
127
106
|
defaultValue: `No ${entity.name.toLowerCase()} available at this time.`,
|
|
128
|
-
}) })] })) : (_jsx(Grid, { cols: cols,
|
|
129
|
-
const imageValue = imageField ? item[imageField] : null;
|
|
130
|
-
// Backend optimizes picture fields for listCard: returns thumbUrl string directly
|
|
131
|
-
// (or fullUrl if thumbUrl missing, or null if no picture)
|
|
132
|
-
const imageUrl = typeof imageValue === 'string' ? imageValue : null;
|
|
133
|
-
// Title from first non-image field
|
|
134
|
-
const titleField = otherFields[0];
|
|
135
|
-
const titleValue = titleField ? item[titleField] : item.id;
|
|
107
|
+
}) })] })) : (_jsx(Grid, { cols: cols, children: data.map((item) => {
|
|
136
108
|
const itemIsFavorite = isFavorite(item.id);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
: tCrud('favorites.add', {
|
|
154
|
-
defaultValue: 'Add to favorites',
|
|
155
|
-
}) }), _jsxs(Stack, { direction: "column", gap: "medium", children: [imageUrl && (_jsx("div", { style: {
|
|
156
|
-
width: '100%',
|
|
157
|
-
aspectRatio: '16/9',
|
|
158
|
-
borderRadius: 'var(--radius-md)',
|
|
159
|
-
overflow: 'hidden',
|
|
160
|
-
backgroundColor: 'var(--muted)',
|
|
161
|
-
position: 'relative',
|
|
162
|
-
}, children: _jsx("img", { src: imageUrl, alt: String(titleValue || ''), style: {
|
|
163
|
-
width: '100%',
|
|
164
|
-
height: '100%',
|
|
165
|
-
objectFit: 'cover',
|
|
166
|
-
} }) })), _jsx(Stack, { direction: "column", gap: "tight", children: otherFields.slice(1, 4).map((fieldName) => {
|
|
167
|
-
const fieldConfig = entity.fields[fieldName];
|
|
168
|
-
if (!fieldConfig)
|
|
169
|
-
return null;
|
|
170
|
-
return (_jsxs("div", { children: [_jsx(Text, { level: "small", variant: "muted", children: translateFieldLabel(fieldName, fieldConfig, t) }), _jsx(Text, { children: formatValue(item[fieldName], fieldConfig, t, {
|
|
171
|
-
compact: true,
|
|
172
|
-
}) })] }, fieldName));
|
|
173
|
-
}) })] })] }, item.id));
|
|
109
|
+
const detailHref = onClick ? undefined : `${base}/${item.id}`;
|
|
110
|
+
return (_jsx(CrudCard, { item: item, entity: entity, detailHref: detailHref, onClick: onClick ? () => handleView(item.id) : undefined, renderActions: _jsx(Heart, { fill: itemIsFavorite ? '#ef4444' : '#ffffff', stroke: itemIsFavorite ? '#ef4444' : 'var(--muted-foreground)', onClick: (e) => {
|
|
111
|
+
e.stopPropagation();
|
|
112
|
+
toggleFavorite(item.id);
|
|
113
|
+
}, style: {
|
|
114
|
+
cursor: 'pointer',
|
|
115
|
+
width: 'var(--icon-md)',
|
|
116
|
+
height: 'var(--icon-md)',
|
|
117
|
+
transition: 'fill 0.2s, stroke 0.2s',
|
|
118
|
+
}, "aria-label": itemIsFavorite
|
|
119
|
+
? tCrud('favorites.remove', {
|
|
120
|
+
defaultValue: 'Remove from favorites',
|
|
121
|
+
})
|
|
122
|
+
: tCrud('favorites.add', {
|
|
123
|
+
defaultValue: 'Add to favorites',
|
|
124
|
+
}) }) }, item.id));
|
|
174
125
|
}) })) })] }));
|
|
175
126
|
}
|
|
@@ -16,6 +16,6 @@ export type { EntityDisplayRendererProps };
|
|
|
16
16
|
* <EntityDisplayRenderer entity={carEntity} id={carId} />
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
-
export declare function EntityDisplayRenderer<T extends EntityRecord = EntityRecord>({ entity, id, t, className,
|
|
19
|
+
export declare function EntityDisplayRenderer<T extends EntityRecord = EntityRecord>({ entity, id, t, className, loadingMessage, notFoundMessage, viewerRole: viewerRoleProp, }: EntityDisplayRendererProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
20
20
|
export default EntityDisplayRenderer;
|
|
21
21
|
//# sourceMappingURL=EntityDisplayRenderer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityDisplayRenderer.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityDisplayRenderer.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,0BAA0B,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"EntityDisplayRenderer.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityDisplayRenderer.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,0BAA0B,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAI/E,YAAY,EAAE,0BAA0B,EAAE,CAAC;AAE3C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC3E,MAAM,EACN,EAAE,EACF,CAAC,EACD,SAAc,EACd,cAAc,EACd,eAAe,EACf,UAAU,EAAE,cAAc,GAC3B,EAAE,0BAA0B,CAAC,CAAC,CAAC,2CAkM/B;AAED,eAAe,qBAAqB,CAAC"}
|
|
@@ -14,6 +14,7 @@ import { useEffect, useState, useMemo } from 'react';
|
|
|
14
14
|
import { Stack, Spinner } from '@donotdev/components';
|
|
15
15
|
import { useTranslation, isFieldVisible } from '@donotdev/core';
|
|
16
16
|
import { useCrud, DisplayFieldRenderer } from '@donotdev/crud';
|
|
17
|
+
import { useAuthSafe } from '../../utils/useAuthSafe';
|
|
17
18
|
/**
|
|
18
19
|
* EntityDisplayRenderer - Automatically fetches and displays entity data
|
|
19
20
|
*
|
|
@@ -30,8 +31,11 @@ import { useCrud, DisplayFieldRenderer } from '@donotdev/crud';
|
|
|
30
31
|
* <EntityDisplayRenderer entity={carEntity} id={carId} />
|
|
31
32
|
* ```
|
|
32
33
|
*/
|
|
33
|
-
export function EntityDisplayRenderer({ entity, id, t, className = '',
|
|
34
|
-
|
|
34
|
+
export function EntityDisplayRenderer({ entity, id, t, className = '', loadingMessage, notFoundMessage, viewerRole: viewerRoleProp, }) {
|
|
35
|
+
// Auto-detect role from auth; prop overrides
|
|
36
|
+
const authRole = useAuthSafe('userRole');
|
|
37
|
+
const viewerRole = viewerRoleProp ?? authRole;
|
|
38
|
+
const { get, loading, data: storeData, error: storeError, isAvailable, } = useCrud(entity);
|
|
35
39
|
const [isFetching, setIsFetching] = useState(false);
|
|
36
40
|
const [fetchError, setFetchError] = useState(null);
|
|
37
41
|
const [data, setData] = useState(null);
|
|
@@ -135,7 +139,7 @@ export function EntityDisplayRenderer({ entity, id, t, className = '', backend =
|
|
|
135
139
|
}
|
|
136
140
|
// Error or not found state
|
|
137
141
|
if (displayError || !displayData) {
|
|
138
|
-
return (_jsx(Stack, { align: "center", justify: "center",
|
|
142
|
+
return (_jsx(Stack, { align: "center", justify: "center", style: {
|
|
139
143
|
padding: 'var(--gap-3xl)',
|
|
140
144
|
textAlign: 'center',
|
|
141
145
|
}, className: className, children: _jsxs(Stack, { direction: "column", gap: "tight", children: [_jsx("h3", { style: { color: 'var(--muted-foreground)' }, children: notFoundMessage ||
|
|
@@ -149,7 +153,7 @@ export function EntityDisplayRenderer({ entity, id, t, className = '', backend =
|
|
|
149
153
|
: String(displayError) }))] }) }));
|
|
150
154
|
}
|
|
151
155
|
// Render all visible fields with values
|
|
152
|
-
return (_jsx(Stack, { direction: "column",
|
|
156
|
+
return (_jsx(Stack, { direction: "column", className: className, children: visibleFields.map(([fieldName, fieldConfig]) => {
|
|
153
157
|
return (_jsx(DisplayFieldRenderer, { name: fieldName, config: fieldConfig, value: displayData[fieldName], t: translate }, fieldName));
|
|
154
158
|
}) }));
|
|
155
159
|
}
|
|
@@ -13,6 +13,6 @@ export type { EntityFormRendererProps };
|
|
|
13
13
|
* @since 0.0.1
|
|
14
14
|
* @author AMBROISE PARK Consulting
|
|
15
15
|
*/
|
|
16
|
-
export declare function EntityFormRenderer<T extends EntityRecord = EntityRecord>({ entity, onSubmit, t, className, submitText, loading, defaultValues, submitVariant, secondaryButtonText, secondaryButtonVariant, onSecondarySubmit, viewerRole, operation, autoSave, formId: externalFormId, cancelText, cancelPath, successPath, onCancel, warnOnUnsavedChanges, unsavedChangesMessage, hideVisibilityInfo, }: EntityFormRendererProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export declare function EntityFormRenderer<T extends EntityRecord = EntityRecord>({ entity, onSubmit, t, className, submitText, loading, defaultValues, submitVariant, secondaryButtonText, secondaryButtonVariant, onSecondarySubmit, viewerRole: viewerRoleProp, operation, autoSave, formId: externalFormId, cancelText, cancelPath, successPath, onCancel, warnOnUnsavedChanges, unsavedChangesMessage, hideVisibilityInfo, }: EntityFormRendererProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
17
17
|
export default EntityFormRenderer;
|
|
18
18
|
//# sourceMappingURL=EntityFormRenderer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityFormRenderer.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityFormRenderer.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"EntityFormRenderer.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityFormRenderer.tsx"],"names":[],"mappings":"AAuCA,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,EAAE,cAAc,EAC1B,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,2CA4c5B;AAED,eAAe,kBAAkB,CAAC"}
|
|
@@ -16,6 +16,7 @@ import { Badge, Button, DropdownMenu, Grid, Stack, Spinner, } from '@donotdev/co
|
|
|
16
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
|
+
import { useAuthSafe } from '../../utils/useAuthSafe';
|
|
19
20
|
/**
|
|
20
21
|
* EntityFormRenderer - Dumb component that renders a form from entity definition.
|
|
21
22
|
*
|
|
@@ -29,8 +30,11 @@ import { DisplayFieldRenderer, FormFieldRenderer, UploadProvider, useEntityForm,
|
|
|
29
30
|
* @since 0.0.1
|
|
30
31
|
* @author AMBROISE PARK Consulting
|
|
31
32
|
*/
|
|
32
|
-
export function EntityFormRenderer({ entity, onSubmit, t, className = '', submitText, loading = false, defaultValues, submitVariant = 'primary', secondaryButtonText, secondaryButtonVariant = 'outline', onSecondarySubmit, viewerRole, operation = defaultValues ? 'edit' : 'create', autoSave = operation === 'create', formId: externalFormId, cancelText, cancelPath, successPath, onCancel, warnOnUnsavedChanges = true, unsavedChangesMessage, hideVisibilityInfo = false, }) {
|
|
33
|
+
export function EntityFormRenderer({ entity, onSubmit, t, className = '', submitText, loading = false, defaultValues, submitVariant = 'primary', secondaryButtonText, secondaryButtonVariant = 'outline', onSecondarySubmit, viewerRole: viewerRoleProp, operation = defaultValues ? 'edit' : 'create', autoSave = operation === 'create', formId: externalFormId, cancelText, cancelPath, successPath, onCancel, warnOnUnsavedChanges = true, unsavedChangesMessage, hideVisibilityInfo = false, }) {
|
|
33
34
|
const navigate = useNavigate();
|
|
35
|
+
// Auto-detect role from auth; prop overrides (e.g. View-As preview)
|
|
36
|
+
const authRole = useAuthSafe('userRole');
|
|
37
|
+
const viewerRole = viewerRoleProp ?? authRole;
|
|
34
38
|
// Generate stable form ID
|
|
35
39
|
const generatedFormId = useId();
|
|
36
40
|
const formId = externalFormId ?? `entity-form-${entity.name}-${generatedFormId}`;
|
|
@@ -101,12 +105,14 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
|
|
|
101
105
|
return fieldRoleIndex <= previewRoleIndex;
|
|
102
106
|
});
|
|
103
107
|
}, [rawFields, canPreviewRole, previewRole]);
|
|
108
|
+
// Subscribe to setIsDirty action via React-compatible selector
|
|
109
|
+
const setFormIsDirty = useFormStore((state) => state.setIsDirty);
|
|
104
110
|
// Sync isDirty to FormStore (single source of truth)
|
|
105
111
|
useEffect(() => {
|
|
106
112
|
if (formId) {
|
|
107
|
-
|
|
113
|
+
setFormIsDirty(formId, isDirtyForBlocking);
|
|
108
114
|
}
|
|
109
|
-
}, [formId, isDirtyForBlocking]);
|
|
115
|
+
}, [formId, isDirtyForBlocking, setFormIsDirty]);
|
|
110
116
|
// Cleanup on unmount
|
|
111
117
|
useEffect(() => {
|
|
112
118
|
return cleanup;
|
|
@@ -151,22 +157,27 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
|
|
|
151
157
|
}
|
|
152
158
|
}
|
|
153
159
|
};
|
|
154
|
-
// Wrap onSubmit to
|
|
160
|
+
// Wrap onSubmit to await the result before navigating
|
|
155
161
|
// handleSubmit from react-hook-form expects: (data: EntityData) => void | Promise<void>
|
|
156
162
|
// Our onSubmit prop is (data: T) => void | Promise<void>, where T extends EntityRecord
|
|
157
163
|
// EntityData should be compatible with T, so we can safely pass it
|
|
158
|
-
const handleFormSubmit = (data) => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
const handleFormSubmit = async (data) => {
|
|
165
|
+
try {
|
|
166
|
+
// Await onSubmit so navigation only happens after successful submission
|
|
167
|
+
// Type assertion is safe: EntityData is the validated form data, T is EntityRecord
|
|
168
|
+
await onSubmit(data);
|
|
169
|
+
// Navigate only after successful submission
|
|
170
|
+
if (successPath) {
|
|
171
|
+
navigate(successPath);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// Default: navigate back (user came from list, go back there)
|
|
175
|
+
navigate('back');
|
|
176
|
+
}
|
|
166
177
|
}
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
|
|
178
|
+
catch {
|
|
179
|
+
// onSubmit rejected — stay on form so user can correct errors
|
|
180
|
+
// Error display is handled by the caller's onSubmit implementation
|
|
170
181
|
}
|
|
171
182
|
};
|
|
172
183
|
// Determine if cancel button should show
|
|
@@ -274,13 +285,13 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
|
|
|
274
285
|
if (buttonCount === 1) {
|
|
275
286
|
return (_jsx(Stack, { direction: "column", gap: "tight", style: buttonContainerStyle, children: buttons[0] }));
|
|
276
287
|
}
|
|
277
|
-
// Multiple buttons:
|
|
278
|
-
const
|
|
288
|
+
// Multiple buttons: responsive stacked on mobile, proportional on tablet+
|
|
289
|
+
const colTemplate = buttonCount === 2
|
|
279
290
|
? '1fr 2fr' // Cancel/Preview + Submit
|
|
280
291
|
: buttonCount === 3
|
|
281
292
|
? '1fr 1fr 2fr' // Cancel + Preview + Submit
|
|
282
293
|
: '1fr 1fr 1fr 2fr'; // Cancel + Preview + Secondary + Submit
|
|
283
|
-
return (_jsx(Grid, { cols: [
|
|
294
|
+
return (_jsx(Grid, { cols: ['1fr', colTemplate, colTemplate, colTemplate], gap: "tight", style: buttonContainerStyle, children: buttons }));
|
|
284
295
|
})()] })] }) }) }));
|
|
285
296
|
}
|
|
286
297
|
export default EntityFormRenderer;
|
|
@@ -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,
|
|
13
|
+
export declare function EntityList({ entity, 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,QAAQ,EACR,OAAO,EACP,WAAmB,EACnB,UAAqB,EACrB,QAAQ,EAAE,YAAY,EACtB,YAAY,EACZ,UAAiB,GAClB,EAAE,eAAe,2CAiTjB"}
|
|
@@ -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,
|
|
32
|
+
export function EntityList({ entity, 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
|
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Reusable recommendations section for related entities
|
|
3
|
+
* @description Fetches and displays related entity cards in a grid.
|
|
4
|
+
* Caller defines "related by what" via queryOptions.
|
|
5
|
+
*
|
|
6
|
+
* @version 0.0.1
|
|
7
|
+
* @since 0.0.15
|
|
8
|
+
* @author AMBROISE PARK Consulting
|
|
9
|
+
*/
|
|
10
|
+
import type { EntityRecommendationsProps } from '@donotdev/core';
|
|
11
|
+
/**
|
|
12
|
+
* Displays a grid of related entity cards.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* <EntityRecommendations
|
|
16
|
+
* entity={apartmentEntity}
|
|
17
|
+
* title={t('apartment.recommendations')}
|
|
18
|
+
* queryOptions={{
|
|
19
|
+
* where: [
|
|
20
|
+
* { field: 'district_code', operator: 'eq', value: current.district_code },
|
|
21
|
+
* { field: 'id', operator: 'neq', value: current.id },
|
|
22
|
+
* ],
|
|
23
|
+
* limit: 3,
|
|
24
|
+
* }}
|
|
25
|
+
* />
|
|
26
|
+
*/
|
|
27
|
+
export declare function EntityRecommendations({ entity, queryOptions, title, basePath, cols, className, }: EntityRecommendationsProps): import("react/jsx-runtime").JSX.Element | null;
|
|
28
|
+
//# sourceMappingURL=EntityRecommendations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EntityRecommendations.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityRecommendations.tsx"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAMjE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,IAAQ,EACR,SAAS,GACV,EAAE,0BAA0B,kDAuB5B"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Section } from '@donotdev/components';
|
|
4
|
+
import { useCrudList } from '@donotdev/crud';
|
|
5
|
+
import { CrudCard } from './CrudCardLink';
|
|
6
|
+
/**
|
|
7
|
+
* Displays a grid of related entity cards.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* <EntityRecommendations
|
|
11
|
+
* entity={apartmentEntity}
|
|
12
|
+
* title={t('apartment.recommendations')}
|
|
13
|
+
* queryOptions={{
|
|
14
|
+
* where: [
|
|
15
|
+
* { field: 'district_code', operator: 'eq', value: current.district_code },
|
|
16
|
+
* { field: 'id', operator: 'neq', value: current.id },
|
|
17
|
+
* ],
|
|
18
|
+
* limit: 3,
|
|
19
|
+
* }}
|
|
20
|
+
* />
|
|
21
|
+
*/
|
|
22
|
+
export function EntityRecommendations({ entity, queryOptions, title, basePath, cols = 3, className, }) {
|
|
23
|
+
const { items, loading } = useCrudList(entity, {
|
|
24
|
+
queryOptions,
|
|
25
|
+
});
|
|
26
|
+
if (!loading && items.length === 0) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const resolvedBasePath = basePath ?? `/${entity.collection}`;
|
|
30
|
+
return (_jsx(Section, { title: title, gridCols: cols, className: className, children: items.map((item) => (_jsx(CrudCard, { item: item, entity: entity, detailHref: `${resolvedBasePath}/${item.id}` }, item.id))) }));
|
|
31
|
+
}
|