@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.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/dist/admin/components/ApplicationRuleBuilder.d.ts +24 -0
  4. package/dist/admin/components/ApplicationRuleBuilder.d.ts.map +1 -0
  5. package/dist/admin/components/ApplicationRuleBuilder.js +249 -0
  6. package/dist/admin/components/ApplicationRuleBuilder.js.map +1 -0
  7. package/dist/admin/components/BracketGrid.d.ts +29 -0
  8. package/dist/admin/components/BracketGrid.d.ts.map +1 -0
  9. package/dist/admin/components/BracketGrid.js +252 -0
  10. package/dist/admin/components/BracketGrid.js.map +1 -0
  11. package/dist/admin/components/DisplayModeOverrideRow.d.ts +46 -0
  12. package/dist/admin/components/DisplayModeOverrideRow.d.ts.map +1 -0
  13. package/dist/admin/components/DisplayModeOverrideRow.js +109 -0
  14. package/dist/admin/components/DisplayModeOverrideRow.js.map +1 -0
  15. package/dist/admin/components/LinkedPriceListsPanel.d.ts +55 -0
  16. package/dist/admin/components/LinkedPriceListsPanel.d.ts.map +1 -0
  17. package/dist/admin/components/LinkedPriceListsPanel.js +131 -0
  18. package/dist/admin/components/LinkedPriceListsPanel.js.map +1 -0
  19. package/dist/admin/index.d.ts +34 -0
  20. package/dist/admin/index.d.ts.map +1 -0
  21. package/dist/admin/index.js +119 -0
  22. package/dist/admin/index.js.map +1 -0
  23. package/dist/admin/pages/DisplayModeOverridesPage.d.ts +25 -0
  24. package/dist/admin/pages/DisplayModeOverridesPage.d.ts.map +1 -0
  25. package/dist/admin/pages/DisplayModeOverridesPage.js +266 -0
  26. package/dist/admin/pages/DisplayModeOverridesPage.js.map +1 -0
  27. package/dist/admin/pages/PriceListDetailPage.d.ts +23 -0
  28. package/dist/admin/pages/PriceListDetailPage.d.ts.map +1 -0
  29. package/dist/admin/pages/PriceListDetailPage.js +421 -0
  30. package/dist/admin/pages/PriceListDetailPage.js.map +1 -0
  31. package/dist/admin/pages/PriceListsPage.d.ts +17 -0
  32. package/dist/admin/pages/PriceListsPage.d.ts.map +1 -0
  33. package/dist/admin/pages/PriceListsPage.js +198 -0
  34. package/dist/admin/pages/PriceListsPage.js.map +1 -0
  35. package/dist/admin/zones/CategoryDisplayMode.d.ts +27 -0
  36. package/dist/admin/zones/CategoryDisplayMode.d.ts.map +1 -0
  37. package/dist/admin/zones/CategoryDisplayMode.js +11 -0
  38. package/dist/admin/zones/CategoryDisplayMode.js.map +1 -0
  39. package/dist/admin/zones/OrganizationDisplayMode.d.ts +36 -0
  40. package/dist/admin/zones/OrganizationDisplayMode.d.ts.map +1 -0
  41. package/dist/admin/zones/OrganizationDisplayMode.js +12 -0
  42. package/dist/admin/zones/OrganizationDisplayMode.js.map +1 -0
  43. package/dist/admin/zones/ProductLinkedPriceLists.d.ts +16 -0
  44. package/dist/admin/zones/ProductLinkedPriceLists.d.ts.map +1 -0
  45. package/dist/admin/zones/ProductLinkedPriceLists.js +7 -0
  46. package/dist/admin/zones/ProductLinkedPriceLists.js.map +1 -0
  47. package/dist/backend/entities/price-display-mode-override.entity.d.ts +23 -0
  48. package/dist/backend/entities/price-display-mode-override.entity.d.ts.map +1 -0
  49. package/dist/backend/entities/price-display-mode-override.entity.js +58 -0
  50. package/dist/backend/entities/price-display-mode-override.entity.js.map +1 -0
  51. package/dist/backend/entities/price-list-price-bracket.entity.d.ts +26 -0
  52. package/dist/backend/entities/price-list-price-bracket.entity.d.ts.map +1 -0
  53. package/dist/backend/entities/price-list-price-bracket.entity.js +73 -0
  54. package/dist/backend/entities/price-list-price-bracket.entity.js.map +1 -0
  55. package/dist/backend/entities/price-list-product.entity.d.ts +15 -0
  56. package/dist/backend/entities/price-list-product.entity.d.ts.map +1 -0
  57. package/dist/backend/entities/price-list-product.entity.js +43 -0
  58. package/dist/backend/entities/price-list-product.entity.js.map +1 -0
  59. package/dist/backend/entities/price-list.entity.d.ts +34 -0
  60. package/dist/backend/entities/price-list.entity.d.ts.map +1 -0
  61. package/dist/backend/entities/price-list.entity.js +115 -0
  62. package/dist/backend/entities/price-list.entity.js.map +1 -0
  63. package/dist/backend/index.d.ts +125 -0
  64. package/dist/backend/index.d.ts.map +1 -0
  65. package/dist/backend/index.js +179 -0
  66. package/dist/backend/index.js.map +1 -0
  67. package/dist/backend/plugin.d.ts +68 -0
  68. package/dist/backend/plugin.d.ts.map +1 -0
  69. package/dist/backend/plugin.js +88 -0
  70. package/dist/backend/plugin.js.map +1 -0
  71. package/dist/backend/routes.d.ts +40 -0
  72. package/dist/backend/routes.d.ts.map +1 -0
  73. package/dist/backend/routes.js +447 -0
  74. package/dist/backend/routes.js.map +1 -0
  75. package/dist/backend/routes.storefront.d.ts +47 -0
  76. package/dist/backend/routes.storefront.d.ts.map +1 -0
  77. package/dist/backend/routes.storefront.js +131 -0
  78. package/dist/backend/routes.storefront.js.map +1 -0
  79. package/dist/backend/services/application-rule-evaluator.d.ts +25 -0
  80. package/dist/backend/services/application-rule-evaluator.d.ts.map +1 -0
  81. package/dist/backend/services/application-rule-evaluator.js +2 -0
  82. package/dist/backend/services/application-rule-evaluator.js.map +1 -0
  83. package/dist/backend/services/audit-references.d.ts +11 -0
  84. package/dist/backend/services/audit-references.d.ts.map +1 -0
  85. package/dist/backend/services/audit-references.js +23 -0
  86. package/dist/backend/services/audit-references.js.map +1 -0
  87. package/dist/backend/services/default-price-list-migration.d.ts +70 -0
  88. package/dist/backend/services/default-price-list-migration.d.ts.map +1 -0
  89. package/dist/backend/services/default-price-list-migration.js +177 -0
  90. package/dist/backend/services/default-price-list-migration.js.map +1 -0
  91. package/dist/backend/services/display-mode-resolver.d.ts +78 -0
  92. package/dist/backend/services/display-mode-resolver.d.ts.map +1 -0
  93. package/dist/backend/services/display-mode-resolver.js +71 -0
  94. package/dist/backend/services/display-mode-resolver.js.map +1 -0
  95. package/dist/backend/services/listing-price-chain.d.ts +26 -0
  96. package/dist/backend/services/listing-price-chain.d.ts.map +1 -0
  97. package/dist/backend/services/listing-price-chain.js +55 -0
  98. package/dist/backend/services/listing-price-chain.js.map +1 -0
  99. package/dist/backend/services/price-bracket-resolver.d.ts +24 -0
  100. package/dist/backend/services/price-bracket-resolver.d.ts.map +1 -0
  101. package/dist/backend/services/price-bracket-resolver.js +16 -0
  102. package/dist/backend/services/price-bracket-resolver.js.map +1 -0
  103. package/dist/backend/services/price-list-candidate-vector.d.ts +116 -0
  104. package/dist/backend/services/price-list-candidate-vector.d.ts.map +1 -0
  105. package/dist/backend/services/price-list-candidate-vector.js +133 -0
  106. package/dist/backend/services/price-list-candidate-vector.js.map +1 -0
  107. package/dist/backend/services/price-list-currency-reference.d.ts +12 -0
  108. package/dist/backend/services/price-list-currency-reference.d.ts.map +1 -0
  109. package/dist/backend/services/price-list-currency-reference.js +24 -0
  110. package/dist/backend/services/price-list-currency-reference.js.map +1 -0
  111. package/dist/backend/services/price-list-read-port.d.ts +29 -0
  112. package/dist/backend/services/price-list-read-port.d.ts.map +1 -0
  113. package/dist/backend/services/price-list-read-port.js +64 -0
  114. package/dist/backend/services/price-list-read-port.js.map +1 -0
  115. package/dist/backend/services/price-list-resolver.d.ts +26 -0
  116. package/dist/backend/services/price-list-resolver.d.ts.map +1 -0
  117. package/dist/backend/services/price-list-resolver.js +37 -0
  118. package/dist/backend/services/price-list-resolver.js.map +1 -0
  119. package/dist/backend/services/price-list-service.d.ts +431 -0
  120. package/dist/backend/services/price-list-service.d.ts.map +1 -0
  121. package/dist/backend/services/price-list-service.js +1238 -0
  122. package/dist/backend/services/price-list-service.js.map +1 -0
  123. package/dist/backend/services/price-list-status-worker.d.ts +28 -0
  124. package/dist/backend/services/price-list-status-worker.d.ts.map +1 -0
  125. package/dist/backend/services/price-list-status-worker.js +38 -0
  126. package/dist/backend/services/price-list-status-worker.js.map +1 -0
  127. package/dist/backend/services/pricing-cache.d.ts +53 -0
  128. package/dist/backend/services/pricing-cache.d.ts.map +1 -0
  129. package/dist/backend/services/pricing-cache.js +88 -0
  130. package/dist/backend/services/pricing-cache.js.map +1 -0
  131. package/dist/backend/services/pricing-service.d.ts +304 -0
  132. package/dist/backend/services/pricing-service.d.ts.map +1 -0
  133. package/dist/backend/services/pricing-service.interface.d.ts +151 -0
  134. package/dist/backend/services/pricing-service.interface.d.ts.map +1 -0
  135. package/dist/backend/services/pricing-service.interface.js +10 -0
  136. package/dist/backend/services/pricing-service.interface.js.map +1 -0
  137. package/dist/backend/services/pricing-service.js +803 -0
  138. package/dist/backend/services/pricing-service.js.map +1 -0
  139. package/dist/backend/services/unit-price-ordering.d.ts +121 -0
  140. package/dist/backend/services/unit-price-ordering.d.ts.map +1 -0
  141. package/dist/backend/services/unit-price-ordering.js +164 -0
  142. package/dist/backend/services/unit-price-ordering.js.map +1 -0
  143. package/dist/manifest.d.ts +218 -0
  144. package/dist/manifest.d.ts.map +1 -0
  145. package/dist/manifest.js +228 -0
  146. package/dist/manifest.js.map +1 -0
  147. package/dist/migrations/20260426T075235_price_lists_pricing_init.d.ts +24 -0
  148. package/dist/migrations/20260426T075235_price_lists_pricing_init.d.ts.map +1 -0
  149. package/dist/migrations/20260426T075235_price_lists_pricing_init.js +89 -0
  150. package/dist/migrations/20260426T075235_price_lists_pricing_init.js.map +1 -0
  151. package/dist/migrations/20260504T125655_price_lists_engine.d.ts +6 -0
  152. package/dist/migrations/20260504T125655_price_lists_engine.d.ts.map +1 -0
  153. package/dist/migrations/20260504T125655_price_lists_engine.js +319 -0
  154. package/dist/migrations/20260504T125655_price_lists_engine.js.map +1 -0
  155. package/dist/migrations/20260817T055457_price_lists_single_system_price_list.d.ts +15 -0
  156. package/dist/migrations/20260817T055457_price_lists_single_system_price_list.d.ts.map +1 -0
  157. package/dist/migrations/20260817T055457_price_lists_single_system_price_list.js +58 -0
  158. package/dist/migrations/20260817T055457_price_lists_single_system_price_list.js.map +1 -0
  159. package/dist/migrations/20260821T135907_price_lists_unit_price_amount_index.d.ts +40 -0
  160. package/dist/migrations/20260821T135907_price_lists_unit_price_amount_index.d.ts.map +1 -0
  161. package/dist/migrations/20260821T135907_price_lists_unit_price_amount_index.js +46 -0
  162. package/dist/migrations/20260821T135907_price_lists_unit_price_amount_index.js.map +1 -0
  163. package/dist/migrations/index.d.ts +30 -0
  164. package/dist/migrations/index.d.ts.map +1 -0
  165. package/dist/migrations/index.js +35 -0
  166. package/dist/migrations/index.js.map +1 -0
  167. package/docs/price_lists.md +278 -0
  168. package/i18n/en.json +14 -0
  169. package/i18n/pl.json +14 -0
  170. package/package.json +99 -0
  171. 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":"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"}
@@ -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