@endora-commerce/mod-price-lists 0.100.0
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/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/admin/components/ApplicationRuleBuilder.d.ts +24 -0
- package/dist/admin/components/ApplicationRuleBuilder.d.ts.map +1 -0
- package/dist/admin/components/ApplicationRuleBuilder.js +249 -0
- package/dist/admin/components/ApplicationRuleBuilder.js.map +1 -0
- package/dist/admin/components/BracketGrid.d.ts +29 -0
- package/dist/admin/components/BracketGrid.d.ts.map +1 -0
- package/dist/admin/components/BracketGrid.js +252 -0
- package/dist/admin/components/BracketGrid.js.map +1 -0
- package/dist/admin/components/DisplayModeOverrideRow.d.ts +46 -0
- package/dist/admin/components/DisplayModeOverrideRow.d.ts.map +1 -0
- package/dist/admin/components/DisplayModeOverrideRow.js +109 -0
- package/dist/admin/components/DisplayModeOverrideRow.js.map +1 -0
- package/dist/admin/components/LinkedPriceListsPanel.d.ts +55 -0
- package/dist/admin/components/LinkedPriceListsPanel.d.ts.map +1 -0
- package/dist/admin/components/LinkedPriceListsPanel.js +131 -0
- package/dist/admin/components/LinkedPriceListsPanel.js.map +1 -0
- package/dist/admin/index.d.ts +34 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +119 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/DisplayModeOverridesPage.d.ts +25 -0
- package/dist/admin/pages/DisplayModeOverridesPage.d.ts.map +1 -0
- package/dist/admin/pages/DisplayModeOverridesPage.js +266 -0
- package/dist/admin/pages/DisplayModeOverridesPage.js.map +1 -0
- package/dist/admin/pages/PriceListDetailPage.d.ts +23 -0
- package/dist/admin/pages/PriceListDetailPage.d.ts.map +1 -0
- package/dist/admin/pages/PriceListDetailPage.js +421 -0
- package/dist/admin/pages/PriceListDetailPage.js.map +1 -0
- package/dist/admin/pages/PriceListsPage.d.ts +17 -0
- package/dist/admin/pages/PriceListsPage.d.ts.map +1 -0
- package/dist/admin/pages/PriceListsPage.js +198 -0
- package/dist/admin/pages/PriceListsPage.js.map +1 -0
- package/dist/admin/zones/CategoryDisplayMode.d.ts +27 -0
- package/dist/admin/zones/CategoryDisplayMode.d.ts.map +1 -0
- package/dist/admin/zones/CategoryDisplayMode.js +11 -0
- package/dist/admin/zones/CategoryDisplayMode.js.map +1 -0
- package/dist/admin/zones/OrganizationDisplayMode.d.ts +36 -0
- package/dist/admin/zones/OrganizationDisplayMode.d.ts.map +1 -0
- package/dist/admin/zones/OrganizationDisplayMode.js +12 -0
- package/dist/admin/zones/OrganizationDisplayMode.js.map +1 -0
- package/dist/admin/zones/ProductLinkedPriceLists.d.ts +16 -0
- package/dist/admin/zones/ProductLinkedPriceLists.d.ts.map +1 -0
- package/dist/admin/zones/ProductLinkedPriceLists.js +7 -0
- package/dist/admin/zones/ProductLinkedPriceLists.js.map +1 -0
- package/dist/backend/entities/price-display-mode-override.entity.d.ts +23 -0
- package/dist/backend/entities/price-display-mode-override.entity.d.ts.map +1 -0
- package/dist/backend/entities/price-display-mode-override.entity.js +58 -0
- package/dist/backend/entities/price-display-mode-override.entity.js.map +1 -0
- package/dist/backend/entities/price-list-price-bracket.entity.d.ts +26 -0
- package/dist/backend/entities/price-list-price-bracket.entity.d.ts.map +1 -0
- package/dist/backend/entities/price-list-price-bracket.entity.js +73 -0
- package/dist/backend/entities/price-list-price-bracket.entity.js.map +1 -0
- package/dist/backend/entities/price-list-product.entity.d.ts +15 -0
- package/dist/backend/entities/price-list-product.entity.d.ts.map +1 -0
- package/dist/backend/entities/price-list-product.entity.js +43 -0
- package/dist/backend/entities/price-list-product.entity.js.map +1 -0
- package/dist/backend/entities/price-list.entity.d.ts +34 -0
- package/dist/backend/entities/price-list.entity.d.ts.map +1 -0
- package/dist/backend/entities/price-list.entity.js +115 -0
- package/dist/backend/entities/price-list.entity.js.map +1 -0
- package/dist/backend/index.d.ts +125 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +179 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +68 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +88 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.d.ts +40 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +447 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/routes.storefront.d.ts +47 -0
- package/dist/backend/routes.storefront.d.ts.map +1 -0
- package/dist/backend/routes.storefront.js +131 -0
- package/dist/backend/routes.storefront.js.map +1 -0
- package/dist/backend/services/application-rule-evaluator.d.ts +25 -0
- package/dist/backend/services/application-rule-evaluator.d.ts.map +1 -0
- package/dist/backend/services/application-rule-evaluator.js +2 -0
- package/dist/backend/services/application-rule-evaluator.js.map +1 -0
- package/dist/backend/services/audit-references.d.ts +11 -0
- package/dist/backend/services/audit-references.d.ts.map +1 -0
- package/dist/backend/services/audit-references.js +23 -0
- package/dist/backend/services/audit-references.js.map +1 -0
- package/dist/backend/services/default-price-list-migration.d.ts +70 -0
- package/dist/backend/services/default-price-list-migration.d.ts.map +1 -0
- package/dist/backend/services/default-price-list-migration.js +177 -0
- package/dist/backend/services/default-price-list-migration.js.map +1 -0
- package/dist/backend/services/display-mode-resolver.d.ts +78 -0
- package/dist/backend/services/display-mode-resolver.d.ts.map +1 -0
- package/dist/backend/services/display-mode-resolver.js +71 -0
- package/dist/backend/services/display-mode-resolver.js.map +1 -0
- package/dist/backend/services/listing-price-chain.d.ts +26 -0
- package/dist/backend/services/listing-price-chain.d.ts.map +1 -0
- package/dist/backend/services/listing-price-chain.js +55 -0
- package/dist/backend/services/listing-price-chain.js.map +1 -0
- package/dist/backend/services/price-bracket-resolver.d.ts +24 -0
- package/dist/backend/services/price-bracket-resolver.d.ts.map +1 -0
- package/dist/backend/services/price-bracket-resolver.js +16 -0
- package/dist/backend/services/price-bracket-resolver.js.map +1 -0
- package/dist/backend/services/price-list-candidate-vector.d.ts +116 -0
- package/dist/backend/services/price-list-candidate-vector.d.ts.map +1 -0
- package/dist/backend/services/price-list-candidate-vector.js +133 -0
- package/dist/backend/services/price-list-candidate-vector.js.map +1 -0
- package/dist/backend/services/price-list-currency-reference.d.ts +12 -0
- package/dist/backend/services/price-list-currency-reference.d.ts.map +1 -0
- package/dist/backend/services/price-list-currency-reference.js +24 -0
- package/dist/backend/services/price-list-currency-reference.js.map +1 -0
- package/dist/backend/services/price-list-read-port.d.ts +29 -0
- package/dist/backend/services/price-list-read-port.d.ts.map +1 -0
- package/dist/backend/services/price-list-read-port.js +64 -0
- package/dist/backend/services/price-list-read-port.js.map +1 -0
- package/dist/backend/services/price-list-resolver.d.ts +26 -0
- package/dist/backend/services/price-list-resolver.d.ts.map +1 -0
- package/dist/backend/services/price-list-resolver.js +37 -0
- package/dist/backend/services/price-list-resolver.js.map +1 -0
- package/dist/backend/services/price-list-service.d.ts +431 -0
- package/dist/backend/services/price-list-service.d.ts.map +1 -0
- package/dist/backend/services/price-list-service.js +1238 -0
- package/dist/backend/services/price-list-service.js.map +1 -0
- package/dist/backend/services/price-list-status-worker.d.ts +28 -0
- package/dist/backend/services/price-list-status-worker.d.ts.map +1 -0
- package/dist/backend/services/price-list-status-worker.js +38 -0
- package/dist/backend/services/price-list-status-worker.js.map +1 -0
- package/dist/backend/services/pricing-cache.d.ts +53 -0
- package/dist/backend/services/pricing-cache.d.ts.map +1 -0
- package/dist/backend/services/pricing-cache.js +88 -0
- package/dist/backend/services/pricing-cache.js.map +1 -0
- package/dist/backend/services/pricing-service.d.ts +304 -0
- package/dist/backend/services/pricing-service.d.ts.map +1 -0
- package/dist/backend/services/pricing-service.interface.d.ts +151 -0
- package/dist/backend/services/pricing-service.interface.d.ts.map +1 -0
- package/dist/backend/services/pricing-service.interface.js +10 -0
- package/dist/backend/services/pricing-service.interface.js.map +1 -0
- package/dist/backend/services/pricing-service.js +803 -0
- package/dist/backend/services/pricing-service.js.map +1 -0
- package/dist/backend/services/unit-price-ordering.d.ts +121 -0
- package/dist/backend/services/unit-price-ordering.d.ts.map +1 -0
- package/dist/backend/services/unit-price-ordering.js +164 -0
- package/dist/backend/services/unit-price-ordering.js.map +1 -0
- package/dist/manifest.d.ts +218 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +228 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260426T075235_price_lists_pricing_init.d.ts +24 -0
- package/dist/migrations/20260426T075235_price_lists_pricing_init.d.ts.map +1 -0
- package/dist/migrations/20260426T075235_price_lists_pricing_init.js +89 -0
- package/dist/migrations/20260426T075235_price_lists_pricing_init.js.map +1 -0
- package/dist/migrations/20260504T125655_price_lists_engine.d.ts +6 -0
- package/dist/migrations/20260504T125655_price_lists_engine.d.ts.map +1 -0
- package/dist/migrations/20260504T125655_price_lists_engine.js +319 -0
- package/dist/migrations/20260504T125655_price_lists_engine.js.map +1 -0
- package/dist/migrations/20260817T055457_price_lists_single_system_price_list.d.ts +15 -0
- package/dist/migrations/20260817T055457_price_lists_single_system_price_list.d.ts.map +1 -0
- package/dist/migrations/20260817T055457_price_lists_single_system_price_list.js +58 -0
- package/dist/migrations/20260817T055457_price_lists_single_system_price_list.js.map +1 -0
- package/dist/migrations/20260821T135907_price_lists_unit_price_amount_index.d.ts +40 -0
- package/dist/migrations/20260821T135907_price_lists_unit_price_amount_index.d.ts.map +1 -0
- package/dist/migrations/20260821T135907_price_lists_unit_price_amount_index.js +46 -0
- package/dist/migrations/20260821T135907_price_lists_unit_price_amount_index.js.map +1 -0
- package/dist/migrations/index.d.ts +30 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +35 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/price_lists.md +278 -0
- package/i18n/en.json +14 -0
- package/i18n/pl.json +14 -0
- package/package.json +99 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Link } from 'react-router-dom';
|
|
4
|
+
import { ArrowUpRight, CircleDollarSign, Star } from 'lucide-react';
|
|
5
|
+
import { ApiError, apiClient } from '@endora-commerce/admin-kit/lib';
|
|
6
|
+
import { useTranslation } from '@endora-commerce/admin-kit/i18n';
|
|
7
|
+
import { DisplayModeOverrideRow } from './DisplayModeOverrideRow.js';
|
|
8
|
+
const STATUS_BADGE = {
|
|
9
|
+
draft: 'b2b-badge b2b-badge--outline',
|
|
10
|
+
active: 'b2b-badge b2b-badge--success',
|
|
11
|
+
scheduled: 'b2b-badge b2b-badge--info',
|
|
12
|
+
expired: 'b2b-badge b2b-badge--muted',
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* LinkedPriceListsPanel (US8 / T096) — Pricing tab on the admin
|
|
16
|
+
* product editor.
|
|
17
|
+
*
|
|
18
|
+
* Read-only: lists every price list the product is part of with the
|
|
19
|
+
* per-currency bracket summary and a deep link into the corresponding
|
|
20
|
+
* editor (`/price-lists/:id?focus=:productId`). Brackets and rules are
|
|
21
|
+
* edited on the price-list editor — this panel is just the index.
|
|
22
|
+
*
|
|
23
|
+
* The deep-link path returned by the backend points at
|
|
24
|
+
* `/admin/price-lists/...`; the admin SPA routes the same view at
|
|
25
|
+
* `/price-lists/...`, so we strip the `/admin` prefix when navigating
|
|
26
|
+
* within the React Router tree.
|
|
27
|
+
*
|
|
28
|
+
* **Moved out of `admin/src/modules/price_lists/` by feature 091's P7a**, where
|
|
29
|
+
* `catalog`'s product editor imported it by path — one of the three admin keys
|
|
30
|
+
* in `backend/scripts/ledgers/cross-module-imports/catalog.ts`. It reaches that
|
|
31
|
+
* screen as a `product.editor.pricing.after` contribution now, so neither
|
|
32
|
+
* module names the other.
|
|
33
|
+
*/
|
|
34
|
+
export function LinkedPriceListsPanel({ productId }) {
|
|
35
|
+
const t = useTranslation('core');
|
|
36
|
+
const [rows, setRows] = useState([]);
|
|
37
|
+
const [loading, setLoading] = useState(true);
|
|
38
|
+
const [error, setError] = useState(null);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
let cancelled = false;
|
|
41
|
+
setLoading(true);
|
|
42
|
+
setError(null);
|
|
43
|
+
apiClient
|
|
44
|
+
.get(`/api/v1/admin/products/${encodeURIComponent(productId)}/price-lists`)
|
|
45
|
+
.then((res) => {
|
|
46
|
+
if (!cancelled)
|
|
47
|
+
setRows(res.data.items);
|
|
48
|
+
})
|
|
49
|
+
.catch((err) => {
|
|
50
|
+
if (!cancelled) {
|
|
51
|
+
setError(err instanceof ApiError ? err.envelope.error.message : t('priceLists.linked.error.load'));
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
.finally(() => {
|
|
55
|
+
if (!cancelled)
|
|
56
|
+
setLoading(false);
|
|
57
|
+
});
|
|
58
|
+
return () => {
|
|
59
|
+
cancelled = true;
|
|
60
|
+
};
|
|
61
|
+
}, [productId, t]);
|
|
62
|
+
if (loading) {
|
|
63
|
+
return _jsx("div", { style: { padding: 16, color: 'var(--fg-muted)', fontSize: 13 }, children: t('priceLists.linked.loading') });
|
|
64
|
+
}
|
|
65
|
+
if (error) {
|
|
66
|
+
return (_jsx("div", { className: "b2b-card", style: {
|
|
67
|
+
padding: 12,
|
|
68
|
+
background: 'var(--danger-soft)',
|
|
69
|
+
color: 'var(--danger-soft-fg)',
|
|
70
|
+
border: '1px solid hsl(8 80% 85%)',
|
|
71
|
+
fontSize: 12,
|
|
72
|
+
}, children: error }));
|
|
73
|
+
}
|
|
74
|
+
if (rows.length === 0) {
|
|
75
|
+
return (_jsx("div", { className: "b2b-card", style: {
|
|
76
|
+
padding: 16,
|
|
77
|
+
background: 'var(--info-soft)',
|
|
78
|
+
border: '1px solid hsl(217 70% 88%)',
|
|
79
|
+
}, children: _jsxs("div", { className: "b2b-row", style: { gap: 12, alignItems: 'flex-start' }, children: [_jsx(CircleDollarSign, { size: 18, style: { color: 'var(--info-soft-fg)', marginTop: 2 } }), _jsxs("div", { className: "b2b-grow", children: [_jsx("div", { style: { fontSize: 13, fontWeight: 600, color: 'var(--info-soft-fg)' }, children: t('priceLists.linked.emptyTitle') }), _jsx("div", { style: { fontSize: 12, color: 'var(--info-soft-fg)', marginTop: 4 }, children: t('priceLists.linked.emptyDescription') })] })] }) }));
|
|
80
|
+
}
|
|
81
|
+
return (_jsxs("div", { className: "b2b-col", style: { gap: 16 }, children: [_jsx("div", { className: "b2b-card", style: { padding: 12 }, children: _jsx(DisplayModeOverrideRow, { scope: "product", targetId: productId }) }), _jsx(LinkedPriceListsList, { rows: rows })] }));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Pure render of the linked-list table — split out so it can be unit
|
|
85
|
+
* tested via renderToString without booting the data-fetch effect.
|
|
86
|
+
*/
|
|
87
|
+
export function LinkedPriceListsList({ rows }) {
|
|
88
|
+
const t = useTranslation('core');
|
|
89
|
+
const STATUS_LABEL = {
|
|
90
|
+
draft: t('priceLists.status.draft'),
|
|
91
|
+
active: t('priceLists.status.active'),
|
|
92
|
+
scheduled: t('priceLists.status.scheduled'),
|
|
93
|
+
expired: t('priceLists.status.expired'),
|
|
94
|
+
};
|
|
95
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "b2b-help", children: t('priceLists.linked.helpText') }), _jsx("ul", { style: { margin: 0, padding: 0, listStyle: 'none' }, children: rows.map((r) => {
|
|
96
|
+
const looksDefault = r.list.name === 'Default';
|
|
97
|
+
const internalPath = toInternalPath(r.deepLinkPath);
|
|
98
|
+
return (_jsxs("li", { className: "b2b-card", "data-testid": "linked-price-list-row", "data-list-id": r.list.id, style: {
|
|
99
|
+
padding: 12,
|
|
100
|
+
marginBottom: 8,
|
|
101
|
+
display: 'grid',
|
|
102
|
+
gridTemplateColumns: '32px 1fr auto',
|
|
103
|
+
gap: 12,
|
|
104
|
+
alignItems: 'center',
|
|
105
|
+
}, children: [_jsx("div", { style: {
|
|
106
|
+
width: 32,
|
|
107
|
+
height: 32,
|
|
108
|
+
borderRadius: 6,
|
|
109
|
+
background: looksDefault ? 'var(--primary-soft)' : 'var(--surface-sunken)',
|
|
110
|
+
display: 'grid',
|
|
111
|
+
placeItems: 'center',
|
|
112
|
+
}, children: looksDefault ? (_jsx(Star, { size: 14, style: { color: 'var(--primary-color)' } })) : (_jsx(CircleDollarSign, { size: 14, style: { color: 'var(--fg-muted)' } })) }), _jsxs("div", { children: [_jsxs("div", { className: "b2b-row", style: { gap: 6, alignItems: 'center' }, children: [_jsx(Link, { to: internalPath, style: { fontSize: 13, fontWeight: 500, color: 'inherit', textDecoration: 'none' }, children: r.list.name }), _jsx("span", { className: "b2b-badge b2b-badge--outline", children: r.list.type === 'base' ? t('priceLists.type.base') : t('priceLists.type.sale') }), _jsx("span", { className: STATUS_BADGE[r.list.status], children: STATUS_LABEL[r.list.status] }), looksDefault ? (_jsx("span", { className: "b2b-badge b2b-badge--success", style: { display: 'inline-flex', alignItems: 'center', gap: 4 }, children: t('priceLists.linked.system') })) : null] }), r.summary.length > 0 ? (_jsx("div", { className: "b2b-help", style: { marginTop: 4, display: 'flex', flexWrap: 'wrap', gap: 8 }, children: r.summary.map((s) => (_jsxs("span", { style: {
|
|
113
|
+
fontFamily: 'var(--font-mono)',
|
|
114
|
+
fontSize: 11,
|
|
115
|
+
color: 'var(--fg-muted)',
|
|
116
|
+
}, children: [_jsx("strong", { style: { color: 'var(--fg-default)' }, children: s.currencyCode }), ' ', s.summary] }, s.currencyCode))) })) : (_jsx("div", { className: "b2b-help", style: { marginTop: 4 }, children: t('priceLists.linked.noBracketsYet') }))] }), _jsxs(Link, { to: internalPath, className: "b2b-btn b2b-btn--default b2b-btn--sm", title: t('priceLists.linked.openEditor'), children: [t('priceLists.linked.open'), " ", _jsx(ArrowUpRight, { size: 12 })] })] }, r.list.id));
|
|
117
|
+
}) })] }));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Backend returns `/admin/price-lists/<id>/products?focus=<pid>`; the admin
|
|
121
|
+
* SPA routes the same view at `/price-lists/<id>?focus=<pid>` — strip the
|
|
122
|
+
* `/admin` prefix and the `/products` suffix to keep the route table simple.
|
|
123
|
+
*/
|
|
124
|
+
export function toInternalPath(deepLink) {
|
|
125
|
+
let path = deepLink;
|
|
126
|
+
if (path.startsWith('/admin/'))
|
|
127
|
+
path = path.slice('/admin'.length);
|
|
128
|
+
path = path.replace('/products?', '?');
|
|
129
|
+
return path;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=LinkedPriceListsPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LinkedPriceListsPanel.js","sourceRoot":"","sources":["../../../src/admin/components/LinkedPriceListsPanel.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAiBrE,MAAM,YAAY,GAAoC;IACpD,KAAK,EAAE,8BAA8B;IACrC,MAAM,EAAE,8BAA8B;IACtC,SAAS,EAAE,2BAA2B;IACtC,OAAO,EAAE,4BAA4B;CACtC,CAAC;AAGF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,qBAAqB,CAAC,EAAE,SAAS,EAAyB;IACxE,MAAM,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACjC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAuB,EAAE,CAAC,CAAC;IAC3D,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAExD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,UAAU,CAAC,IAAI,CAAC,CAAC;QACjB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,SAAS;aACN,GAAG,CACF,0BAA0B,kBAAkB,CAAC,SAAS,CAAC,cAAc,CACtE;aACA,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;YACZ,IAAI,CAAC,SAAS;gBAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;YACtB,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,QAAQ,CAAC,GAAG,YAAY,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC;YACrG,CAAC;QACH,CAAC,CAAC;aACD,OAAO,CAAC,GAAG,EAAE;YACZ,IAAI,CAAC,SAAS;gBAAE,UAAU,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QACL,OAAO,GAAS,EAAE;YAChB,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC;IAEnB,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,cAAK,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,EAAE,EAAE,YAAG,CAAC,CAAC,2BAA2B,CAAC,GAAO,CAAC;IACrH,CAAC;IAED,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,CACL,cACE,SAAS,EAAC,UAAU,EACpB,KAAK,EAAE;gBACL,OAAO,EAAE,EAAE;gBACX,UAAU,EAAE,oBAAoB;gBAChC,KAAK,EAAE,uBAAuB;gBAC9B,MAAM,EAAE,0BAA0B;gBAClC,QAAQ,EAAE,EAAE;aACb,YAEA,KAAK,GACF,CACP,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,CACL,cACE,SAAS,EAAC,UAAU,EACpB,KAAK,EAAE;gBACL,OAAO,EAAE,EAAE;gBACX,UAAU,EAAE,kBAAkB;gBAC9B,MAAM,EAAE,4BAA4B;aACrC,YAED,eAAK,SAAS,EAAC,SAAS,EAAC,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,aACnE,KAAC,gBAAgB,IAAC,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,qBAAqB,EAAE,SAAS,EAAE,CAAC,EAAE,GAAI,EACrF,eAAK,SAAS,EAAC,UAAU,aACvB,cAAK,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,qBAAqB,EAAE,YACxE,CAAC,CAAC,8BAA8B,CAAC,GAC9B,EACN,cAAK,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,qBAAqB,EAAE,SAAS,EAAE,CAAC,EAAE,YACrE,CAAC,CAAC,oCAAoC,CAAC,GACpC,IACF,IACF,GACF,CACP,CAAC;IACJ,CAAC;IAED,OAAO,CACL,eAAK,SAAS,EAAC,SAAS,EAAC,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,aACzC,cAAK,SAAS,EAAC,UAAU,EAAC,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,YAI9C,KAAC,sBAAsB,IAAC,KAAK,EAAC,SAAS,EAAC,QAAQ,EAAE,SAAS,GAAI,GAC3D,EACN,KAAC,oBAAoB,IAAC,IAAI,EAAE,IAAI,GAAI,IAChC,CACP,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,EAAE,IAAI,EAAkC;IAC3E,MAAM,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACjC,MAAM,YAAY,GAAoC;QACpD,KAAK,EAAE,CAAC,CAAC,yBAAyB,CAAC;QACnC,MAAM,EAAE,CAAC,CAAC,0BAA0B,CAAC;QACrC,SAAS,EAAE,CAAC,CAAC,6BAA6B,CAAC;QAC3C,OAAO,EAAE,CAAC,CAAC,2BAA2B,CAAC;KACxC,CAAC;IACF,OAAO,CACL,8BACE,cAAK,SAAS,EAAC,UAAU,YACtB,CAAC,CAAC,4BAA4B,CAAC,GAC5B,EACN,aAAI,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,YACpD,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;oBACd,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC;oBAC/C,MAAM,YAAY,GAAG,cAAc,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;oBACpD,OAAO,CACL,cAEE,SAAS,EAAC,UAAU,iBACR,uBAAuB,kBACrB,CAAC,CAAC,IAAI,CAAC,EAAE,EACvB,KAAK,EAAE;4BACL,OAAO,EAAE,EAAE;4BACX,YAAY,EAAE,CAAC;4BACf,OAAO,EAAE,MAAM;4BACf,mBAAmB,EAAE,eAAe;4BACpC,GAAG,EAAE,EAAE;4BACP,UAAU,EAAE,QAAQ;yBACrB,aAED,cACE,KAAK,EAAE;oCACL,KAAK,EAAE,EAAE;oCACT,MAAM,EAAE,EAAE;oCACV,YAAY,EAAE,CAAC;oCACf,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,uBAAuB;oCAC1E,OAAO,EAAE,MAAM;oCACf,UAAU,EAAE,QAAQ;iCACrB,YAEA,YAAY,CAAC,CAAC,CAAC,CACd,KAAC,IAAI,IAAC,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,sBAAsB,EAAE,GAAI,CAC7D,CAAC,CAAC,CAAC,CACF,KAAC,gBAAgB,IAAC,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,GAAI,CACpE,GACG,EACN,0BACE,eAAK,SAAS,EAAC,SAAS,EAAC,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,aAC9D,KAAC,IAAI,IACH,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,YAEjF,CAAC,CAAC,IAAI,CAAC,IAAI,GACP,EACP,eAAM,SAAS,EAAC,8BAA8B,YAC3C,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,GAC1E,EACP,eAAM,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,YAAG,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAQ,EACjF,YAAY,CAAC,CAAC,CAAC,CACd,eACE,SAAS,EAAC,8BAA8B,EACxC,KAAK,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,EAAE,YAE9D,CAAC,CAAC,0BAA0B,CAAC,GACzB,CACR,CAAC,CAAC,CAAC,IAAI,IACJ,EACL,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACtB,cAAK,SAAS,EAAC,UAAU,EAAC,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,YACzF,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CACpB,gBAEE,KAAK,EAAE;gDACL,UAAU,EAAE,kBAAkB;gDAC9B,QAAQ,EAAE,EAAE;gDACZ,KAAK,EAAE,iBAAiB;6CACzB,aAED,iBAAQ,KAAK,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,YAAG,CAAC,CAAC,YAAY,GAAU,EAAC,GAAG,EAC3E,CAAC,CAAC,OAAO,KARL,CAAC,CAAC,YAAY,CASd,CACR,CAAC,GACE,CACP,CAAC,CAAC,CAAC,CACF,cAAK,SAAS,EAAC,UAAU,EAAC,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,YAC9C,CAAC,CAAC,iCAAiC,CAAC,GACjC,CACP,IACG,EACN,MAAC,IAAI,IACH,EAAE,EAAE,YAAY,EAChB,SAAS,EAAC,sCAAsC,EAChD,KAAK,EAAE,CAAC,CAAC,8BAA8B,CAAC,aAEvC,CAAC,CAAC,wBAAwB,CAAC,OAAE,KAAC,YAAY,IAAC,IAAI,EAAE,EAAE,GAAI,IACnD,KA9EF,CAAC,CAAC,IAAI,CAAC,EAAE,CA+EX,CACN,CAAC;gBACJ,CAAC,CAAC,GACC,IACJ,CACJ,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,IAAI,IAAI,GAAG,QAAQ,CAAC;IACpB,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACnE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;IACvC,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `price_lists`' admin surface — three routes, one sidebar entry and three zone
|
|
3
|
+
* contributions (feature 091, P7a and P7b for the zones, Phase 4 batch 13 for
|
|
4
|
+
* the registrations;
|
|
5
|
+
* `specs/091-module-owned-admin-surfaces/contracts/admin-component-contribution.md`
|
|
6
|
+
* §10).
|
|
7
|
+
*
|
|
8
|
+
* **The zones came first, and that is the property the batch rests on.** P7a
|
|
9
|
+
* and P7b declared the three contributions below while this module's screens
|
|
10
|
+
* were still `admin/src/App.tsx`'s, because `check:admin-registrations` counts
|
|
11
|
+
* routes and nav entries and a zone is neither — so a contributor can be
|
|
12
|
+
* scheduled independently of the host that renders its place. Batch 13 is the
|
|
13
|
+
* other half: the three screens are this package's now.
|
|
14
|
+
*
|
|
15
|
+
* **What it drains**: two of the three admin keys in
|
|
16
|
+
* `backend/scripts/ledgers/cross-module-imports/catalog.ts` —
|
|
17
|
+
* `CategoriesTree.tsx` importing `DisplayModeOverrideRow` and
|
|
18
|
+
* `ProductEditor.tsx` importing `LinkedPriceListsPanel` — and, with P7b, the
|
|
19
|
+
* `DisplayModeOverrideRow` key of
|
|
20
|
+
* `backend/scripts/ledgers/cross-module-imports/organizations.ts`. Each entry's
|
|
21
|
+
* recorded retiring condition is *"the owner declaring an admin contribution
|
|
22
|
+
* zone"*, and this is that declaration.
|
|
23
|
+
*
|
|
24
|
+
* **Zone and not a published component**, decided from the two signatures read
|
|
25
|
+
* off the files in this merge request (§9.3): `(scope, targetId, …)` and
|
|
26
|
+
* `(productId)` — identifiers in, nothing back, no `onChange` — which is Z1
|
|
27
|
+
* question 2, an addition this module makes because this module is installed.
|
|
28
|
+
*
|
|
29
|
+
* **This entry exports data and nothing else** (R2); every component is a
|
|
30
|
+
* dynamic-import factory (R6).
|
|
31
|
+
*/
|
|
32
|
+
import { type AdminContributions } from '@endora-commerce/admin-kit/contributions';
|
|
33
|
+
export declare const contributions: AdminContributions;
|
|
34
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,0CAA0C,CAAC;AAqBlD,eAAO,MAAM,aAAa,EAAE,kBA4E3B,CAAC"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `price_lists`' admin surface — three routes, one sidebar entry and three zone
|
|
3
|
+
* contributions (feature 091, P7a and P7b for the zones, Phase 4 batch 13 for
|
|
4
|
+
* the registrations;
|
|
5
|
+
* `specs/091-module-owned-admin-surfaces/contracts/admin-component-contribution.md`
|
|
6
|
+
* §10).
|
|
7
|
+
*
|
|
8
|
+
* **The zones came first, and that is the property the batch rests on.** P7a
|
|
9
|
+
* and P7b declared the three contributions below while this module's screens
|
|
10
|
+
* were still `admin/src/App.tsx`'s, because `check:admin-registrations` counts
|
|
11
|
+
* routes and nav entries and a zone is neither — so a contributor can be
|
|
12
|
+
* scheduled independently of the host that renders its place. Batch 13 is the
|
|
13
|
+
* other half: the three screens are this package's now.
|
|
14
|
+
*
|
|
15
|
+
* **What it drains**: two of the three admin keys in
|
|
16
|
+
* `backend/scripts/ledgers/cross-module-imports/catalog.ts` —
|
|
17
|
+
* `CategoriesTree.tsx` importing `DisplayModeOverrideRow` and
|
|
18
|
+
* `ProductEditor.tsx` importing `LinkedPriceListsPanel` — and, with P7b, the
|
|
19
|
+
* `DisplayModeOverrideRow` key of
|
|
20
|
+
* `backend/scripts/ledgers/cross-module-imports/organizations.ts`. Each entry's
|
|
21
|
+
* recorded retiring condition is *"the owner declaring an admin contribution
|
|
22
|
+
* zone"*, and this is that declaration.
|
|
23
|
+
*
|
|
24
|
+
* **Zone and not a published component**, decided from the two signatures read
|
|
25
|
+
* off the files in this merge request (§9.3): `(scope, targetId, …)` and
|
|
26
|
+
* `(productId)` — identifiers in, nothing back, no `onChange` — which is Z1
|
|
27
|
+
* question 2, an addition this module makes because this module is installed.
|
|
28
|
+
*
|
|
29
|
+
* **This entry exports data and nothing else** (R2); every component is a
|
|
30
|
+
* dynamic-import factory (R6).
|
|
31
|
+
*/
|
|
32
|
+
import { zoneComponent, } from '@endora-commerce/admin-kit/contributions';
|
|
33
|
+
/**
|
|
34
|
+
* The one code every surface below needs — the three screens and the three
|
|
35
|
+
* contributions alike.
|
|
36
|
+
*
|
|
37
|
+
* `packages/modules/price_lists/src/backend/routes.ts` builds one `readGate`
|
|
38
|
+
* from `PRICE_LIST_PERMISSIONS.READ` and gates every read with it: the price
|
|
39
|
+
* list roster and one price list, plus the two endpoints behind the
|
|
40
|
+
* contributions, `GET /api/v1/admin/pricing/display-mode-overrides/:scope/:targetId`
|
|
41
|
+
* and `GET /api/v1/admin/products/:productId/price-lists`. Saving a list,
|
|
42
|
+
* writing a bracket and setting a display mode enforce `price_lists:write` on
|
|
43
|
+
* the API, and each screen gates its own controls on that code through
|
|
44
|
+
* `useAuth().hasPermission`; the read code is what opens the screen (issue
|
|
45
|
+
* #232's rule, and the code the hand-written sidebar row carried).
|
|
46
|
+
*/
|
|
47
|
+
const READ_PERMISSION = 'price_lists:read';
|
|
48
|
+
/** The module's landing route: the price-list roster. */
|
|
49
|
+
const ROUTE_PATH = '/price-lists';
|
|
50
|
+
export const contributions = {
|
|
51
|
+
routes: [
|
|
52
|
+
{
|
|
53
|
+
path: ROUTE_PATH,
|
|
54
|
+
component: () => import('./pages/PriceListsPage.js'),
|
|
55
|
+
requiredPermission: READ_PERMISSION,
|
|
56
|
+
index: true,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
// Declared before `/price-lists/:id`, and `App.tsx` declared it before it
|
|
60
|
+
// too. `<Routes>` ranks by specificity rather than by declaration order,
|
|
61
|
+
// so the static segment wins whatever the order — the ordering is kept
|
|
62
|
+
// anyway, so a reader does not have to know that to see why the detail
|
|
63
|
+
// route is safe here.
|
|
64
|
+
path: `${ROUTE_PATH}/display-modes`,
|
|
65
|
+
component: () => import('./pages/DisplayModeOverridesPage.js'),
|
|
66
|
+
requiredPermission: READ_PERMISSION,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
path: `${ROUTE_PATH}/:id`,
|
|
70
|
+
component: () => import('./pages/PriceListDetailPage.js'),
|
|
71
|
+
requiredPermission: READ_PERMISSION,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
// One row for three screens, and the two without one are deliberate:
|
|
75
|
+
// `/price-lists/:id` is a detail reached from the roster, and
|
|
76
|
+
// `/price-lists/display-modes` is reached from the button the roster renders
|
|
77
|
+
// beside its heading. A route is what `App.tsx` used to hold; a button is not
|
|
78
|
+
// a route.
|
|
79
|
+
nav: [
|
|
80
|
+
{
|
|
81
|
+
to: ROUTE_PATH,
|
|
82
|
+
// Module-relative (R8), resolved in this module's own namespace out of
|
|
83
|
+
// `packages/modules/price_lists/i18n/`. It was `appShell.nav.priceLists`
|
|
84
|
+
// in the shared `_i18n` bundle, one of the four shared files a module
|
|
85
|
+
// author had to edit.
|
|
86
|
+
labelKey: 'nav.priceLists.label',
|
|
87
|
+
// The glyph `AppShell.tsx` rendered by hand, and already on
|
|
88
|
+
// `KnownIconNameSchema` — the palette action this batch declares names
|
|
89
|
+
// the same one.
|
|
90
|
+
icon: 'CircleDollarSign',
|
|
91
|
+
section: 'pricing',
|
|
92
|
+
// First in *Pricing*, which is where the hand-written table put it and
|
|
93
|
+
// where the operator has always seen it. The section's other rows are
|
|
94
|
+
// already the registry's — `taxes` and `promotions` at 200,
|
|
95
|
+
// `delivery_methods` and `promotions`' second row at 300,
|
|
96
|
+
// `payment_methods` at 600 — and the host declares none in it once this
|
|
97
|
+
// one leaves, so 100 is the whole of what keeps this row on top.
|
|
98
|
+
weight: 100,
|
|
99
|
+
requiredPermission: READ_PERMISSION,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
zones: [
|
|
103
|
+
zoneComponent('category.editor.after', () => import('./zones/CategoryDisplayMode.js'), {
|
|
104
|
+
// The only contribution to this place, so the weight orders nothing
|
|
105
|
+
// today. Declared rather than defaulted so a second contributor arrives
|
|
106
|
+
// beside it rather than ahead of it by accident.
|
|
107
|
+
weight: 100,
|
|
108
|
+
requiredPermission: READ_PERMISSION,
|
|
109
|
+
}),
|
|
110
|
+
zoneComponent('product.editor.pricing.after', () => import('./zones/ProductLinkedPriceLists.js'), { weight: 200, requiredPermission: READ_PERMISSION }),
|
|
111
|
+
// Weight 200 of four contributors to the organization detail's zone
|
|
112
|
+
// (feature 091, P7b): `sales_channels`' membership at 100, this row, then
|
|
113
|
+
// `quick_order` at 300 and `carts` at 400. The order is the one the
|
|
114
|
+
// operator saw when the three panels were scattered through
|
|
115
|
+
// `OrganizationDetail.tsx`, preserved rather than re-chosen.
|
|
116
|
+
zoneComponent('organization.detail.after', () => import('./zones/OrganizationDisplayMode.js'), { weight: 200, requiredPermission: READ_PERMISSION }),
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,EACL,aAAa,GAEd,MAAM,0CAA0C,CAAC;AAElD;;;;;;;;;;;;;GAaG;AACH,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAE3C,yDAAyD;AACzD,MAAM,UAAU,GAAG,cAAc,CAAC;AAElC,MAAM,CAAC,MAAM,aAAa,GAAuB;IAC/C,MAAM,EAAE;QACN;YACE,IAAI,EAAE,UAAU;YAChB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,2BAA2B,CAAC;YACpD,kBAAkB,EAAE,eAAe;YACnC,KAAK,EAAE,IAAI;SACZ;QACD;YACE,0EAA0E;YAC1E,yEAAyE;YACzE,uEAAuE;YACvE,uEAAuE;YACvE,sBAAsB;YACtB,IAAI,EAAE,GAAG,UAAU,gBAAgB;YACnC,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,qCAAqC,CAAC;YAC9D,kBAAkB,EAAE,eAAe;SACpC;QACD;YACE,IAAI,EAAE,GAAG,UAAU,MAAM;YACzB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,gCAAgC,CAAC;YACzD,kBAAkB,EAAE,eAAe;SACpC;KACF;IACD,qEAAqE;IACrE,8DAA8D;IAC9D,6EAA6E;IAC7E,8EAA8E;IAC9E,WAAW;IACX,GAAG,EAAE;QACH;YACE,EAAE,EAAE,UAAU;YACd,uEAAuE;YACvE,yEAAyE;YACzE,sEAAsE;YACtE,sBAAsB;YACtB,QAAQ,EAAE,sBAAsB;YAChC,4DAA4D;YAC5D,uEAAuE;YACvE,gBAAgB;YAChB,IAAI,EAAE,kBAAkB;YACxB,OAAO,EAAE,SAAS;YAClB,uEAAuE;YACvE,sEAAsE;YACtE,4DAA4D;YAC5D,0DAA0D;YAC1D,wEAAwE;YACxE,iEAAiE;YACjE,MAAM,EAAE,GAAG;YACX,kBAAkB,EAAE,eAAe;SACpC;KACF;IACD,KAAK,EAAE;QACL,aAAa,CAAC,uBAAuB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,gCAAgC,CAAC,EAAE;YACrF,oEAAoE;YACpE,wEAAwE;YACxE,iDAAiD;YACjD,MAAM,EAAE,GAAG;YACX,kBAAkB,EAAE,eAAe;SACpC,CAAC;QACF,aAAa,CACX,8BAA8B,EAC9B,GAAG,EAAE,CAAC,MAAM,CAAC,oCAAoC,CAAC,EAClD,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,EAAE,eAAe,EAAE,CACrD;QACD,oEAAoE;QACpE,0EAA0E;QAC1E,oEAAoE;QACpE,4DAA4D;QAC5D,6DAA6D;QAC7D,aAAa,CACX,2BAA2B,EAC3B,GAAG,EAAE,CAAC,MAAM,CAAC,oCAAoC,CAAC,EAClD,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,EAAE,eAAe,EAAE,CACrD;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Display-mode overrides + pricing settings (US7 / T087 + T088).
|
|
4
|
+
*
|
|
5
|
+
* Single admin landing page with:
|
|
6
|
+
* - The two `pricing.*` settings keys at the top, edited inline.
|
|
7
|
+
* - A scope tab strip and a table per scope of every override on
|
|
8
|
+
* the platform; rows can be edited (mode dropdown) or removed
|
|
9
|
+
* (revert to "inherit" via PUT { mode: 'inherit' }).
|
|
10
|
+
* - An "Add override" button that picks an organization / category
|
|
11
|
+
* / product (depending on the active scope) and lets the operator
|
|
12
|
+
* set the initial mode.
|
|
13
|
+
*
|
|
14
|
+
* The settings keys come from the existing settings module
|
|
15
|
+
* (`/api/v1/admin/settings/<code>`); the override endpoints live under
|
|
16
|
+
* `/api/v1/admin/pricing/display-mode-overrides`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function DisplayModeOverridesPage(): ReactNode;
|
|
19
|
+
/**
|
|
20
|
+
* The registry loads a route component through a dynamic-import factory and
|
|
21
|
+
* reads its default export (feature 091, FR-013). The named export stays: it is
|
|
22
|
+
* the spelling this module's own code and its tests use.
|
|
23
|
+
*/
|
|
24
|
+
export default DisplayModeOverridesPage;
|
|
25
|
+
//# sourceMappingURL=DisplayModeOverridesPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DisplayModeOverridesPage.d.ts","sourceRoot":"","sources":["../../../src/admin/pages/DisplayModeOverridesPage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6C,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAyClF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,IAAI,SAAS,CA0QpD;AAiSD;;;;GAIG;AACH,eAAe,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { Link } from 'react-router-dom';
|
|
4
|
+
import { ChevronLeft, Eye, EyeOff, Filter, Save, Search, Settings as SettingsIcon, Trash2, } from 'lucide-react';
|
|
5
|
+
import { ApiError, apiClient, normalize } from '@endora-commerce/admin-kit/lib';
|
|
6
|
+
import { useTranslation } from '@endora-commerce/admin-kit/i18n';
|
|
7
|
+
/**
|
|
8
|
+
* Display-mode overrides + pricing settings (US7 / T087 + T088).
|
|
9
|
+
*
|
|
10
|
+
* Single admin landing page with:
|
|
11
|
+
* - The two `pricing.*` settings keys at the top, edited inline.
|
|
12
|
+
* - A scope tab strip and a table per scope of every override on
|
|
13
|
+
* the platform; rows can be edited (mode dropdown) or removed
|
|
14
|
+
* (revert to "inherit" via PUT { mode: 'inherit' }).
|
|
15
|
+
* - An "Add override" button that picks an organization / category
|
|
16
|
+
* / product (depending on the active scope) and lets the operator
|
|
17
|
+
* set the initial mode.
|
|
18
|
+
*
|
|
19
|
+
* The settings keys come from the existing settings module
|
|
20
|
+
* (`/api/v1/admin/settings/<code>`); the override endpoints live under
|
|
21
|
+
* `/api/v1/admin/pricing/display-mode-overrides`.
|
|
22
|
+
*/
|
|
23
|
+
export function DisplayModeOverridesPage() {
|
|
24
|
+
const t = useTranslation('core');
|
|
25
|
+
const MODE_LABEL = {
|
|
26
|
+
gross_only: t('priceLists.displayMode.grossOnly'),
|
|
27
|
+
net_only: t('priceLists.displayMode.netOnly'),
|
|
28
|
+
both: t('priceLists.displayMode.both'),
|
|
29
|
+
none: t('priceLists.displayMode.none'),
|
|
30
|
+
};
|
|
31
|
+
const SCOPE_LABEL = {
|
|
32
|
+
organization: t('priceLists.displayModes.scope.organization'),
|
|
33
|
+
category: t('priceLists.displayModes.scope.category'),
|
|
34
|
+
product: t('priceLists.displayModes.scope.product'),
|
|
35
|
+
};
|
|
36
|
+
const [scope, setScope] = useState('organization');
|
|
37
|
+
const [overrides, setOverrides] = useState([]);
|
|
38
|
+
const [loading, setLoading] = useState(true);
|
|
39
|
+
const [error, setError] = useState(null);
|
|
40
|
+
const [info, setInfo] = useState(null);
|
|
41
|
+
const [picker, setPicker] = useState(false);
|
|
42
|
+
const [search, setSearch] = useState('');
|
|
43
|
+
const [orgs, setOrgs] = useState({});
|
|
44
|
+
const [cats, setCats] = useState({});
|
|
45
|
+
const [products, setProducts] = useState({});
|
|
46
|
+
const refresh = useCallback(async () => {
|
|
47
|
+
setLoading(true);
|
|
48
|
+
setError(null);
|
|
49
|
+
try {
|
|
50
|
+
const res = await apiClient.get(`/api/v1/admin/pricing/display-mode-overrides?scope=${scope}`);
|
|
51
|
+
setOverrides(res.data.items);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
setError(err instanceof ApiError ? err.envelope.error.message : t('priceLists.displayModes.error.load'));
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
setLoading(false);
|
|
58
|
+
}
|
|
59
|
+
}, [scope, t]);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
void refresh();
|
|
62
|
+
}, [refresh]);
|
|
63
|
+
// Eagerly load name dictionaries the first time the page is opened so the
|
|
64
|
+
// table can render labels instead of bare UUIDs. Each lookup endpoint is
|
|
65
|
+
// already paginated/bounded — these are admin-side dictionaries.
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
void (async () => {
|
|
68
|
+
try {
|
|
69
|
+
const [orgsRes, catsRes, prodRes] = await Promise.all([
|
|
70
|
+
apiClient.get('/api/v1/admin/pricing/rule-targets/organizations?limit=200'),
|
|
71
|
+
apiClient.get('/api/v1/admin/pricing/rule-targets/categories'),
|
|
72
|
+
apiClient.get('/api/v1/admin/catalog/products'),
|
|
73
|
+
]);
|
|
74
|
+
setOrgs(Object.fromEntries(orgsRes.data.items.map((o) => [o.id, o])));
|
|
75
|
+
setCats(Object.fromEntries(catsRes.data.items.map((c) => [c.id, c])));
|
|
76
|
+
setProducts(Object.fromEntries(prodRes.data.map((p) => [p.id, p])));
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// dictionaries are decorative — table still renders raw IDs on failure
|
|
80
|
+
}
|
|
81
|
+
})();
|
|
82
|
+
}, []);
|
|
83
|
+
const labelFor = (row) => {
|
|
84
|
+
if (row.scope === 'organization')
|
|
85
|
+
return orgs[row.targetId]?.name ?? row.targetId;
|
|
86
|
+
if (row.scope === 'category')
|
|
87
|
+
return cats[row.targetId]?.name ?? row.targetId;
|
|
88
|
+
return products[row.targetId]?.name ?? row.targetId;
|
|
89
|
+
};
|
|
90
|
+
const subtitleFor = (row) => {
|
|
91
|
+
if (row.scope === 'organization')
|
|
92
|
+
return orgs[row.targetId]?.taxId ?? '';
|
|
93
|
+
if (row.scope === 'category')
|
|
94
|
+
return cats[row.targetId]?.slug ?? '';
|
|
95
|
+
return products[row.targetId]?.sku ?? '';
|
|
96
|
+
};
|
|
97
|
+
const filtered = useMemo(() => {
|
|
98
|
+
const q = normalize(search);
|
|
99
|
+
if (q === '')
|
|
100
|
+
return overrides;
|
|
101
|
+
return overrides.filter((row) => {
|
|
102
|
+
const label = normalize(labelFor(row));
|
|
103
|
+
const subtitle = normalize(subtitleFor(row));
|
|
104
|
+
return label.includes(q) || subtitle.includes(q) || normalize(row.targetId).includes(q);
|
|
105
|
+
});
|
|
106
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
107
|
+
}, [overrides, search, orgs, cats, products]);
|
|
108
|
+
const handleUpsert = async (targetId, mode) => {
|
|
109
|
+
setError(null);
|
|
110
|
+
try {
|
|
111
|
+
await apiClient.put(`/api/v1/admin/pricing/display-mode-overrides/${scope}/${encodeURIComponent(targetId)}`, { mode });
|
|
112
|
+
setInfo(mode === 'inherit' ? t('priceLists.displayModes.info.removed') : t('priceLists.displayModes.info.saved'));
|
|
113
|
+
await refresh();
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
setError(err instanceof ApiError ? err.envelope.error.message : t('priceLists.displayModes.error.save'));
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
return (_jsxs("div", { className: "b2b-page b2b-page--wide", children: [_jsx("div", { className: "b2b-page-head", children: _jsxs("div", { className: "b2b-grow", children: [_jsxs(Link, { to: "/price-lists", className: "b2b-page-head__back", children: [_jsx(ChevronLeft, { size: 14 }), " ", t('priceLists.page.title')] }), _jsx("div", { className: "b2b-page-head__title", children: t('priceLists.displayModes.title') }), _jsx("div", { className: "b2b-page-head__sub", children: t('priceLists.displayModes.description') })] }) }), info ? (_jsx(Banner, { kind: "info", message: info, onDismiss: () => setInfo(null) })) : null, error ? (_jsx(Banner, { kind: "error", message: error, onDismiss: () => setError(null) })) : null, _jsx(PricingSettingsCard, { onError: setError, onInfo: setInfo }), _jsxs("div", { className: "b2b-card", style: { marginTop: 16 }, children: [_jsxs("div", { className: "b2b-row", style: {
|
|
120
|
+
padding: '8px 12px',
|
|
121
|
+
borderBottom: '1px solid var(--border-color)',
|
|
122
|
+
gap: 6,
|
|
123
|
+
alignItems: 'center',
|
|
124
|
+
}, children: [['organization', 'category', 'product'].map((s) => (_jsx("button", { type: "button", className: "b2b-btn b2b-btn--sm", onClick: () => setScope(s), style: {
|
|
125
|
+
background: scope === s ? 'var(--primary-color)' : 'transparent',
|
|
126
|
+
color: scope === s ? '#fff' : 'var(--fg-default)',
|
|
127
|
+
border: `1px solid ${scope === s ? 'var(--primary-color)' : 'var(--border-color)'}`,
|
|
128
|
+
}, children: SCOPE_LABEL[s] }, s))), _jsx("div", { className: "b2b-grow" }), _jsxs("div", { className: "b2b-input-wrap", style: { width: 240 }, children: [_jsx(Search, { size: 14, className: "lead" }), _jsx("input", { className: "b2b-field b2b-field--addon", placeholder: t('priceLists.displayModes.searchPlaceholder', { scope: SCOPE_LABEL[scope].toLowerCase() }), value: search, onChange: (e) => setSearch(e.target.value) })] }), _jsxs("button", { type: "button", className: "b2b-btn b2b-btn--primary b2b-btn--sm", onClick: () => setPicker(true), children: [_jsx(Filter, { size: 12 }), " ", t('priceLists.displayModes.addOverride')] })] }), _jsx("div", { className: "b2b-card__body b2b-card__body--flush", children: loading ? (_jsx("div", { style: { padding: 32, color: 'var(--fg-muted)', fontSize: 13 }, children: t('priceLists.loading') })) : filtered.length === 0 ? (_jsxs("div", { className: "b2b-empty", children: [_jsx("div", { className: "b2b-empty__icon", children: _jsx(Eye, { size: 20 }) }), _jsx("div", { className: "b2b-empty__title", children: search.trim()
|
|
129
|
+
? t('priceLists.displayModes.empty.filtered', { scope: SCOPE_LABEL[scope].toLowerCase() })
|
|
130
|
+
: t('priceLists.displayModes.empty.none', { scope: SCOPE_LABEL[scope].toLowerCase() }) }), _jsx("div", { className: "b2b-empty__sub", children: t('priceLists.displayModes.empty.subtitle') })] })) : (_jsxs("table", { className: "b2b-tbl", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: SCOPE_LABEL[scope] }), _jsx("th", { children: t('priceLists.displayModes.column.mode') }), _jsx("th", { children: t('priceLists.displayModes.column.updated') }), _jsx("th", {})] }) }), _jsx("tbody", { children: filtered.map((row) => (_jsxs("tr", { children: [_jsxs("td", { children: [_jsx("div", { style: { fontSize: 13, fontWeight: 500 }, children: labelFor(row) }), _jsx("div", { className: "b2b-mono", style: { fontSize: 11, color: 'var(--fg-muted)' }, children: subtitleFor(row) || row.targetId.slice(0, 8) })] }), _jsx("td", { children: _jsx("select", { className: "b2b-field", value: row.mode, onChange: (e) => {
|
|
131
|
+
void handleUpsert(row.targetId, e.target.value);
|
|
132
|
+
}, children: Object.keys(MODE_LABEL).map((m) => (_jsx("option", { value: m, children: MODE_LABEL[m] }, m))) }) }), _jsx("td", { children: _jsx("span", { style: { fontSize: 12, color: 'var(--fg-muted)' }, children: new Date(row.updatedAt).toLocaleString() }) }), _jsx("td", { children: _jsxs("button", { type: "button", className: "b2b-btn b2b-btn--ghost b2b-btn--sm", style: { color: 'hsl(8 80% 50%)' }, onClick: () => {
|
|
133
|
+
void handleUpsert(row.targetId, 'inherit');
|
|
134
|
+
}, children: [_jsx(Trash2, { size: 12 }), " ", t('priceLists.displayModes.remove')] }) })] }, row.targetId))) })] })) })] }), picker ? (_jsx(AddOverrideDialog, { scope: scope, existing: new Set(overrides.map((o) => o.targetId)), orgs: orgs, cats: cats, products: products, onClose: () => setPicker(false), onPick: (targetId, mode) => {
|
|
135
|
+
setPicker(false);
|
|
136
|
+
void handleUpsert(targetId, mode);
|
|
137
|
+
} })) : null] }));
|
|
138
|
+
}
|
|
139
|
+
function PricingSettingsCard({ onError, onInfo, }) {
|
|
140
|
+
const t = useTranslation('core');
|
|
141
|
+
const MODE_LABEL = {
|
|
142
|
+
gross_only: t('priceLists.displayMode.grossOnly'),
|
|
143
|
+
net_only: t('priceLists.displayMode.netOnly'),
|
|
144
|
+
both: t('priceLists.displayMode.both'),
|
|
145
|
+
none: t('priceLists.displayMode.none'),
|
|
146
|
+
};
|
|
147
|
+
const [defaultMode, setDefaultMode] = useState('gross_only');
|
|
148
|
+
const [unauthMode, setUnauthMode] = useState('gross_only');
|
|
149
|
+
const [loading, setLoading] = useState(true);
|
|
150
|
+
const [savingKey, setSavingKey] = useState(null);
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
void (async () => {
|
|
153
|
+
try {
|
|
154
|
+
const [a, b] = await Promise.all([
|
|
155
|
+
apiClient.get('/api/v1/admin/settings/pricing.default_display_mode'),
|
|
156
|
+
apiClient.get('/api/v1/admin/settings/pricing.unauthenticated_display_mode'),
|
|
157
|
+
]);
|
|
158
|
+
if (a.defaultValue)
|
|
159
|
+
setDefaultMode(a.defaultValue);
|
|
160
|
+
if (b.defaultValue)
|
|
161
|
+
setUnauthMode(b.defaultValue);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// ignore — leave defaults; the operator can still write
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
setLoading(false);
|
|
168
|
+
}
|
|
169
|
+
})();
|
|
170
|
+
}, []);
|
|
171
|
+
const save = async (key, value) => {
|
|
172
|
+
setSavingKey(key);
|
|
173
|
+
try {
|
|
174
|
+
await apiClient.put(`/api/v1/admin/settings/${encodeURIComponent(key)}/value`, {
|
|
175
|
+
scope: 'all',
|
|
176
|
+
value,
|
|
177
|
+
});
|
|
178
|
+
onInfo(t('priceLists.displayModes.settings.info.saved'));
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
onError(err instanceof ApiError ? err.envelope.error.message : t('priceLists.displayModes.error.save'));
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
setSavingKey(null);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
return (_jsxs("div", { className: "b2b-card", style: { padding: 16 }, children: [_jsxs("div", { className: "b2b-row", style: { alignItems: 'center', gap: 8, marginBottom: 8 }, children: [_jsx(SettingsIcon, { size: 14 }), _jsx("div", { style: { fontSize: 13, fontWeight: 600 }, children: t('priceLists.displayModes.settings.title') }), _jsx("span", { className: "b2b-help", children: t('priceLists.displayModes.settings.subtitle') })] }), loading ? (_jsx("div", { className: "b2b-help", children: t('priceLists.loading') })) : (_jsxs("div", { className: "b2b-row", style: { gap: 16, alignItems: 'flex-end' }, children: [_jsxs("div", { className: "b2b-grow", children: [_jsx("label", { className: "b2b-label", htmlFor: "pl-default-display", children: t('priceLists.displayModes.settings.defaultLabel') }), _jsx("select", { id: "pl-default-display", className: "b2b-field", value: defaultMode, onChange: (e) => {
|
|
188
|
+
const v = e.target.value;
|
|
189
|
+
setDefaultMode(v);
|
|
190
|
+
void save('pricing.default_display_mode', v);
|
|
191
|
+
}, disabled: savingKey === 'pricing.default_display_mode', children: Object.keys(MODE_LABEL).map((m) => (_jsx("option", { value: m, children: MODE_LABEL[m] }, m))) })] }), _jsxs("div", { className: "b2b-grow", children: [_jsx("label", { className: "b2b-label", htmlFor: "pl-unauth-display", children: t('priceLists.displayModes.settings.unauthLabel') }), _jsx("select", { id: "pl-unauth-display", className: "b2b-field", value: unauthMode, onChange: (e) => {
|
|
192
|
+
const v = e.target.value;
|
|
193
|
+
setUnauthMode(v);
|
|
194
|
+
void save('pricing.unauthenticated_display_mode', v);
|
|
195
|
+
}, disabled: savingKey === 'pricing.unauthenticated_display_mode', children: Object.keys(MODE_LABEL).map((m) => (_jsx("option", { value: m, children: MODE_LABEL[m] }, m))) })] })] }))] }));
|
|
196
|
+
}
|
|
197
|
+
function Banner({ kind, message, onDismiss, }) {
|
|
198
|
+
const t = useTranslation('core');
|
|
199
|
+
const palette = kind === 'error'
|
|
200
|
+
? { bg: 'var(--danger-soft)', fg: 'var(--danger-soft-fg)', border: 'hsl(8 80% 85%)' }
|
|
201
|
+
: { bg: 'var(--success-soft)', fg: 'var(--success-soft-fg)', border: 'hsl(142 50% 80%)' };
|
|
202
|
+
return (_jsxs("div", { className: "b2b-card b2b-row", style: {
|
|
203
|
+
background: palette.bg,
|
|
204
|
+
color: palette.fg,
|
|
205
|
+
padding: 12,
|
|
206
|
+
marginBottom: 16,
|
|
207
|
+
border: `1px solid ${palette.border}`,
|
|
208
|
+
gap: 8,
|
|
209
|
+
alignItems: 'center',
|
|
210
|
+
}, children: [_jsx("div", { className: "b2b-grow", children: message }), _jsx("button", { type: "button", className: "b2b-btn b2b-btn--ghost b2b-btn--sm", onClick: onDismiss, children: t('priceLists.dismiss') })] }));
|
|
211
|
+
}
|
|
212
|
+
function AddOverrideDialog(props) {
|
|
213
|
+
const t = useTranslation('core');
|
|
214
|
+
const MODE_LABEL = {
|
|
215
|
+
gross_only: t('priceLists.displayMode.grossOnly'),
|
|
216
|
+
net_only: t('priceLists.displayMode.netOnly'),
|
|
217
|
+
both: t('priceLists.displayMode.both'),
|
|
218
|
+
none: t('priceLists.displayMode.none'),
|
|
219
|
+
};
|
|
220
|
+
const SCOPE_LABEL = {
|
|
221
|
+
organization: t('priceLists.displayModes.scope.organization'),
|
|
222
|
+
category: t('priceLists.displayModes.scope.category'),
|
|
223
|
+
product: t('priceLists.displayModes.scope.product'),
|
|
224
|
+
};
|
|
225
|
+
const { scope, existing, orgs, cats, products, onClose, onPick } = props;
|
|
226
|
+
const [search, setSearch] = useState('');
|
|
227
|
+
const [picked, setPicked] = useState(null);
|
|
228
|
+
const [mode, setMode] = useState('gross_only');
|
|
229
|
+
const candidates = useMemo(() => {
|
|
230
|
+
const rows = [];
|
|
231
|
+
if (scope === 'organization') {
|
|
232
|
+
for (const o of Object.values(orgs))
|
|
233
|
+
rows.push({ id: o.id, label: o.name, subtitle: o.taxId });
|
|
234
|
+
}
|
|
235
|
+
else if (scope === 'category') {
|
|
236
|
+
for (const c of Object.values(cats))
|
|
237
|
+
rows.push({ id: c.id, label: c.name, subtitle: c.slug });
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
for (const p of Object.values(products))
|
|
241
|
+
rows.push({ id: p.id, label: p.name, subtitle: p.sku });
|
|
242
|
+
}
|
|
243
|
+
rows.sort((a, b) => a.label.localeCompare(b.label));
|
|
244
|
+
const q = normalize(search);
|
|
245
|
+
if (q === '')
|
|
246
|
+
return rows.filter((r) => !existing.has(r.id));
|
|
247
|
+
return rows.filter((r) => !existing.has(r.id) &&
|
|
248
|
+
(normalize(r.label).includes(q) || normalize(r.subtitle).includes(q)));
|
|
249
|
+
}, [scope, orgs, cats, products, existing, search]);
|
|
250
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "b2b-scrim", onClick: onClose }), _jsxs("aside", { className: "b2b-drawer", role: "dialog", "aria-modal": "true", children: [_jsxs("div", { className: "b2b-drawer__head", children: [_jsx("div", { className: "b2b-drawer__title", children: t('priceLists.displayModes.addDialog.title') }), _jsx("div", { className: "b2b-card__sub", children: t('priceLists.displayModes.addDialog.subtitle', { scope: SCOPE_LABEL[scope].toLowerCase() }) })] }), _jsxs("div", { className: "b2b-drawer__body", children: [_jsxs("div", { className: "b2b-input-wrap", style: { marginBottom: 8 }, children: [_jsx(Search, { size: 14, className: "lead" }), _jsx("input", { autoFocus: true, className: "b2b-field b2b-field--addon", placeholder: t('priceLists.displayModes.searchPlaceholder', { scope: SCOPE_LABEL[scope].toLowerCase() }), value: search, onChange: (e) => setSearch(e.target.value) })] }), candidates.length === 0 ? (_jsx("div", { className: "b2b-help", children: t('priceLists.displayModes.addDialog.noMore', { scope: SCOPE_LABEL[scope].toLowerCase() }) })) : (_jsx("ul", { style: { margin: 0, padding: 0, listStyle: 'none', maxHeight: 320, overflow: 'auto' }, children: candidates.slice(0, 200).map((c) => (_jsxs("li", { onClick: () => setPicked(c.id), style: {
|
|
251
|
+
padding: '6px 8px',
|
|
252
|
+
cursor: 'pointer',
|
|
253
|
+
background: picked === c.id ? 'var(--primary-soft)' : 'transparent',
|
|
254
|
+
borderLeft: picked === c.id ? '3px solid var(--primary-color)' : '3px solid transparent',
|
|
255
|
+
}, children: [_jsx("div", { style: { fontSize: 13, fontWeight: 500 }, children: c.label }), _jsx("div", { className: "b2b-mono", style: { fontSize: 11, color: 'var(--fg-muted)' }, children: c.subtitle })] }, c.id))) })), _jsxs("div", { style: { marginTop: 14 }, children: [_jsx("label", { className: "b2b-label", htmlFor: "ov-mode", children: t('priceLists.displayModes.addDialog.modeLabel') }), _jsx("select", { id: "ov-mode", className: "b2b-field", value: mode, onChange: (e) => setMode(e.target.value), children: Object.keys(MODE_LABEL).map((m) => (_jsx("option", { value: m, children: MODE_LABEL[m] }, m))) }), mode === 'none' ? (_jsxs("div", { className: "b2b-help", style: { marginTop: 4, color: 'hsl(217 70% 30%)', display: 'flex', gap: 4, alignItems: 'center' }, children: [_jsx(EyeOff, { size: 12 }), " ", t('priceLists.displayModes.addDialog.noneHint')] })) : null] })] }), _jsxs("div", { className: "b2b-drawer__foot", children: [_jsx("button", { type: "button", className: "b2b-btn b2b-btn--ghost", onClick: onClose, children: t('priceLists.displayModes.addDialog.cancel') }), _jsxs("button", { type: "button", className: "b2b-btn b2b-btn--primary", disabled: !picked, onClick: () => {
|
|
256
|
+
if (picked)
|
|
257
|
+
onPick(picked, mode);
|
|
258
|
+
}, children: [_jsx(Save, { size: 13 }), " ", t('priceLists.displayModes.addDialog.save')] })] })] })] }));
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* The registry loads a route component through a dynamic-import factory and
|
|
262
|
+
* reads its default export (feature 091, FR-013). The named export stays: it is
|
|
263
|
+
* the spelling this module's own code and its tests use.
|
|
264
|
+
*/
|
|
265
|
+
export default DisplayModeOverridesPage;
|
|
266
|
+
//# sourceMappingURL=DisplayModeOverridesPage.js.map
|