@hartl-services/medusa-affiliate 0.3.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 (169) hide show
  1. package/.medusa/server/src/admin/index.js +3739 -0
  2. package/.medusa/server/src/admin/index.mjs +3738 -0
  3. package/.medusa/server/src/api/admin/affiliate-applications/[id]/approve/route.js +28 -0
  4. package/.medusa/server/src/api/admin/affiliate-applications/[id]/reject/route.js +19 -0
  5. package/.medusa/server/src/api/admin/affiliate-applications/route.js +23 -0
  6. package/.medusa/server/src/api/admin/affiliate-distribution-reports/query.js +171 -0
  7. package/.medusa/server/src/api/admin/affiliate-distribution-reports/route.js +13 -0
  8. package/.medusa/server/src/api/admin/affiliate-program-settings/route.js +27 -0
  9. package/.medusa/server/src/api/admin/affiliate-reports/[id]/route.js +45 -0
  10. package/.medusa/server/src/api/admin/affiliate-reports/route.js +27 -0
  11. package/.medusa/server/src/api/admin/affiliates/[id]/consignment-reminders/route.js +31 -0
  12. package/.medusa/server/src/api/admin/affiliates/[id]/consignment-transfers/route.js +41 -0
  13. package/.medusa/server/src/api/admin/affiliates/[id]/coupon-codes/[coupon_id]/reconcile/route.js +34 -0
  14. package/.medusa/server/src/api/admin/affiliates/[id]/coupon-codes/[coupon_id]/revoke/route.js +36 -0
  15. package/.medusa/server/src/api/admin/affiliates/[id]/coupon-codes/[coupon_id]/rotate/route.js +40 -0
  16. package/.medusa/server/src/api/admin/affiliates/[id]/coupon-codes/route.js +56 -0
  17. package/.medusa/server/src/api/admin/affiliates/[id]/distribution/route.js +45 -0
  18. package/.medusa/server/src/api/admin/affiliates/[id]/inactive/route.js +20 -0
  19. package/.medusa/server/src/api/admin/affiliates/[id]/route.js +50 -0
  20. package/.medusa/server/src/api/admin/affiliates/[id]/slugs/[slug_id]/route.js +19 -0
  21. package/.medusa/server/src/api/admin/affiliates/[id]/slugs/route.js +26 -0
  22. package/.medusa/server/src/api/admin/affiliates/route.js +43 -0
  23. package/.medusa/server/src/api/admin/consignment-report-response.js +36 -0
  24. package/.medusa/server/src/api/admin/consignment-reports/[id]/approve/route.js +25 -0
  25. package/.medusa/server/src/api/admin/consignment-reports/[id]/correct/route.js +18 -0
  26. package/.medusa/server/src/api/admin/consignment-reports/[id]/reject/route.js +15 -0
  27. package/.medusa/server/src/api/admin/consignment-reports/[id]/route.js +11 -0
  28. package/.medusa/server/src/api/admin/consignment-reports/route.js +27 -0
  29. package/.medusa/server/src/api/admin/customers/[id]/affiliate/route.js +50 -0
  30. package/.medusa/server/src/api/admin/orders/[id]/affiliate-attribution/route.js +30 -0
  31. package/.medusa/server/src/api/affiliate-coupon-http.js +152 -0
  32. package/.medusa/server/src/api/affiliate-referral-cookie.js +34 -0
  33. package/.medusa/server/src/api/affiliate-self-service-http.js +37 -0
  34. package/.medusa/server/src/api/middlewares.js +786 -0
  35. package/.medusa/server/src/api/store/affiliate-referrals/apply-coupon/route.js +47 -0
  36. package/.medusa/server/src/api/store/affiliate-referrals/capture/route.js +76 -0
  37. package/.medusa/server/src/api/store/affiliate-referrals/carts/[cart_id]/route.js +20 -0
  38. package/.medusa/server/src/api/store/affiliates/consignment-inventory/route.js +53 -0
  39. package/.medusa/server/src/api/store/affiliates/consignment-report-response.js +31 -0
  40. package/.medusa/server/src/api/store/affiliates/consignment-reports/[id]/submit/route.js +40 -0
  41. package/.medusa/server/src/api/store/affiliates/consignment-reports/route.js +43 -0
  42. package/.medusa/server/src/api/store/affiliates/coupon-labels/route.js +14 -0
  43. package/.medusa/server/src/api/store/affiliates/me/commissions/route.js +35 -0
  44. package/.medusa/server/src/api/store/affiliates/me/coupon-codes/[coupon_id]/revoke/route.js +36 -0
  45. package/.medusa/server/src/api/store/affiliates/me/coupon-codes/[coupon_id]/rotate/route.js +40 -0
  46. package/.medusa/server/src/api/store/affiliates/me/coupon-codes/route.js +55 -0
  47. package/.medusa/server/src/api/store/affiliates/me/dashboard/route.js +15 -0
  48. package/.medusa/server/src/api/store/affiliates/me/route.js +85 -0
  49. package/.medusa/server/src/api/store/affiliates/me/slugs/[slug_id]/route.js +29 -0
  50. package/.medusa/server/src/api/store/affiliates/me/slugs/route.js +34 -0
  51. package/.medusa/server/src/api/store/affiliates/own-order-discount/route.js +22 -0
  52. package/.medusa/server/src/api/store/affiliates/own-order-promotion/route.js +30 -0
  53. package/.medusa/server/src/api/store/affiliates/register/route.js +27 -0
  54. package/.medusa/server/src/api/store/affiliates/resolve-referral/route.js +34 -0
  55. package/.medusa/server/src/api/validators.js +52 -0
  56. package/.medusa/server/src/api/wire-response.js +10 -0
  57. package/.medusa/server/src/integration/email-events.js +51 -0
  58. package/.medusa/server/src/integrations/inventory-transfer/batch-management-transfer-adapter.js +36 -0
  59. package/.medusa/server/src/integrations/inventory-transfer/native-inventory-transfer-adapter.js +30 -0
  60. package/.medusa/server/src/integrations/inventory-transfer/resolve-inventory-transfer-adapter.js +46 -0
  61. package/.medusa/server/src/integrations/inventory-transfer/types.js +3 -0
  62. package/.medusa/server/src/jobs/affiliate-attribution-expiry.js +20 -0
  63. package/.medusa/server/src/jobs/affiliate-consignment-report-reminders.js +15 -0
  64. package/.medusa/server/src/jobs/affiliate-promotion-freshness.js +24 -0
  65. package/.medusa/server/src/lib/query-string.js +15 -0
  66. package/.medusa/server/src/modules/affiliate/domain/affiliate-attribution-policy.js +81 -0
  67. package/.medusa/server/src/modules/affiliate/domain/affiliate-coupon-policy.js +61 -0
  68. package/.medusa/server/src/modules/affiliate/domain/commission-policy.js +33 -0
  69. package/.medusa/server/src/modules/affiliate/domain/consignment-reminder-rules.js +74 -0
  70. package/.medusa/server/src/modules/affiliate/domain/email-normalization.js +20 -0
  71. package/.medusa/server/src/modules/affiliate/domain/own-order-coupon-code.js +37 -0
  72. package/.medusa/server/src/modules/affiliate/domain/plugin-options.js +40 -0
  73. package/.medusa/server/src/modules/affiliate/index.js +13 -0
  74. package/.medusa/server/src/modules/affiliate/migrations/Migration20260827085154.js +423 -0
  75. package/.medusa/server/src/modules/affiliate/models/affiliate-application.js +38 -0
  76. package/.medusa/server/src/modules/affiliate/models/affiliate-cart-referral.js +65 -0
  77. package/.medusa/server/src/modules/affiliate/models/affiliate-code-redemption.js +41 -0
  78. package/.medusa/server/src/modules/affiliate/models/affiliate-coupon.js +55 -0
  79. package/.medusa/server/src/modules/affiliate/models/affiliate-distribution-settings.js +22 -0
  80. package/.medusa/server/src/modules/affiliate/models/affiliate-order-attribution.js +49 -0
  81. package/.medusa/server/src/modules/affiliate/models/affiliate-program-settings.js +28 -0
  82. package/.medusa/server/src/modules/affiliate/models/affiliate-referral-session.js +34 -0
  83. package/.medusa/server/src/modules/affiliate/models/affiliate-slug.js +27 -0
  84. package/.medusa/server/src/modules/affiliate/models/affiliate.js +49 -0
  85. package/.medusa/server/src/modules/affiliate/models/commission-entry.js +62 -0
  86. package/.medusa/server/src/modules/affiliate/models/consignment-report-line.js +21 -0
  87. package/.medusa/server/src/modules/affiliate/models/consignment-report.js +31 -0
  88. package/.medusa/server/src/modules/affiliate/models/consignment-transfer-line.js +16 -0
  89. package/.medusa/server/src/modules/affiliate/models/consignment-transfer.js +20 -0
  90. package/.medusa/server/src/modules/affiliate/models/customer-affiliate-assignment.js +58 -0
  91. package/.medusa/server/src/modules/affiliate/service.js +2321 -0
  92. package/.medusa/server/src/modules/affiliate/services/affiliate-application-manager.js +171 -0
  93. package/.medusa/server/src/modules/affiliate/services/affiliate-coupon-manager.js +441 -0
  94. package/.medusa/server/src/modules/affiliate/services/affiliate-discount.service.js +55 -0
  95. package/.medusa/server/src/modules/affiliate/services/affiliate-manager.js +242 -0
  96. package/.medusa/server/src/modules/affiliate/services/affiliate-partner-portal.service.js +138 -0
  97. package/.medusa/server/src/modules/affiliate/services/affiliate-report.service.js +436 -0
  98. package/.medusa/server/src/modules/affiliate/services/affiliate-slug-manager.js +163 -0
  99. package/.medusa/server/src/modules/affiliate/services/commission-ledger.service.js +729 -0
  100. package/.medusa/server/src/modules/affiliate/services/customer-affiliate-assignment-manager.js +350 -0
  101. package/.medusa/server/src/modules/affiliate/services/order-attribution-manager.js +1051 -0
  102. package/.medusa/server/src/modules/affiliate/services/referral-lifecycle-manager.js +1420 -0
  103. package/.medusa/server/src/modules/affiliate/services/utils.js +49 -0
  104. package/.medusa/server/src/modules/affiliate/types/internal.js +3 -0
  105. package/.medusa/server/src/paths/affiliate.js +7 -0
  106. package/.medusa/server/src/paths/index.js +18 -0
  107. package/.medusa/server/src/schemas/index.js +18 -0
  108. package/.medusa/server/src/schemas/store.js +24 -0
  109. package/.medusa/server/src/subscribers/affiliate-cart-updated.js +92 -0
  110. package/.medusa/server/src/subscribers/affiliate-order-canceled.js +70 -0
  111. package/.medusa/server/src/subscribers/affiliate-order-placed.js +63 -0
  112. package/.medusa/server/src/subscribers/affiliate-order-return-received.js +67 -0
  113. package/.medusa/server/src/subscribers/affiliate-product-eligibility-updated.js +87 -0
  114. package/.medusa/server/src/types/admin-api.js +3 -0
  115. package/.medusa/server/src/types/enums.js +53 -0
  116. package/.medusa/server/src/types/index.js +32 -0
  117. package/.medusa/server/src/types/public.js +3 -0
  118. package/.medusa/server/src/types/store-api.js +3 -0
  119. package/.medusa/server/src/utils/affiliate-cart-operation-lock.js +316 -0
  120. package/.medusa/server/src/utils/affiliate-promotion-mutation-authorization.js +49 -0
  121. package/.medusa/server/src/utils/cart-promotions.js +9 -0
  122. package/.medusa/server/src/utils/money.js +79 -0
  123. package/.medusa/server/src/utils/promotion-code-availability.js +34 -0
  124. package/.medusa/server/src/utils/registered-email-checkout.js +101 -0
  125. package/.medusa/server/src/utils/resolve-services.js +7 -0
  126. package/.medusa/server/src/utils/settle-cart-promotions.js +44 -0
  127. package/.medusa/server/src/workflows/affiliate/affiliate-coupon-cart.js +215 -0
  128. package/.medusa/server/src/workflows/affiliate/affiliate-coupon-command-authorization.js +20 -0
  129. package/.medusa/server/src/workflows/affiliate/apply-affiliate-coupon.js +174 -0
  130. package/.medusa/server/src/workflows/affiliate/apply-own-order-promotion.js +89 -0
  131. package/.medusa/server/src/workflows/affiliate/approve-consignment-report.js +186 -0
  132. package/.medusa/server/src/workflows/affiliate/correct-approved-consignment-report.js +263 -0
  133. package/.medusa/server/src/workflows/affiliate/create-affiliate-coupon.js +35 -0
  134. package/.medusa/server/src/workflows/affiliate/create-commission-correction-for-cancellation.js +21 -0
  135. package/.medusa/server/src/workflows/affiliate/create-commission-correction-for-refund.js +24 -0
  136. package/.medusa/server/src/workflows/affiliate/create-commission-for-order.js +17 -0
  137. package/.medusa/server/src/workflows/affiliate/deactivate-affiliate.js +89 -0
  138. package/.medusa/server/src/workflows/affiliate/enable-distribution.js +120 -0
  139. package/.medusa/server/src/workflows/affiliate/enrich-affiliate-report.js +75 -0
  140. package/.medusa/server/src/workflows/affiliate/ensure-affiliate-coupon-promotion.js +410 -0
  141. package/.medusa/server/src/workflows/affiliate/ensure-affiliate-own-order-promotion.js +240 -0
  142. package/.medusa/server/src/workflows/affiliate/ensure-default-affiliate-coupon.js +32 -0
  143. package/.medusa/server/src/workflows/affiliate/expire-affiliate-attribution-state.js +131 -0
  144. package/.medusa/server/src/workflows/affiliate/finalize-affiliate-order-attribution.js +82 -0
  145. package/.medusa/server/src/workflows/affiliate/payment-authorization.js +33 -0
  146. package/.medusa/server/src/workflows/affiliate/promotion-eligibility.js +179 -0
  147. package/.medusa/server/src/workflows/affiliate/reconcile-affiliate-cart-referral.js +219 -0
  148. package/.medusa/server/src/workflows/affiliate/reconcile-affiliate-coupon.js +388 -0
  149. package/.medusa/server/src/workflows/affiliate/refresh-affiliate-promotions.js +114 -0
  150. package/.medusa/server/src/workflows/affiliate/resolve-coupon-labels.js +40 -0
  151. package/.medusa/server/src/workflows/affiliate/revoke-affiliate-coupon.js +29 -0
  152. package/.medusa/server/src/workflows/affiliate/rotate-affiliate-coupon.js +36 -0
  153. package/.medusa/server/src/workflows/affiliate/run-consignment-report-reminders.js +58 -0
  154. package/.medusa/server/src/workflows/affiliate/submit-consignment-report.js +279 -0
  155. package/.medusa/server/src/workflows/affiliate/transfer-batch-aware-inventory.js +172 -0
  156. package/.medusa/server/src/workflows/affiliate/transfer-consignment-stock.js +106 -0
  157. package/.medusa/server/src/workflows/affiliate/update-affiliate-coupon-discounts.js +129 -0
  158. package/.medusa/server/src/workflows/affiliate/utils.js +118 -0
  159. package/.medusa/server/src/workflows/hooks/complete-cart-affiliate-attribution.js +57 -0
  160. package/.medusa/server/src/workflows/hooks/complete-cart-order-created-composition.js +111 -0
  161. package/.medusa/server/src/workflows/hooks/complete-cart-own-order-discount.js +75 -0
  162. package/.medusa/server/src/workflows/hooks/enforce-unique-promotion-codes.js +33 -0
  163. package/.medusa/server/src/workflows/hooks/validate-affiliate-customer-code.js +239 -0
  164. package/.medusa/server/src/workflows/hooks/validate-complete-cart-affiliate-referral.js +137 -0
  165. package/LICENSE +21 -0
  166. package/README.md +799 -0
  167. package/dist/integration/email-events.d.ts +20 -0
  168. package/dist/integration/email-events.js +50 -0
  169. package/package.json +84 -0
@@ -0,0 +1,3739 @@
1
+ "use strict";
2
+ const jsxRuntime = require("react/jsx-runtime");
3
+ const adminSdk = require("@medusajs/admin-sdk");
4
+ const ui = require("@medusajs/ui");
5
+ const react = require("react");
6
+ const reactQuery = require("@tanstack/react-query");
7
+ const affiliate = require("@hartl-services/medusa-food-supplements-contracts/paths/affiliate");
8
+ const Medusa = require("@medusajs/js-sdk");
9
+ const reactRouterDom = require("react-router-dom");
10
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
11
+ const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
12
+ const texts = {
13
+ routes: {
14
+ affiliates: "Affiliates",
15
+ newAffiliate: "Affiliate anlegen",
16
+ applications: "Offene Anfragen",
17
+ reports: "Affiliate-Berichte"
18
+ },
19
+ common: {
20
+ dash: "-",
21
+ back: "Zurück",
22
+ next: "Weiter",
23
+ pageRange: (from, to, count) => `${from} bis ${to} von ${count}`
24
+ },
25
+ status: {
26
+ active: "Aktiv",
27
+ inactive: "Inaktiv",
28
+ pending: "Ausstehend",
29
+ rejected: "Abgelehnt",
30
+ approved: "Genehmigt"
31
+ },
32
+ hooks: {
33
+ missingAffiliateId: "Affiliate-ID fehlt.",
34
+ noApplicationSelected: "Keine Bewerbung ausgewählt."
35
+ },
36
+ list: {
37
+ heading: "Affiliates",
38
+ description: "Affiliates, Sätze, Slugs und Status verwalten.",
39
+ searchPlaceholder: "Anzeigename suchen",
40
+ statusAll: "Alle Status",
41
+ create: "Neu anlegen",
42
+ edit: "Bearbeiten",
43
+ reactivate: "Reaktivieren",
44
+ loading: "Affiliates werden geladen…",
45
+ loadError: (message) => `Affiliates konnten nicht geladen werden: ${message}`,
46
+ emptyFiltered: "Keine Affiliates für diese Filter gefunden.",
47
+ empty: "Keine Affiliates gefunden.",
48
+ columns: {
49
+ affiliate: "Affiliate",
50
+ status: "Status",
51
+ commission: "Provision",
52
+ ownDiscount: "Eigenrabatt",
53
+ primarySlug: "Primärer Slug",
54
+ created: "Erstellt",
55
+ action: "Aktion"
56
+ },
57
+ statusOptions: {
58
+ all: "Alle Status",
59
+ active: "Aktiv",
60
+ inactive: "Inaktiv",
61
+ rejected: "Abgelehnt"
62
+ },
63
+ toasts: {
64
+ reactivated: "Affiliate wurde reaktiviert",
65
+ reactivateFailed: "Affiliate konnte nicht reaktiviert werden"
66
+ }
67
+ },
68
+ applications: {
69
+ heading: "Affiliate-Bewerbungen",
70
+ description: "Anfragen aus dem Storefront zur Aufnahme als Affiliate prüfen.",
71
+ loading: "Bewerbungen werden geladen…",
72
+ loadError: (message) => `Bewerbungen konnten nicht geladen werden: ${message}`,
73
+ empty: "Keine Affiliate-Bewerbungen gefunden.",
74
+ columns: {
75
+ applicant: "Bewerber",
76
+ customer: "Kunde",
77
+ slug: "Slug",
78
+ submitted: "Eingereicht",
79
+ action: "Aktion"
80
+ },
81
+ review: "Prüfen",
82
+ reviewHeading: (name) => `${name} prüfen`,
83
+ customerLine: (customerId) => `Kunde ${customerId}`,
84
+ approveHeading: "Genehmigen",
85
+ affiliateSlugLabel: "Affiliate-Slug",
86
+ affiliateSlugPlaceholder: "affiliate-slug",
87
+ commissionLabel: "Provision %",
88
+ ownDiscountLabel: "Eigenrabatt %",
89
+ approve: "Genehmigen",
90
+ approving: "Wird genehmigt…",
91
+ rejectHeading: "Ablehnen",
92
+ reasonLabel: "Grund",
93
+ reject: "Ablehnen",
94
+ rejecting: "Wird abgelehnt…",
95
+ toasts: {
96
+ approved: "Affiliate-Bewerbung genehmigt",
97
+ approveFailed: "Bewerbung konnte nicht genehmigt werden",
98
+ rejected: "Affiliate-Bewerbung abgelehnt",
99
+ rejectFailed: "Bewerbung konnte nicht abgelehnt werden"
100
+ }
101
+ },
102
+ form: {
103
+ createHeading: "Affiliate anlegen",
104
+ createDescription: "Legen Sie einen wiederverwendbaren Affiliate-Datensatz an, der optional mit einem Kunden verknüpft ist.",
105
+ initialSlugLabel: "Anfangs-Slug",
106
+ initialSlugPlaceholder: "wird generiert, falls leer",
107
+ creating: "Wird erstellt…",
108
+ create: "Affiliate anlegen",
109
+ notFoundHeading: "Affiliate nicht gefunden",
110
+ notFoundBody: "Die Affiliate-ID fehlt in der aktuellen Route.",
111
+ backToList: "Zurück zu den Affiliates",
112
+ loadErrorHeading: "Affiliate konnte nicht geladen werden",
113
+ loading: "Affiliate wird geladen…",
114
+ createdAt: (value) => `Erstellt ${value}`,
115
+ setInactive: "Auf inaktiv setzen",
116
+ saving: "Wird gespeichert…",
117
+ save: "Änderungen speichern",
118
+ fields: {
119
+ displayName: "Anzeigename",
120
+ commission: "Provision %",
121
+ ownDiscount: "Eigenrabatt %",
122
+ referredBy: "Werbender Affiliate",
123
+ referrerCommission: "Werber-Provision %",
124
+ internalNotes: "Interne Notizen",
125
+ customerAssignmentMode: "Kunden-Zuordnung",
126
+ customerAssignmentModeHint: "Legt fest, wie Bestellungen eines geworbenen Kunden zugeordnet werden.",
127
+ assignmentDurationDays: "Zuordnungsfrist"
128
+ },
129
+ assignmentModes: {
130
+ lifetime: {
131
+ label: "Lifetime",
132
+ description: "Alle zukünftigen Bestellungen dieses Kunden bleiben zugeordnet."
133
+ },
134
+ timeLimited: {
135
+ label: "Zeitlich begrenzt",
136
+ description: "Die Frist beginnt mit der ersten erfolgreichen Bestellung und wird nicht verlängert."
137
+ },
138
+ orderOnly: {
139
+ label: "Nur Bestellung",
140
+ description: "Eine spätere Bestellung benötigt einen neuen Referral-Link oder Coupon."
141
+ }
142
+ },
143
+ slugs: {
144
+ heading: "Affiliate-Slugs",
145
+ description: "Slugs sind global eindeutig. Nur aktive Slugs lösen Empfehlungen auf.",
146
+ placeholder: "new-affiliate-slug",
147
+ add: "Slug hinzufügen"
148
+ },
149
+ toasts: {
150
+ created: "Affiliate erstellt",
151
+ createFailed: "Affiliate konnte nicht erstellt werden",
152
+ updated: "Affiliate aktualisiert",
153
+ updateFailed: "Affiliate konnte nicht aktualisiert werden",
154
+ setInactive: "Affiliate auf inaktiv gesetzt",
155
+ setInactiveFailed: "Affiliate konnte nicht auf inaktiv gesetzt werden",
156
+ slugCreated: "Affiliate-Slug erstellt",
157
+ slugCreateFailed: "Slug konnte nicht erstellt werden"
158
+ }
159
+ },
160
+ statusSelect: {
161
+ label: "Status",
162
+ options: {
163
+ pending: "Ausstehend",
164
+ active: "Aktiv",
165
+ inactive: "Inaktiv",
166
+ rejected: "Abgelehnt"
167
+ }
168
+ },
169
+ slugTable: {
170
+ empty: "Noch keine Slugs.",
171
+ columns: {
172
+ slug: "Slug",
173
+ state: "Zustand",
174
+ primary: "Primär",
175
+ actions: "Aktionen"
176
+ },
177
+ active: "Aktiv",
178
+ inactive: "Inaktiv",
179
+ yes: "Ja",
180
+ no: "Nein",
181
+ makePrimary: "Primär",
182
+ activate: "Aktivieren",
183
+ deactivate: "Deaktivieren",
184
+ toasts: {
185
+ updated: "Affiliate-Slug aktualisiert",
186
+ updateFailed: "Slug konnte nicht aktualisiert werden"
187
+ }
188
+ },
189
+ coupons: {
190
+ heading: "Affiliate-Coupons",
191
+ description: "Coupon-Codes gewähren den Kundenrabatt und sind unabhängig von Referral-Slugs.",
192
+ createHeading: "Coupon anlegen",
193
+ generated: "Code automatisch generieren",
194
+ custom: "Eigenen Coupon-Code verwenden",
195
+ customCodeLabel: "Eigener Coupon-Code",
196
+ customCodePlaceholder: "SOMMER-2026",
197
+ codeHint: "3 bis 32 Zeichen: Buchstaben, Ziffern und Bindestriche.",
198
+ create: "Coupon anlegen",
199
+ creating: "Wird angelegt…",
200
+ loading: "Coupons werden geladen…",
201
+ loadError: (message) => `Coupons konnten nicht geladen werden: ${message}`,
202
+ empty: "Noch keine Coupons vorhanden.",
203
+ details: {
204
+ couponId: "Coupon-ID",
205
+ promotionId: "Promotion-ID",
206
+ idempotencyKey: "Idempotency-Key",
207
+ created: "Erstellt",
208
+ createdBy: "Erstellt von",
209
+ revoked: "Widerrufen",
210
+ revokedBy: "Widerrufen von",
211
+ setupError: "Bereitstellungsfehler"
212
+ },
213
+ statuses: {
214
+ active: "Aktiv",
215
+ affiliate_inactive: "Affiliate inaktiv",
216
+ provisioning: "Wird bereitgestellt",
217
+ provisioning_failed: "Bereitstellung fehlgeschlagen",
218
+ revoked: "Widerrufen"
219
+ },
220
+ actorTypes: {
221
+ system: "System",
222
+ admin: "Admin",
223
+ affiliate: "Affiliate"
224
+ },
225
+ actor: (type, id) => id ? `${type} · ${id}` : type,
226
+ rotate: "Rotieren",
227
+ rotateHeading: (code) => `Coupon ${code} rotieren`,
228
+ rotateDescription: "Die Rotation legt einen neuen Coupon an und widerruft den bisherigen dauerhaft.",
229
+ rotating: "Wird rotiert…",
230
+ cancel: "Abbrechen",
231
+ revoke: "Widerrufen",
232
+ revokeHeading: "Coupon widerrufen?",
233
+ revokeDescription: (code) => `${code} wird dauerhaft gesperrt und kann nicht reaktiviert werden.`,
234
+ reconcile: "Bereitstellung abgleichen",
235
+ reconciling: "Wird abgeglichen…",
236
+ reconcileOutcome: (outcome) => `Ergebnis: ${outcome}`,
237
+ reconcileOutcomes: {
238
+ created: "Promotion erstellt",
239
+ adopted: "Promotion übernommen",
240
+ repaired: "Promotion repariert",
241
+ unchanged: "Keine Änderung erforderlich",
242
+ revoked_cleanup: "Widerruf bereinigt"
243
+ },
244
+ rotationFinalized: "Rotation wurde abgeschlossen.",
245
+ toasts: {
246
+ created: "Coupon angelegt",
247
+ createFailed: "Coupon konnte nicht angelegt werden",
248
+ rotated: "Coupon rotiert",
249
+ rotateFailed: "Coupon konnte nicht rotiert werden",
250
+ revoked: "Coupon widerrufen",
251
+ revokeFailed: "Coupon konnte nicht widerrufen werden",
252
+ reconciled: "Coupon-Bereitstellung abgeglichen",
253
+ reconcileFailed: "Coupon-Bereitstellung konnte nicht abgeglichen werden"
254
+ }
255
+ },
256
+ assignment: {
257
+ heading: "Affiliate-Zuordnung",
258
+ checking: "Wird geprüft…",
259
+ isAffiliate: "Dieser Kunde ist selbst Affiliate und kann keinen werbenden Affiliate zugewiesen bekommen.",
260
+ none: "Keine aktive Affiliate-Zuordnung.",
261
+ history: "Zuordnungsverlauf",
262
+ historyEmpty: "Keine beendeten Affiliate-Zuordnungen.",
263
+ current: "Aktuelle Zuordnung",
264
+ validUntil: (value) => `Gültig bis ${value}`,
265
+ lifetime: "Lifetime",
266
+ ended: "Beendet",
267
+ active: "Aktiv",
268
+ mode: "Modus",
269
+ assignedAt: "Zugeordnet",
270
+ endedReason: "Beendigungsgrund",
271
+ endedReasons: {
272
+ expired: "Frist abgelaufen",
273
+ reassigned: "Durch andere Zuordnung ersetzt",
274
+ adminRemoved: "Manuell entfernt",
275
+ affiliateInactive: "Affiliate wurde inaktiv gesetzt",
276
+ customerBecameAffiliate: "Kunde wurde selbst Affiliate",
277
+ notProvided: "Nicht angegeben",
278
+ unknown: "Unbekannter Beendigungsgrund"
279
+ },
280
+ orderOnlyUnavailable: "Dieser Affiliate verwendet Nur Bestellung und kann keinem Kunden manuell zugeordnet werden.",
281
+ save: "Zuordnung speichern",
282
+ remove: "Zuordnung entfernen",
283
+ toasts: {
284
+ saved: "Affiliate-Zuordnung aktualisiert",
285
+ saveFailed: "Zuordnung konnte nicht aktualisiert werden",
286
+ removed: "Affiliate-Zuordnung entfernt",
287
+ removeFailed: "Zuordnung konnte nicht entfernt werden"
288
+ }
289
+ },
290
+ reference: {
291
+ remove: "Entfernen",
292
+ searching: "Wird gesucht…",
293
+ affiliate: {
294
+ label: "Affiliate",
295
+ placeholder: "Aktive Affiliates nach Anzeigename suchen",
296
+ empty: "Keine aktiven Affiliates gefunden."
297
+ },
298
+ customer: {
299
+ label: "Kunde",
300
+ placeholder: "Kunden nach E-Mail oder Name suchen",
301
+ empty: "Keine Kunden gefunden."
302
+ }
303
+ },
304
+ reports: {
305
+ heading: "Affiliate-Berichte",
306
+ description: "Berichte fassen unveränderliche Einträge des Provisionsjournals nach Buchungsdatum zusammen.",
307
+ from: "Von",
308
+ to: "Bis",
309
+ loading: "Bericht wird geladen…",
310
+ loadError: (message) => `Bericht konnte nicht geladen werden: ${message}`,
311
+ empty: "Keine Provisionseinträge für diesen Zeitraum gefunden.",
312
+ details: "Details",
313
+ columns: {
314
+ affiliate: "Affiliate",
315
+ status: "Status",
316
+ orderValue: "Bestellungswert",
317
+ commission: "davon Provision",
318
+ orderCount: "Anzahl Bestellungen",
319
+ action: "Aktion"
320
+ },
321
+ orderValueTooltip: "Netto-Warenwert, auf dessen Basis die Provision berechnet wurde (ohne Steuer/Versand).",
322
+ attributedOrdersTooltip: "Enthält alle Attributionen, auch Bestellungen mit 0 EUR oder ohne provisionsfähige Positionen.",
323
+ commissionedOrdersTooltip: "Zählt nur positive direkte Order-Provisionsbuchungen."
324
+ },
325
+ reportDetail: {
326
+ fallbackHeading: "Affiliate-Bericht",
327
+ description: "Journaleinträge für den ausgewählten Affiliate und Zeitraum.",
328
+ from: "Von",
329
+ to: "Bis",
330
+ journalEntries: "Journaleinträge",
331
+ entryCount: (count) => `${count} Journaleinträge`,
332
+ orderValue: "Bestellungswert",
333
+ orderCount: (count) => `${count} Bestellungen`,
334
+ attributedOrders: (count) => `${count} attribuierte Bestellungen`,
335
+ commissionedOrders: (count) => `${count} provisionierte Bestellungen`,
336
+ loading: "Bericht wird geladen…",
337
+ loadError: (message) => `Bericht konnte nicht geladen werden: ${message}`,
338
+ empty: "Keine Provisionseinträge für diesen Zeitraum gefunden.",
339
+ viewOrder: "Bestellung ansehen",
340
+ columns: {
341
+ occurredAt: "Buchungsdatum",
342
+ type: "Typ",
343
+ order: "Bestellung",
344
+ customer: "Kunde",
345
+ base: "Basis",
346
+ rate: "Satz",
347
+ commission: "Provision"
348
+ },
349
+ entryTypes: {
350
+ order_commission: "Direkte Provision",
351
+ affiliate_referral_bonus: "Empfehlungsbonus",
352
+ affiliate_own_order_referrer_bonus: "Partner werben",
353
+ refund_correction: "Korrektur: Rückerstattung",
354
+ cancellation_correction: "Korrektur: Stornierung",
355
+ manual_correction: "Manuelle Korrektur"
356
+ }
357
+ },
358
+ orderAttribution: {
359
+ heading: "Affiliate-Attribution",
360
+ loading: "Affiliate-Attribution wird geladen…",
361
+ loadError: (message) => `Affiliate-Attribution konnte nicht geladen werden: ${message}`,
362
+ empty: "Keine Affiliate-Zuordnung.",
363
+ affiliate: "Affiliate",
364
+ source: "Referral-Quelle",
365
+ sources: {
366
+ customer_assignment: "Kunden-Zuordnung",
367
+ referral_link: "Referral-Link",
368
+ coupon: "Coupon"
369
+ },
370
+ referralSlug: "Referral-Slug",
371
+ referralCoupon: "Referral-Coupon",
372
+ appliedCoupon: "Angewandter Coupon",
373
+ appliedCouponId: "Angewandte Coupon-ID",
374
+ appliedPromotionId: "Angewandte Promotion-ID",
375
+ noAppliedCoupon: "Kein Coupon angewandt",
376
+ mode: "Modus",
377
+ duration: "Dauer",
378
+ assignmentReference: "Zuordnungsreferenz",
379
+ commissionRate: "Provisionssatz",
380
+ referrerRate: "Werber-Provision",
381
+ referrerAffiliate: "Werbender Affiliate",
382
+ currency: "Währung",
383
+ attributedAt: "Attribuiert am",
384
+ noDuration: "Keine Frist",
385
+ noReference: "Keine Referenz",
386
+ noReferrerRate: "Keine Werber-Provision",
387
+ noReferrerAffiliate: "Keiner"
388
+ }
389
+ };
390
+ const affiliateKeys = {
391
+ all: ["affiliate"],
392
+ lists: () => [...affiliateKeys.all, "list"],
393
+ list: (params) => [...affiliateKeys.lists(), params],
394
+ details: () => [...affiliateKeys.all, "detail"],
395
+ detail: (id) => [...affiliateKeys.details(), id],
396
+ couponCodes: (affiliateId) => [...affiliateKeys.all, "coupon-codes", affiliateId],
397
+ couponCodeList: (affiliateId, params) => [...affiliateKeys.couponCodes(affiliateId), "list", params],
398
+ applications: () => [...affiliateKeys.all, "applications"],
399
+ applicationList: (params) => [...affiliateKeys.applications(), params],
400
+ reports: () => [...affiliateKeys.all, "reports"],
401
+ reportOverview: (params) => [...affiliateKeys.reports(), "overview", params],
402
+ reportDetail: (affiliateId, params) => [...affiliateKeys.reports(), "detail", affiliateId, params],
403
+ customerAssignment: (customerId) => [...affiliateKeys.all, "customer-assignment", customerId],
404
+ customerIsAffiliate: (customerId) => [...affiliateKeys.all, "customer-is-affiliate", customerId],
405
+ orderAttribution: (orderId) => [...affiliateKeys.all, "order-attribution", orderId],
406
+ affiliateReferenceSearch: (search) => [...affiliateKeys.all, "affiliate-reference-search", search],
407
+ affiliateReference: (id) => [...affiliateKeys.all, "affiliate-reference", id],
408
+ customerReferenceSearch: (search) => [...affiliateKeys.all, "customer-reference-search", search],
409
+ customerReference: (id) => [...affiliateKeys.all, "customer-reference", id]
410
+ };
411
+ const withQuery = (path, params) => {
412
+ const search = new URLSearchParams();
413
+ for (const [key, value] of Object.entries(params)) {
414
+ if (value !== void 0 && value !== null && value !== "") {
415
+ search.set(key, String(value));
416
+ }
417
+ }
418
+ const query = search.toString();
419
+ return query ? `${path}?${query}` : path;
420
+ };
421
+ const sdk = new Medusa__default.default({
422
+ baseUrl: __BACKEND_URL__ ?? "/",
423
+ auth: {
424
+ type: __AUTH_TYPE__ ?? "session",
425
+ jwtTokenStorageKey: __JWT_TOKEN_STORAGE_KEY__ || void 0
426
+ }
427
+ });
428
+ const listAffiliateApplications = (params) => sdk.client.fetch(withQuery(affiliate.affiliateAdminPaths.get.applications, params));
429
+ const approveAffiliateApplication = (applicationId, payload) => sdk.client.fetch(affiliate.affiliateAdminPaths.post.approveApplication(applicationId), {
430
+ method: "POST",
431
+ body: payload
432
+ });
433
+ const rejectAffiliateApplication = (applicationId, payload) => sdk.client.fetch(affiliate.affiliateAdminPaths.post.rejectApplication(applicationId), {
434
+ method: "POST",
435
+ body: payload
436
+ });
437
+ const listAffiliates = (params) => sdk.client.fetch(withQuery(affiliate.affiliateAdminPaths.get.affiliates, params));
438
+ const listCustomers = async (params) => {
439
+ const { customers, count, limit, offset } = await sdk.admin.customer.list({
440
+ q: params.q,
441
+ limit: params.limit,
442
+ offset: params.offset,
443
+ fields: "id,email,first_name,last_name"
444
+ });
445
+ return { customers, count, limit, offset };
446
+ };
447
+ const retrieveCustomer = async (customerId) => {
448
+ const { customer } = await sdk.admin.customer.retrieve(customerId, {
449
+ fields: "id,email,first_name,last_name"
450
+ });
451
+ return { customer };
452
+ };
453
+ const createAffiliate = (payload) => sdk.client.fetch(affiliate.affiliateAdminPaths.post.createAffiliate, {
454
+ method: "POST",
455
+ body: payload
456
+ });
457
+ const retrieveAffiliate = (affiliateId) => sdk.client.fetch(affiliate.affiliateAdminPaths.get.affiliate(affiliateId));
458
+ const updateAffiliate = (affiliateId, payload) => sdk.client.fetch(affiliate.affiliateAdminPaths.post.updateAffiliate(affiliateId), {
459
+ method: "POST",
460
+ body: payload
461
+ });
462
+ const setAffiliateInactive = (affiliateId) => sdk.client.fetch(affiliate.affiliateAdminPaths.post.setAffiliateInactive(affiliateId), {
463
+ method: "POST"
464
+ });
465
+ const createAffiliateSlug = (affiliateId, payload) => sdk.client.fetch(affiliate.affiliateAdminPaths.post.createAffiliateSlug(affiliateId), {
466
+ method: "POST",
467
+ body: payload
468
+ });
469
+ const updateAffiliateSlug = (affiliateId, slugId, payload) => sdk.client.fetch(affiliate.affiliateAdminPaths.post.updateAffiliateSlug(affiliateId, slugId), {
470
+ method: "POST",
471
+ body: payload
472
+ });
473
+ const listAffiliateCouponCodes = (affiliateId, params) => sdk.client.fetch(
474
+ withQuery(affiliate.affiliateAdminPaths.get.affiliateCouponCodes(affiliateId), {
475
+ lifecycle_status: params.lifecycle_status,
476
+ provisioning_status: params.provisioning_status,
477
+ limit: params.limit,
478
+ offset: params.offset
479
+ })
480
+ );
481
+ const createAffiliateCouponCode = (affiliateId, payload) => sdk.client.fetch(
482
+ affiliate.affiliateAdminPaths.post.createAffiliateCouponCode(affiliateId),
483
+ { method: "POST", body: payload }
484
+ );
485
+ const rotateAffiliateCouponCode = (affiliateId, couponId, payload) => sdk.client.fetch(
486
+ affiliate.affiliateAdminPaths.post.rotateAffiliateCouponCode(affiliateId, couponId),
487
+ { method: "POST", body: payload }
488
+ );
489
+ const revokeAffiliateCouponCode = (affiliateId, couponId) => sdk.client.fetch(
490
+ affiliate.affiliateAdminPaths.post.revokeAffiliateCouponCode(affiliateId, couponId),
491
+ { method: "POST" }
492
+ );
493
+ const reconcileAffiliateCouponCode = (affiliateId, couponId) => sdk.client.fetch(
494
+ affiliate.affiliateAdminPaths.post.reconcileAffiliateCouponCode(affiliateId, couponId),
495
+ { method: "POST" }
496
+ );
497
+ const getCustomerAffiliateAssignment = (customerId) => sdk.client.fetch(affiliate.affiliateAdminPaths.get.customerAssignment(customerId));
498
+ const getOrderAffiliateAttribution = (orderId) => sdk.client.fetch(
499
+ affiliate.affiliateAdminPaths.get.orderAttribution(orderId)
500
+ );
501
+ const setCustomerAffiliateAssignment = (customerId, payload) => sdk.client.fetch(affiliate.affiliateAdminPaths.post.setCustomerAssignment(customerId), {
502
+ method: "POST",
503
+ body: payload
504
+ });
505
+ const removeCustomerAffiliateAssignment = (customerId) => sdk.client.fetch(affiliate.affiliateAdminPaths.delete.customerAssignment(customerId), {
506
+ method: "DELETE"
507
+ });
508
+ const getAffiliateReportOverview = (params) => sdk.client.fetch(withQuery(affiliate.affiliateAdminPaths.get.reports, params));
509
+ const getAffiliateReportDetail = (affiliateId, params) => sdk.client.fetch(withQuery(affiliate.affiliateAdminPaths.get.report(affiliateId), params));
510
+ const requestConsignmentReminder = (affiliateId, payload) => sdk.client.fetch(
511
+ affiliate.affiliateAdminPaths.post.requestConsignmentReminder(affiliateId),
512
+ { method: "POST", body: payload }
513
+ );
514
+ const getAffiliateDistribution = (affiliateId) => sdk.client.fetch(
515
+ affiliate.affiliateAdminPaths.get.distribution(affiliateId)
516
+ );
517
+ const enableAffiliateDistribution = (affiliateId, payload) => sdk.client.fetch(
518
+ affiliate.affiliateAdminPaths.post.updateDistribution(affiliateId),
519
+ { method: "POST", body: payload }
520
+ );
521
+ const transferConsignmentStock = (affiliateId, payload) => sdk.client.fetch(
522
+ affiliate.affiliateAdminPaths.post.transferConsignmentStock(affiliateId),
523
+ { method: "POST", body: payload }
524
+ );
525
+ const getSourceInventoryQuantity = async (inventoryItemId, locationId) => {
526
+ const response = await sdk.admin.inventoryItem.listLevels(inventoryItemId, {
527
+ location_id: locationId,
528
+ limit: 1
529
+ });
530
+ return response.inventory_levels[0]?.stocked_quantity ?? null;
531
+ };
532
+ const listSourceBatches = (inventoryItemId, locationId) => sdk.client.fetch(
533
+ withQuery("/admin/batch-management/batches", {
534
+ inventory_item_id: inventoryItemId,
535
+ location_id: locationId,
536
+ limit: 100,
537
+ offset: 0
538
+ })
539
+ );
540
+ const listAdminConsignmentReports = (params) => sdk.client.fetch(
541
+ withQuery(affiliate.affiliateAdminPaths.get.consignmentReports, params)
542
+ );
543
+ const retrieveAdminConsignmentReport = (reportId) => sdk.client.fetch(
544
+ affiliate.affiliateAdminPaths.get.consignmentReport(reportId)
545
+ );
546
+ const approveAdminConsignmentReport = (reportId, payload) => sdk.client.fetch(
547
+ affiliate.affiliateAdminPaths.post.approveConsignmentReport(reportId),
548
+ { method: "POST", body: payload }
549
+ );
550
+ const rejectAdminConsignmentReport = (reportId, payload) => sdk.client.fetch(
551
+ affiliate.affiliateAdminPaths.post.rejectConsignmentReport(reportId),
552
+ { method: "POST", body: payload }
553
+ );
554
+ const getAffiliateProgramSettings = () => sdk.client.fetch(
555
+ affiliate.affiliateAdminPaths.get.programSettings
556
+ );
557
+ const updateAffiliateProgramSettings = (payload) => sdk.client.fetch(
558
+ affiliate.affiliateAdminPaths.post.updateProgramSettings,
559
+ { method: "POST", body: payload }
560
+ );
561
+ const getAffiliateDistributionReports = (params) => sdk.client.fetch(
562
+ withQuery(affiliate.affiliateAdminPaths.get.distributionReports, params)
563
+ );
564
+ const SearchableReferenceField = ({
565
+ label,
566
+ value,
567
+ selectedLabel,
568
+ search,
569
+ placeholder,
570
+ emptyText,
571
+ isLoading,
572
+ options,
573
+ disabled = false,
574
+ onSearchChange,
575
+ onSelect,
576
+ onClear
577
+ }) => {
578
+ const [isOpen, setIsOpen] = react.useState(false);
579
+ const showDropdown = isOpen && !disabled;
580
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
581
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: label }),
582
+ value && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-ui-border-base bg-ui-bg-subtle flex items-start justify-between gap-3 rounded-md border px-3 py-2", children: [
583
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
584
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "truncate text-sm font-medium", children: selectedLabel || value }),
585
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted truncate text-xs", children: value })
586
+ ] }),
587
+ /* @__PURE__ */ jsxRuntime.jsx(
588
+ ui.Button,
589
+ {
590
+ type: "button",
591
+ size: "small",
592
+ variant: "secondary",
593
+ disabled,
594
+ onClick: onClear,
595
+ children: texts.reference.remove
596
+ }
597
+ )
598
+ ] }),
599
+ /* @__PURE__ */ jsxRuntime.jsx(
600
+ ui.Input,
601
+ {
602
+ value: search,
603
+ disabled,
604
+ onFocus: () => setIsOpen(true),
605
+ onChange: (event) => {
606
+ onSearchChange(event.target.value);
607
+ setIsOpen(true);
608
+ },
609
+ onKeyDown: (event) => {
610
+ if (event.key === "Escape") {
611
+ setIsOpen(false);
612
+ }
613
+ },
614
+ placeholder
615
+ }
616
+ ),
617
+ showDropdown && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-ui-border-base bg-ui-bg-base shadow-elevation-card-rest max-h-64 overflow-auto rounded-md border", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle px-3 py-2 text-sm", children: texts.reference.searching }) : options.length ? options.map((option) => /* @__PURE__ */ jsxRuntime.jsxs(
618
+ "button",
619
+ {
620
+ type: "button",
621
+ className: "hover:bg-ui-bg-subtle grid w-full gap-0.5 px-3 py-2 text-left",
622
+ onClick: () => {
623
+ onSelect(option);
624
+ setIsOpen(false);
625
+ },
626
+ children: [
627
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ui-fg-base truncate text-sm font-medium", children: option.label }),
628
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ui-fg-muted truncate text-xs", children: option.description ?? option.id })
629
+ ]
630
+ },
631
+ option.id
632
+ )) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle px-3 py-2 text-sm", children: emptyText }) })
633
+ ] });
634
+ };
635
+ const AffiliateReferenceField = ({
636
+ label = texts.reference.affiliate.label,
637
+ value,
638
+ onChange,
639
+ excludeAffiliateId,
640
+ disabled
641
+ }) => {
642
+ const [search, setSearch] = react.useState("");
643
+ const [selectedLabel, setSelectedLabel] = react.useState("");
644
+ const deferredSearch = react.useDeferredValue(search);
645
+ const affiliatesQuery = reactQuery.useQuery({
646
+ queryKey: affiliateKeys.affiliateReferenceSearch(deferredSearch),
647
+ queryFn: () => listAffiliates({
648
+ status: "active",
649
+ q: deferredSearch.trim(),
650
+ limit: 100,
651
+ offset: 0
652
+ })
653
+ });
654
+ const selectedAffiliateQuery = reactQuery.useQuery({
655
+ queryKey: affiliateKeys.affiliateReference(value),
656
+ enabled: !!value,
657
+ queryFn: () => retrieveAffiliate(value)
658
+ });
659
+ const options = (affiliatesQuery.data?.affiliates ?? []).filter((affiliate2) => affiliate2.id !== excludeAffiliateId).map((affiliate2) => {
660
+ const primarySlug = affiliate2.slugs?.find((slug) => slug.is_primary)?.slug;
661
+ return {
662
+ id: affiliate2.id,
663
+ label: affiliate2.display_name,
664
+ description: primarySlug ? `${affiliate2.id} · ${primarySlug}` : affiliate2.id
665
+ };
666
+ });
667
+ const selectedAffiliate = selectedAffiliateQuery.data?.affiliate;
668
+ const displayLabel = selectedLabel || selectedAffiliate?.display_name || value;
669
+ return /* @__PURE__ */ jsxRuntime.jsx(
670
+ SearchableReferenceField,
671
+ {
672
+ label,
673
+ value,
674
+ selectedLabel: displayLabel,
675
+ search,
676
+ placeholder: texts.reference.affiliate.placeholder,
677
+ emptyText: texts.reference.affiliate.empty,
678
+ isLoading: affiliatesQuery.isLoading || affiliatesQuery.isFetching,
679
+ options,
680
+ disabled,
681
+ onSearchChange: setSearch,
682
+ onSelect: (option) => {
683
+ onChange(option.id);
684
+ setSelectedLabel(option.label);
685
+ setSearch("");
686
+ },
687
+ onClear: () => {
688
+ onChange("");
689
+ setSelectedLabel("");
690
+ setSearch("");
691
+ }
692
+ }
693
+ );
694
+ };
695
+ const getErrorMessage = (error) => {
696
+ if (error instanceof Error && error.message) {
697
+ return error.message;
698
+ }
699
+ if (typeof error !== "object" || error === null) {
700
+ return "Unbekannter Fehler";
701
+ }
702
+ const candidate = error;
703
+ for (const value of [
704
+ candidate.response?.data?.message,
705
+ candidate.response?.data?.error,
706
+ candidate.body?.message,
707
+ candidate.body?.error,
708
+ candidate.message,
709
+ candidate.statusText
710
+ ]) {
711
+ if (typeof value === "string" && value.trim()) {
712
+ return value;
713
+ }
714
+ }
715
+ return "Unbekannter Fehler";
716
+ };
717
+ const ADMIN_LOCALE = "de-DE";
718
+ const formatDate = (value) => {
719
+ if (!value) {
720
+ return texts.common.dash;
721
+ }
722
+ return new Intl.DateTimeFormat(ADMIN_LOCALE, {
723
+ year: "numeric",
724
+ month: "2-digit",
725
+ day: "2-digit"
726
+ }).format(new Date(value));
727
+ };
728
+ const formatDateTime = (value) => {
729
+ if (!value) {
730
+ return texts.common.dash;
731
+ }
732
+ return new Intl.DateTimeFormat(ADMIN_LOCALE, {
733
+ year: "numeric",
734
+ month: "2-digit",
735
+ day: "2-digit",
736
+ hour: "2-digit",
737
+ minute: "2-digit"
738
+ }).format(new Date(value));
739
+ };
740
+ const formatAmount = (amount, currencyCode) => new Intl.NumberFormat(ADMIN_LOCALE, {
741
+ style: "currency",
742
+ currency: currencyCode.toUpperCase()
743
+ }).format(amount);
744
+ const statusLabels = {
745
+ active: texts.status.active,
746
+ inactive: texts.status.inactive,
747
+ pending: texts.status.pending,
748
+ rejected: texts.status.rejected,
749
+ approved: texts.status.approved
750
+ };
751
+ const formatStatus = (status) => {
752
+ if (!status) {
753
+ return texts.common.dash;
754
+ }
755
+ return statusLabels[status] ?? status;
756
+ };
757
+ const commissionEntryTypeLabels = texts.reportDetail.entryTypes;
758
+ const formatCommissionEntryType = (type) => {
759
+ if (!type) {
760
+ return texts.common.dash;
761
+ }
762
+ return commissionEntryTypeLabels[type] ?? type;
763
+ };
764
+ const getTodayInputDate = () => {
765
+ const now = /* @__PURE__ */ new Date();
766
+ const timezoneOffsetMs = now.getTimezoneOffset() * 60 * 1e3;
767
+ return new Date(now.getTime() - timezoneOffsetMs).toISOString().slice(0, 10);
768
+ };
769
+ const getMonthStartInputDate = () => {
770
+ const now = /* @__PURE__ */ new Date();
771
+ const start = new Date(now.getFullYear(), now.getMonth(), 1);
772
+ const timezoneOffsetMs = start.getTimezoneOffset() * 60 * 1e3;
773
+ return new Date(start.getTime() - timezoneOffsetMs).toISOString().slice(0, 10);
774
+ };
775
+ const useCustomerAffiliateAssignment = (customerId) => reactQuery.useQuery({
776
+ queryKey: affiliateKeys.customerAssignment(customerId),
777
+ queryFn: () => getCustomerAffiliateAssignment(customerId)
778
+ });
779
+ const useCustomerIsAffiliate = (customerId) => reactQuery.useQuery({
780
+ queryKey: affiliateKeys.customerIsAffiliate(customerId),
781
+ queryFn: async () => {
782
+ const { affiliates } = await listAffiliates({
783
+ customer_id: customerId,
784
+ limit: 1,
785
+ offset: 0
786
+ });
787
+ return affiliates.some((affiliate2) => affiliate2.status !== "rejected");
788
+ }
789
+ });
790
+ const useSetCustomerAffiliateAssignment = (customerId) => {
791
+ const queryClient = reactQuery.useQueryClient();
792
+ return reactQuery.useMutation({
793
+ mutationFn: (affiliateId) => setCustomerAffiliateAssignment(customerId, {
794
+ affiliate_id: affiliateId,
795
+ source: "admin"
796
+ }),
797
+ onSuccess: async () => {
798
+ await queryClient.invalidateQueries({
799
+ queryKey: affiliateKeys.customerAssignment(customerId)
800
+ });
801
+ }
802
+ });
803
+ };
804
+ const useRemoveCustomerAffiliateAssignment = (customerId) => {
805
+ const queryClient = reactQuery.useQueryClient();
806
+ return reactQuery.useMutation({
807
+ mutationFn: () => removeCustomerAffiliateAssignment(customerId),
808
+ onSuccess: async () => {
809
+ await queryClient.invalidateQueries({
810
+ queryKey: affiliateKeys.customerAssignment(customerId)
811
+ });
812
+ }
813
+ });
814
+ };
815
+ const useAffiliates = (params = {}) => reactQuery.useQuery({
816
+ queryKey: affiliateKeys.list(params),
817
+ queryFn: () => listAffiliates({
818
+ limit: 50,
819
+ offset: 0,
820
+ ...params
821
+ })
822
+ });
823
+ const useAffiliate = (affiliateId) => reactQuery.useQuery({
824
+ queryKey: affiliateKeys.detail(affiliateId),
825
+ enabled: !!affiliateId,
826
+ queryFn: () => retrieveAffiliate(affiliateId ?? "")
827
+ });
828
+ const useAffiliateCouponCodes = (affiliateId, params = {}) => reactQuery.useQuery({
829
+ queryKey: affiliateKeys.couponCodeList(affiliateId, params),
830
+ enabled: !!affiliateId,
831
+ queryFn: () => listAffiliateCouponCodes(affiliateId ?? "", params)
832
+ });
833
+ const requireAffiliateId = (affiliateId) => {
834
+ if (!affiliateId) {
835
+ throw new Error(texts.hooks.missingAffiliateId);
836
+ }
837
+ return affiliateId;
838
+ };
839
+ const useCreateAffiliateCouponCode = (affiliateId) => {
840
+ const queryClient = reactQuery.useQueryClient();
841
+ return reactQuery.useMutation({
842
+ mutationFn: (payload) => createAffiliateCouponCode(requireAffiliateId(affiliateId), payload),
843
+ onSuccess: async () => {
844
+ await queryClient.invalidateQueries({
845
+ queryKey: affiliateKeys.couponCodes(affiliateId)
846
+ });
847
+ }
848
+ });
849
+ };
850
+ const useRotateAffiliateCouponCode = (affiliateId) => {
851
+ const queryClient = reactQuery.useQueryClient();
852
+ return reactQuery.useMutation({
853
+ mutationFn: (input) => rotateAffiliateCouponCode(
854
+ requireAffiliateId(affiliateId),
855
+ input.couponId,
856
+ input.payload
857
+ ),
858
+ onSuccess: async () => {
859
+ await queryClient.invalidateQueries({
860
+ queryKey: affiliateKeys.couponCodes(affiliateId)
861
+ });
862
+ }
863
+ });
864
+ };
865
+ const useRevokeAffiliateCouponCode = (affiliateId) => {
866
+ const queryClient = reactQuery.useQueryClient();
867
+ return reactQuery.useMutation({
868
+ mutationFn: (couponId) => revokeAffiliateCouponCode(requireAffiliateId(affiliateId), couponId),
869
+ onSuccess: async () => {
870
+ await queryClient.invalidateQueries({
871
+ queryKey: affiliateKeys.couponCodes(affiliateId)
872
+ });
873
+ }
874
+ });
875
+ };
876
+ const useReconcileAffiliateCouponCode = (affiliateId) => {
877
+ const queryClient = reactQuery.useQueryClient();
878
+ return reactQuery.useMutation({
879
+ mutationFn: (couponId) => reconcileAffiliateCouponCode(requireAffiliateId(affiliateId), couponId),
880
+ onSuccess: async () => {
881
+ await queryClient.invalidateQueries({
882
+ queryKey: affiliateKeys.couponCodes(affiliateId)
883
+ });
884
+ }
885
+ });
886
+ };
887
+ const useCreateAffiliate = () => {
888
+ const queryClient = reactQuery.useQueryClient();
889
+ return reactQuery.useMutation({
890
+ mutationFn: (payload) => createAffiliate(payload),
891
+ onSuccess: async (data) => {
892
+ queryClient.setQueryData(
893
+ affiliateKeys.detail(data.affiliate.id),
894
+ data
895
+ );
896
+ await queryClient.invalidateQueries({ queryKey: affiliateKeys.lists() });
897
+ }
898
+ });
899
+ };
900
+ const useUpdateAffiliate = (affiliateId) => {
901
+ const queryClient = reactQuery.useQueryClient();
902
+ return reactQuery.useMutation({
903
+ mutationFn: (payload) => {
904
+ if (!affiliateId) {
905
+ throw new Error(texts.hooks.missingAffiliateId);
906
+ }
907
+ return updateAffiliate(affiliateId, payload);
908
+ },
909
+ onSuccess: async (data) => {
910
+ queryClient.setQueryData(affiliateKeys.detail(affiliateId), data);
911
+ await queryClient.invalidateQueries({ queryKey: affiliateKeys.lists() });
912
+ }
913
+ });
914
+ };
915
+ const useSetAffiliateInactive = (affiliateId) => {
916
+ const queryClient = reactQuery.useQueryClient();
917
+ return reactQuery.useMutation({
918
+ mutationFn: () => {
919
+ if (!affiliateId) {
920
+ throw new Error(texts.hooks.missingAffiliateId);
921
+ }
922
+ return setAffiliateInactive(affiliateId);
923
+ },
924
+ onSuccess: async (data) => {
925
+ queryClient.setQueryData(affiliateKeys.detail(affiliateId), data);
926
+ await queryClient.invalidateQueries({ queryKey: affiliateKeys.lists() });
927
+ }
928
+ });
929
+ };
930
+ const useCreateAffiliateSlug = (affiliateId) => {
931
+ const queryClient = reactQuery.useQueryClient();
932
+ return reactQuery.useMutation({
933
+ mutationFn: (payload) => {
934
+ if (!affiliateId) {
935
+ throw new Error(texts.hooks.missingAffiliateId);
936
+ }
937
+ return createAffiliateSlug(affiliateId, {
938
+ slug: payload.slug,
939
+ active: true,
940
+ is_primary: payload.is_primary
941
+ });
942
+ },
943
+ onSuccess: async () => {
944
+ await queryClient.invalidateQueries({
945
+ queryKey: affiliateKeys.detail(affiliateId)
946
+ });
947
+ }
948
+ });
949
+ };
950
+ const useUpdateAffiliateSlug = (affiliateId) => {
951
+ const queryClient = reactQuery.useQueryClient();
952
+ return reactQuery.useMutation({
953
+ mutationFn: (payload) => {
954
+ if (!affiliateId) {
955
+ throw new Error(texts.hooks.missingAffiliateId);
956
+ }
957
+ return updateAffiliateSlug(affiliateId, payload.slugId, {
958
+ active: payload.active,
959
+ is_primary: payload.is_primary
960
+ });
961
+ },
962
+ onSuccess: async () => {
963
+ await queryClient.invalidateQueries({
964
+ queryKey: affiliateKeys.detail(affiliateId)
965
+ });
966
+ }
967
+ });
968
+ };
969
+ const formatAssignmentEndedReason = (reason) => {
970
+ switch (reason) {
971
+ case "expired":
972
+ return texts.assignment.endedReasons.expired;
973
+ case "reassigned":
974
+ return texts.assignment.endedReasons.reassigned;
975
+ case "admin_removed":
976
+ return texts.assignment.endedReasons.adminRemoved;
977
+ case "affiliate_inactive":
978
+ return texts.assignment.endedReasons.affiliateInactive;
979
+ case "customer_became_affiliate":
980
+ return texts.assignment.endedReasons.customerBecameAffiliate;
981
+ case null:
982
+ case void 0:
983
+ return texts.assignment.endedReasons.notProvided;
984
+ default:
985
+ return texts.assignment.endedReasons.unknown;
986
+ }
987
+ };
988
+ const getCustomerAffiliateAssignmentControlState = ({
989
+ hasActiveAssignment,
990
+ hasSelectedAffiliate,
991
+ selfAffiliateCheckPending,
992
+ customerIsAffiliate,
993
+ selectedModePending,
994
+ selectedAffiliateIsOrderOnly,
995
+ setMutationPending,
996
+ removeMutationPending
997
+ }) => ({
998
+ showAssignmentForm: !customerIsAffiliate,
999
+ affiliateFieldDisabled: selfAffiliateCheckPending || customerIsAffiliate,
1000
+ saveDisabled: selfAffiliateCheckPending || customerIsAffiliate || selectedModePending || selectedAffiliateIsOrderOnly || !hasSelectedAffiliate || setMutationPending,
1001
+ removeDisabled: !hasActiveAssignment || removeMutationPending
1002
+ });
1003
+ const formatAssignmentMode = (mode) => mode === "lifetime" ? "Lifetime" : "Zeitlich begrenzt";
1004
+ const CustomerAffiliateAssignmentPanel = ({ customerId }) => {
1005
+ const [affiliateId, setAffiliateId] = react.useState("");
1006
+ const [confirmationOpen, setConfirmationOpen] = react.useState(false);
1007
+ const assignmentQuery = useCustomerAffiliateAssignment(customerId);
1008
+ const customerIsAffiliateQuery = useCustomerIsAffiliate(customerId);
1009
+ const assignment = assignmentQuery.data?.assignment ?? null;
1010
+ const history = (assignmentQuery.data?.history ?? []).filter((item) => item.id !== assignment?.id).sort(
1011
+ (left, right) => new Date(right.assigned_at).getTime() - new Date(left.assigned_at).getTime()
1012
+ );
1013
+ const selectedAffiliateQuery = useAffiliate(affiliateId || void 0);
1014
+ const selectedAffiliate = selectedAffiliateQuery.data?.affiliate;
1015
+ react.useEffect(() => {
1016
+ if (assignment?.affiliate_id) {
1017
+ setAffiliateId(assignment.affiliate_id);
1018
+ }
1019
+ }, [assignment?.affiliate_id]);
1020
+ const setMutation = useSetCustomerAffiliateAssignment(customerId);
1021
+ const removeMutation = useRemoveCustomerAffiliateAssignment(customerId);
1022
+ const selfAffiliateCheckPending = customerIsAffiliateQuery.isLoading;
1023
+ const customerIsAffiliate = customerIsAffiliateQuery.data ?? false;
1024
+ const selectedModePending = !!affiliateId && selectedAffiliateQuery.isLoading;
1025
+ const selectedAffiliateIsOrderOnly = selectedAffiliate?.customer_assignment_mode === "order_only";
1026
+ const controlState = getCustomerAffiliateAssignmentControlState({
1027
+ hasActiveAssignment: !!assignment,
1028
+ hasSelectedAffiliate: !!affiliateId,
1029
+ selfAffiliateCheckPending,
1030
+ customerIsAffiliate,
1031
+ selectedModePending,
1032
+ selectedAffiliateIsOrderOnly,
1033
+ setMutationPending: setMutation.isPending,
1034
+ removeMutationPending: removeMutation.isPending
1035
+ });
1036
+ const saveAssignment = () => !controlState.saveDisabled && setMutation.mutate(affiliateId, {
1037
+ onSuccess: () => {
1038
+ setConfirmationOpen(false);
1039
+ ui.toast.success(texts.assignment.toasts.saved);
1040
+ },
1041
+ onError: (error) => ui.toast.error(texts.assignment.toasts.saveFailed, {
1042
+ description: getErrorMessage(error)
1043
+ })
1044
+ });
1045
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
1046
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
1047
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: texts.assignment.heading }),
1048
+ selfAffiliateCheckPending ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.assignment.checking }) : customerIsAffiliate ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.assignment.isAffiliate }) : assignment ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
1049
+ assignment.affiliate?.display_name ?? assignment.affiliate_id,
1050
+ " · ",
1051
+ assignment.source,
1052
+ " ·",
1053
+ " ",
1054
+ formatDateTime(assignment.assigned_at)
1055
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.assignment.none })
1056
+ ] }),
1057
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 px-6 py-4", children: [
1058
+ controlState.showAssignmentForm ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1059
+ /* @__PURE__ */ jsxRuntime.jsx(
1060
+ AffiliateReferenceField,
1061
+ {
1062
+ value: affiliateId,
1063
+ onChange: setAffiliateId,
1064
+ disabled: controlState.affiliateFieldDisabled
1065
+ }
1066
+ ),
1067
+ selectedAffiliateIsOrderOnly ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-sm", children: texts.assignment.orderOnlyUnavailable }) : null
1068
+ ] }) : null,
1069
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
1070
+ controlState.showAssignmentForm ? /* @__PURE__ */ jsxRuntime.jsx(
1071
+ ui.Button,
1072
+ {
1073
+ size: "small",
1074
+ disabled: controlState.saveDisabled,
1075
+ onClick: () => {
1076
+ if (assignment && assignment.affiliate_id !== affiliateId) {
1077
+ setConfirmationOpen(true);
1078
+ return;
1079
+ }
1080
+ saveAssignment();
1081
+ },
1082
+ children: texts.assignment.save
1083
+ }
1084
+ ) : null,
1085
+ /* @__PURE__ */ jsxRuntime.jsx(
1086
+ ui.Button,
1087
+ {
1088
+ size: "small",
1089
+ variant: "secondary",
1090
+ disabled: controlState.removeDisabled,
1091
+ onClick: () => removeMutation.mutate(void 0, {
1092
+ onSuccess: () => {
1093
+ ui.toast.success(texts.assignment.toasts.removed);
1094
+ setAffiliateId("");
1095
+ },
1096
+ onError: (error) => ui.toast.error(texts.assignment.toasts.removeFailed, {
1097
+ description: getErrorMessage(error)
1098
+ })
1099
+ }),
1100
+ children: texts.assignment.remove
1101
+ }
1102
+ )
1103
+ ] })
1104
+ ] }),
1105
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-3 px-6 py-4", children: [
1106
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: texts.assignment.current }),
1107
+ assignment ? /* @__PURE__ */ jsxRuntime.jsx(AssignmentHistoryItem, { assignment }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-sm", children: texts.assignment.none })
1108
+ ] }),
1109
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-3 px-6 py-4", children: [
1110
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: texts.assignment.history }),
1111
+ assignmentQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-sm", children: texts.assignment.checking }) : assignmentQuery.isError ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error text-sm", children: getErrorMessage(assignmentQuery.error) }) : history.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-2", children: history.map((item) => /* @__PURE__ */ jsxRuntime.jsx(AssignmentHistoryItem, { assignment: item }, item.id)) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-sm", children: texts.assignment.historyEmpty })
1112
+ ] }),
1113
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt, { open: confirmationOpen, onOpenChange: setConfirmationOpen, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Content, { children: [
1114
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Header, { children: [
1115
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Title, { children: "Affiliate-Zuordnung ändern?" }),
1116
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Description, { children: "Die aktuelle Zuordnung wird beendet und historisch erhalten. Zukünftige Bestellungen werden dem neu gewählten Affiliate zugeordnet." })
1117
+ ] }),
1118
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Footer, { children: [
1119
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Cancel, { children: "Abbrechen" }),
1120
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Action, { onClick: saveAssignment, children: "Zuordnung ändern" })
1121
+ ] })
1122
+ ] }) })
1123
+ ] });
1124
+ };
1125
+ const AssignmentHistoryItem = ({
1126
+ assignment
1127
+ }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-ui-border-base grid gap-1 rounded-md border p-3 text-sm", children: [
1128
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: assignment.affiliate?.display_name ?? assignment.affiliate_id }),
1129
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
1130
+ texts.assignment.mode,
1131
+ ": ",
1132
+ formatAssignmentMode(assignment.customer_assignment_mode),
1133
+ assignment.assignment_duration_days ? `, ${assignment.assignment_duration_days} Tage` : `, ${texts.assignment.lifetime}`
1134
+ ] }),
1135
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
1136
+ texts.assignment.assignedAt,
1137
+ ": ",
1138
+ formatDateTime(assignment.assigned_at),
1139
+ assignment.valid_until ? `, ${texts.assignment.validUntil(formatDateTime(assignment.valid_until))}` : `, ${texts.assignment.lifetime}`
1140
+ ] }),
1141
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
1142
+ assignment.active ? texts.assignment.active : texts.assignment.ended,
1143
+ !assignment.active ? `, ${texts.assignment.endedReason}: ${formatAssignmentEndedReason(assignment.ended_reason)}` : ""
1144
+ ] })
1145
+ ] });
1146
+ const CustomerAffiliateWidget = ({ data }) => /* @__PURE__ */ jsxRuntime.jsx(CustomerAffiliateAssignmentPanel, { customerId: data.id });
1147
+ adminSdk.defineWidgetConfig({
1148
+ zone: "customer.details.side.after"
1149
+ });
1150
+ const useOrderAffiliateAttribution = (orderId) => reactQuery.useQuery({
1151
+ queryKey: affiliateKeys.orderAttribution(orderId),
1152
+ queryFn: () => getOrderAffiliateAttribution(orderId)
1153
+ });
1154
+ const formatMode = (mode) => {
1155
+ switch (mode) {
1156
+ case "lifetime":
1157
+ return "Lifetime";
1158
+ case "time_limited":
1159
+ return "Zeitlich begrenzt";
1160
+ case "order_only":
1161
+ return "Nur Bestellung";
1162
+ }
1163
+ };
1164
+ const formatSource = (source) => texts.orderAttribution.sources[source];
1165
+ const OrderAffiliateAttributionPanel = ({ orderId }) => {
1166
+ const attributionQuery = useOrderAffiliateAttribution(orderId);
1167
+ const attribution = attributionQuery.data?.attribution;
1168
+ let content;
1169
+ if (attributionQuery.isLoading) {
1170
+ content = /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.orderAttribution.loading });
1171
+ } else if (attributionQuery.isError) {
1172
+ content = /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: texts.orderAttribution.loadError(getErrorMessage(attributionQuery.error)) });
1173
+ } else if (!attribution) {
1174
+ content = /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.orderAttribution.empty });
1175
+ } else {
1176
+ const fields = [
1177
+ [texts.orderAttribution.affiliate, attribution.affiliate.display_name],
1178
+ [texts.orderAttribution.source, formatSource(attribution.source)],
1179
+ [
1180
+ texts.orderAttribution.referralSlug,
1181
+ attribution.source === "referral_link" ? attribution.slug_or_code_snapshot ?? texts.common.dash : texts.common.dash
1182
+ ],
1183
+ [
1184
+ texts.orderAttribution.referralCoupon,
1185
+ attribution.source === "coupon" ? attribution.slug_or_code_snapshot ?? texts.common.dash : texts.common.dash
1186
+ ],
1187
+ [
1188
+ texts.orderAttribution.appliedCoupon,
1189
+ attribution.applied_coupon?.code ?? texts.orderAttribution.noAppliedCoupon
1190
+ ],
1191
+ [
1192
+ texts.orderAttribution.appliedCouponId,
1193
+ attribution.applied_coupon?.affiliate_coupon_id ?? texts.common.dash
1194
+ ],
1195
+ [
1196
+ texts.orderAttribution.appliedPromotionId,
1197
+ attribution.applied_coupon?.promotion_id ?? texts.common.dash
1198
+ ],
1199
+ [texts.orderAttribution.mode, formatMode(attribution.customer_assignment_mode)],
1200
+ [
1201
+ texts.orderAttribution.duration,
1202
+ attribution.assignment_duration_days ? `${attribution.assignment_duration_days} Tage` : texts.orderAttribution.noDuration
1203
+ ],
1204
+ [
1205
+ texts.orderAttribution.assignmentReference,
1206
+ attribution.customer_affiliate_assignment_id ?? texts.orderAttribution.noReference
1207
+ ],
1208
+ [texts.orderAttribution.commissionRate, `${attribution.commission_rate}%`],
1209
+ [
1210
+ texts.orderAttribution.referrerAffiliate,
1211
+ attribution.referrer_affiliate_id ?? texts.orderAttribution.noReferrerAffiliate
1212
+ ],
1213
+ [
1214
+ texts.orderAttribution.referrerRate,
1215
+ attribution.referrer_commission_rate == null ? texts.orderAttribution.noReferrerRate : `${attribution.referrer_commission_rate}%`
1216
+ ],
1217
+ [texts.orderAttribution.currency, attribution.currency_code.toUpperCase()],
1218
+ [texts.orderAttribution.attributedAt, formatDateTime(attribution.attributed_at)]
1219
+ ];
1220
+ content = /* @__PURE__ */ jsxRuntime.jsx("dl", { className: "grid gap-x-4 gap-y-3 text-sm md:grid-cols-2", children: fields.map(([label, value]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-0.5", children: [
1221
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-ui-fg-subtle", children: label }),
1222
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "break-all font-medium", children: value })
1223
+ ] }, label)) });
1224
+ }
1225
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "grid gap-4", children: [
1226
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: texts.orderAttribution.heading }),
1227
+ content
1228
+ ] });
1229
+ };
1230
+ const OrderAffiliateAttributionWidget = ({
1231
+ data
1232
+ }) => /* @__PURE__ */ jsxRuntime.jsx(OrderAffiliateAttributionPanel, { orderId: data.id });
1233
+ adminSdk.defineWidgetConfig({
1234
+ zone: "order.details.side.after"
1235
+ });
1236
+ const AFFILIATES_PAGE_SIZE = 20;
1237
+ const AffiliateRow = ({ affiliate: affiliate2 }) => {
1238
+ const primarySlug = affiliate2.slugs?.find((slug) => slug.id === affiliate2.primary_slug_id) ?? affiliate2.slugs?.find((slug) => slug.is_primary);
1239
+ const reactivateMutation = useUpdateAffiliate(affiliate2.id);
1240
+ const canReactivate = affiliate2.status === "inactive" || affiliate2.status === "rejected";
1241
+ const reactivate = () => {
1242
+ reactivateMutation.mutate(
1243
+ { status: "active" },
1244
+ {
1245
+ onSuccess: () => ui.toast.success(texts.list.toasts.reactivated),
1246
+ onError: (error) => ui.toast.error(texts.list.toasts.reactivateFailed, {
1247
+ description: getErrorMessage(error)
1248
+ })
1249
+ }
1250
+ );
1251
+ };
1252
+ return /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "border-ui-border-base border-b", children: [
1253
+ /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "px-6 py-3", children: [
1254
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium", children: affiliate2.display_name }),
1255
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-ui-fg-subtle font-mono text-xs", children: affiliate2.customer_id ?? affiliate2.id })
1256
+ ] }),
1257
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 capitalize", children: formatStatus(affiliate2.status) }),
1258
+ /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "px-6 py-3 text-right tabular-nums", children: [
1259
+ affiliate2.commission_rate,
1260
+ "%"
1261
+ ] }),
1262
+ /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "px-6 py-3 text-right tabular-nums", children: [
1263
+ affiliate2.own_order_discount_rate,
1264
+ "%"
1265
+ ] }),
1266
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3", children: primarySlug?.slug ?? texts.common.dash }),
1267
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3", children: formatDate(affiliate2.created_at) }),
1268
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 text-right", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2", children: [
1269
+ canReactivate ? /* @__PURE__ */ jsxRuntime.jsx(
1270
+ ui.Button,
1271
+ {
1272
+ size: "small",
1273
+ variant: "secondary",
1274
+ disabled: reactivateMutation.isPending,
1275
+ onClick: reactivate,
1276
+ children: texts.list.reactivate
1277
+ }
1278
+ ) : null,
1279
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: `/affiliates/${affiliate2.id}`, children: texts.list.edit }) })
1280
+ ] }) })
1281
+ ] });
1282
+ };
1283
+ const AffiliatesTable = ({ affiliates }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full text-left text-sm", children: [
1284
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "border-ui-border-base border-b", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "text-ui-fg-subtle", children: [
1285
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: texts.list.columns.affiliate }),
1286
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: texts.list.columns.status }),
1287
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right font-medium", children: texts.list.columns.commission }),
1288
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right font-medium", children: texts.list.columns.ownDiscount }),
1289
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: texts.list.columns.primarySlug }),
1290
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: texts.list.columns.created }),
1291
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right font-medium", children: texts.list.columns.action })
1292
+ ] }) }),
1293
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: affiliates.map((affiliate2) => /* @__PURE__ */ jsxRuntime.jsx(AffiliateRow, { affiliate: affiliate2 }, affiliate2.id)) })
1294
+ ] }) });
1295
+ const AffiliateListPage = () => {
1296
+ const [status, setStatus] = react.useState("active");
1297
+ const [q, setQ] = react.useState("");
1298
+ const [offset, setOffset] = react.useState(0);
1299
+ const deferredQ = react.useDeferredValue(q);
1300
+ react.useEffect(() => {
1301
+ setOffset(0);
1302
+ }, [status, deferredQ]);
1303
+ const affiliatesQuery = useAffiliates({
1304
+ status: status === "all" ? void 0 : status,
1305
+ q: deferredQ || void 0,
1306
+ limit: AFFILIATES_PAGE_SIZE,
1307
+ offset
1308
+ });
1309
+ const affiliates = affiliatesQuery.data?.affiliates ?? [];
1310
+ const count = affiliatesQuery.data?.count ?? 0;
1311
+ const hasFilters = Boolean(deferredQ) || status !== "all";
1312
+ const renderBody = () => {
1313
+ if (affiliatesQuery.isLoading) {
1314
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.list.loading }) });
1315
+ }
1316
+ if (affiliatesQuery.isError) {
1317
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.list.loadError(getErrorMessage(affiliatesQuery.error)) }) });
1318
+ }
1319
+ if (!affiliates.length) {
1320
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: hasFilters ? texts.list.emptyFiltered : texts.list.empty }) });
1321
+ }
1322
+ return /* @__PURE__ */ jsxRuntime.jsx(AffiliatesTable, { affiliates });
1323
+ };
1324
+ const showPagination = !affiliatesQuery.isLoading && !affiliatesQuery.isError && count > 0;
1325
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
1326
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 px-6 py-4 lg:flex-row lg:items-center lg:justify-between", children: [
1327
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1328
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: texts.list.heading }),
1329
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.list.description })
1330
+ ] }),
1331
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 sm:flex-row", children: [
1332
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { value: q, onChange: (event) => setQ(event.target.value), placeholder: texts.list.searchPlaceholder }),
1333
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select, { value: status, onValueChange: setStatus, children: [
1334
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { className: "w-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: texts.list.statusAll }) }),
1335
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
1336
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "all", children: texts.list.statusOptions.all }),
1337
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "active", children: texts.list.statusOptions.active }),
1338
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "inactive", children: texts.list.statusOptions.inactive }),
1339
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "rejected", children: texts.list.statusOptions.rejected })
1340
+ ] })
1341
+ ] }),
1342
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: "/affiliates/new", children: texts.list.create }) })
1343
+ ] })
1344
+ ] }),
1345
+ renderBody(),
1346
+ showPagination ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
1347
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-sm", children: texts.common.pageRange(offset + 1, offset + affiliates.length, count) }),
1348
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
1349
+ /* @__PURE__ */ jsxRuntime.jsx(
1350
+ ui.Button,
1351
+ {
1352
+ size: "small",
1353
+ variant: "secondary",
1354
+ disabled: offset === 0,
1355
+ onClick: () => setOffset((current) => Math.max(0, current - AFFILIATES_PAGE_SIZE)),
1356
+ children: texts.common.back
1357
+ }
1358
+ ),
1359
+ /* @__PURE__ */ jsxRuntime.jsx(
1360
+ ui.Button,
1361
+ {
1362
+ size: "small",
1363
+ variant: "secondary",
1364
+ disabled: offset + affiliates.length >= count,
1365
+ onClick: () => setOffset((current) => current + AFFILIATES_PAGE_SIZE),
1366
+ children: texts.common.next
1367
+ }
1368
+ )
1369
+ ] })
1370
+ ] }) : null
1371
+ ] });
1372
+ };
1373
+ const AffiliatesRoute = () => /* @__PURE__ */ jsxRuntime.jsx(AffiliateListPage, {});
1374
+ const config$6 = adminSdk.defineRouteConfig({
1375
+ label: texts.routes.affiliates,
1376
+ rank: 81
1377
+ });
1378
+ const useAffiliateApplications = (params) => reactQuery.useQuery({
1379
+ queryKey: affiliateKeys.applicationList(params),
1380
+ queryFn: () => listAffiliateApplications({
1381
+ status: params.status || void 0,
1382
+ limit: params.limit,
1383
+ offset: params.offset
1384
+ })
1385
+ });
1386
+ const useApproveAffiliateApplication = (applicationId) => {
1387
+ const queryClient = reactQuery.useQueryClient();
1388
+ return reactQuery.useMutation({
1389
+ mutationFn: (payload) => {
1390
+ if (!applicationId) {
1391
+ throw new Error(texts.hooks.noApplicationSelected);
1392
+ }
1393
+ return approveAffiliateApplication(applicationId, payload);
1394
+ },
1395
+ onSuccess: async () => {
1396
+ await queryClient.invalidateQueries({
1397
+ queryKey: affiliateKeys.applications()
1398
+ });
1399
+ await queryClient.invalidateQueries({ queryKey: affiliateKeys.lists() });
1400
+ }
1401
+ });
1402
+ };
1403
+ const useRejectAffiliateApplication = (applicationId) => {
1404
+ const queryClient = reactQuery.useQueryClient();
1405
+ return reactQuery.useMutation({
1406
+ mutationFn: (payload) => {
1407
+ if (!applicationId) {
1408
+ throw new Error(texts.hooks.noApplicationSelected);
1409
+ }
1410
+ return rejectAffiliateApplication(applicationId, payload);
1411
+ },
1412
+ onSuccess: async () => {
1413
+ await queryClient.invalidateQueries({
1414
+ queryKey: affiliateKeys.applications()
1415
+ });
1416
+ await queryClient.invalidateQueries({ queryKey: affiliateKeys.lists() });
1417
+ }
1418
+ });
1419
+ };
1420
+ const PercentRateInput = ({
1421
+ label,
1422
+ value,
1423
+ onChange
1424
+ }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
1425
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: label }),
1426
+ /* @__PURE__ */ jsxRuntime.jsx(
1427
+ ui.Input,
1428
+ {
1429
+ min: 0,
1430
+ max: 100,
1431
+ step: "0.01",
1432
+ type: "number",
1433
+ value,
1434
+ onChange: (event) => onChange(Number(event.target.value))
1435
+ }
1436
+ )
1437
+ ] });
1438
+ const DEFAULT_COMMISSION_RATE = 10;
1439
+ const DEFAULT_OWN_ORDER_DISCOUNT_RATE = 0;
1440
+ const APPLICATIONS_PAGE_SIZE = 20;
1441
+ const ApplicationsTable = ({
1442
+ applications,
1443
+ onSelect
1444
+ }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full text-left text-sm", children: [
1445
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "border-ui-border-base border-b", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "text-ui-fg-subtle", children: [
1446
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: texts.applications.columns.applicant }),
1447
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: texts.applications.columns.customer }),
1448
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: texts.applications.columns.slug }),
1449
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: texts.applications.columns.submitted }),
1450
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right font-medium", children: texts.applications.columns.action })
1451
+ ] }) }),
1452
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: applications.map((application) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "border-ui-border-base border-b", children: [
1453
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 font-medium", children: application.display_name }),
1454
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 font-mono text-xs", children: application.customer_id }),
1455
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3", children: application.requested_slug ?? texts.common.dash }),
1456
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3", children: formatDateTime(application.created_at) }),
1457
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 text-right", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", onClick: () => onSelect(application), children: texts.applications.review }) })
1458
+ ] }, application.id)) })
1459
+ ] }) });
1460
+ const AffiliateApplicationsPage = () => {
1461
+ const [offset, setOffset] = react.useState(0);
1462
+ const [selected, setSelected] = react.useState(null);
1463
+ const [commissionRate, setCommissionRate] = react.useState(DEFAULT_COMMISSION_RATE);
1464
+ const [ownOrderDiscountRate, setOwnOrderDiscountRate] = react.useState(DEFAULT_OWN_ORDER_DISCOUNT_RATE);
1465
+ const [slug, setSlug] = react.useState("");
1466
+ const [rejectionReason, setRejectionReason] = react.useState("");
1467
+ const applicationsQuery = useAffiliateApplications({
1468
+ status: "pending",
1469
+ limit: APPLICATIONS_PAGE_SIZE,
1470
+ offset
1471
+ });
1472
+ const approveMutation = useApproveAffiliateApplication(selected?.id);
1473
+ const rejectMutation = useRejectAffiliateApplication(selected?.id);
1474
+ const reviewPending = approveMutation.isPending || rejectMutation.isPending;
1475
+ const applications = applicationsQuery.data?.applications ?? [];
1476
+ const count = applicationsQuery.data?.count ?? 0;
1477
+ const showPagination = !applicationsQuery.isLoading && !applicationsQuery.isError && count > 0;
1478
+ const selectApplication = (application) => {
1479
+ setSelected(application);
1480
+ setSlug(application.requested_slug ?? "");
1481
+ setRejectionReason(application.rejection_reason ?? "");
1482
+ setCommissionRate(DEFAULT_COMMISSION_RATE);
1483
+ setOwnOrderDiscountRate(DEFAULT_OWN_ORDER_DISCOUNT_RATE);
1484
+ };
1485
+ const approve = (event) => {
1486
+ event.preventDefault();
1487
+ approveMutation.mutate(
1488
+ {
1489
+ commission_rate: commissionRate,
1490
+ own_order_discount_rate: ownOrderDiscountRate,
1491
+ slug: slug || selected?.requested_slug || null
1492
+ },
1493
+ {
1494
+ onSuccess: () => {
1495
+ ui.toast.success(texts.applications.toasts.approved);
1496
+ setSelected(null);
1497
+ },
1498
+ onError: (error) => ui.toast.error(texts.applications.toasts.approveFailed, {
1499
+ description: getErrorMessage(error)
1500
+ })
1501
+ }
1502
+ );
1503
+ };
1504
+ const reject = (event) => {
1505
+ event.preventDefault();
1506
+ rejectMutation.mutate(
1507
+ { rejection_reason: rejectionReason || null },
1508
+ {
1509
+ onSuccess: () => {
1510
+ ui.toast.success(texts.applications.toasts.rejected);
1511
+ setSelected(null);
1512
+ setRejectionReason("");
1513
+ },
1514
+ onError: (error) => ui.toast.error(texts.applications.toasts.rejectFailed, {
1515
+ description: getErrorMessage(error)
1516
+ })
1517
+ }
1518
+ );
1519
+ };
1520
+ const renderBody = () => {
1521
+ if (applicationsQuery.isLoading) {
1522
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.applications.loading }) });
1523
+ }
1524
+ if (applicationsQuery.isError) {
1525
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.applications.loadError(getErrorMessage(applicationsQuery.error)) }) });
1526
+ }
1527
+ if (!applications.length) {
1528
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.applications.empty }) });
1529
+ }
1530
+ return /* @__PURE__ */ jsxRuntime.jsx(ApplicationsTable, { applications, onSelect: selectApplication });
1531
+ };
1532
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6", children: [
1533
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
1534
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between gap-4 px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1535
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: texts.applications.heading }),
1536
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.applications.description })
1537
+ ] }) }),
1538
+ renderBody(),
1539
+ showPagination ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
1540
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-sm", children: texts.common.pageRange(offset + 1, offset + applications.length, count) }),
1541
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
1542
+ /* @__PURE__ */ jsxRuntime.jsx(
1543
+ ui.Button,
1544
+ {
1545
+ size: "small",
1546
+ variant: "secondary",
1547
+ disabled: offset === 0,
1548
+ onClick: () => setOffset((current) => Math.max(0, current - APPLICATIONS_PAGE_SIZE)),
1549
+ children: texts.common.back
1550
+ }
1551
+ ),
1552
+ /* @__PURE__ */ jsxRuntime.jsx(
1553
+ ui.Button,
1554
+ {
1555
+ size: "small",
1556
+ variant: "secondary",
1557
+ disabled: offset + applications.length >= count,
1558
+ onClick: () => setOffset((current) => current + APPLICATIONS_PAGE_SIZE),
1559
+ children: texts.common.next
1560
+ }
1561
+ )
1562
+ ] })
1563
+ ] }) : null
1564
+ ] }),
1565
+ selected ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
1566
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
1567
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: texts.applications.reviewHeading(selected.display_name) }),
1568
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
1569
+ texts.applications.customerLine(selected.customer_id),
1570
+ selected.message ? ` · ${selected.message}` : ""
1571
+ ] })
1572
+ ] }),
1573
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-5 px-6 py-5 lg:grid-cols-2", children: [
1574
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: approve, className: "grid gap-4", children: [
1575
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: texts.applications.approveHeading }),
1576
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
1577
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: texts.applications.affiliateSlugLabel }),
1578
+ /* @__PURE__ */ jsxRuntime.jsx(
1579
+ ui.Input,
1580
+ {
1581
+ value: slug,
1582
+ onChange: (event) => setSlug(event.target.value),
1583
+ placeholder: texts.applications.affiliateSlugPlaceholder
1584
+ }
1585
+ )
1586
+ ] }),
1587
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
1588
+ /* @__PURE__ */ jsxRuntime.jsx(
1589
+ PercentRateInput,
1590
+ {
1591
+ label: texts.applications.commissionLabel,
1592
+ value: commissionRate,
1593
+ onChange: setCommissionRate
1594
+ }
1595
+ ),
1596
+ /* @__PURE__ */ jsxRuntime.jsx(
1597
+ PercentRateInput,
1598
+ {
1599
+ label: texts.applications.ownDiscountLabel,
1600
+ value: ownOrderDiscountRate,
1601
+ onChange: setOwnOrderDiscountRate
1602
+ }
1603
+ )
1604
+ ] }),
1605
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", disabled: reviewPending, children: approveMutation.isPending ? texts.applications.approving : texts.applications.approve }) })
1606
+ ] }),
1607
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: reject, className: "grid gap-4", children: [
1608
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: texts.applications.rejectHeading }),
1609
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
1610
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: texts.applications.reasonLabel }),
1611
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Textarea, { value: rejectionReason, onChange: (event) => setRejectionReason(event.target.value) })
1612
+ ] }),
1613
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", variant: "danger", disabled: reviewPending, children: rejectMutation.isPending ? texts.applications.rejecting : texts.applications.reject }) })
1614
+ ] })
1615
+ ] })
1616
+ ] }) : null
1617
+ ] });
1618
+ };
1619
+ const AffiliateApplicationsRoute = () => /* @__PURE__ */ jsxRuntime.jsx(AffiliateApplicationsPage, {});
1620
+ const config$5 = adminSdk.defineRouteConfig({
1621
+ label: texts.routes.applications,
1622
+ rank: 80
1623
+ });
1624
+ const getCustomerLabel = (customer) => {
1625
+ const name = [customer.first_name, customer.last_name].filter(Boolean).join(" ");
1626
+ return name ? `${name} (${customer.email})` : customer.email;
1627
+ };
1628
+ const CustomerReferenceField = ({
1629
+ label = texts.reference.customer.label,
1630
+ value,
1631
+ onChange
1632
+ }) => {
1633
+ const [search, setSearch] = react.useState("");
1634
+ const [selectedLabel, setSelectedLabel] = react.useState("");
1635
+ const deferredSearch = react.useDeferredValue(search);
1636
+ const customersQuery = reactQuery.useQuery({
1637
+ queryKey: affiliateKeys.customerReferenceSearch(deferredSearch),
1638
+ queryFn: () => listCustomers({
1639
+ q: deferredSearch.trim(),
1640
+ limit: 10,
1641
+ offset: 0
1642
+ })
1643
+ });
1644
+ const selectedCustomerQuery = reactQuery.useQuery({
1645
+ queryKey: affiliateKeys.customerReference(value),
1646
+ enabled: !!value,
1647
+ queryFn: () => retrieveCustomer(value)
1648
+ });
1649
+ const options = (customersQuery.data?.customers ?? []).map(
1650
+ (customer) => ({
1651
+ id: customer.id,
1652
+ label: getCustomerLabel(customer),
1653
+ description: customer.id
1654
+ })
1655
+ );
1656
+ const selectedCustomer = selectedCustomerQuery.data?.customer;
1657
+ const displayLabel = selectedLabel || (selectedCustomer ? getCustomerLabel(selectedCustomer) : value);
1658
+ return /* @__PURE__ */ jsxRuntime.jsx(
1659
+ SearchableReferenceField,
1660
+ {
1661
+ label,
1662
+ value,
1663
+ selectedLabel: displayLabel,
1664
+ search,
1665
+ placeholder: texts.reference.customer.placeholder,
1666
+ emptyText: texts.reference.customer.empty,
1667
+ isLoading: customersQuery.isLoading || customersQuery.isFetching,
1668
+ options,
1669
+ onSearchChange: setSearch,
1670
+ onSelect: (option) => {
1671
+ onChange(option.id);
1672
+ setSelectedLabel(option.label);
1673
+ setSearch("");
1674
+ },
1675
+ onClear: () => {
1676
+ onChange("");
1677
+ setSelectedLabel("");
1678
+ setSearch("");
1679
+ }
1680
+ }
1681
+ );
1682
+ };
1683
+ const STATUS_OPTIONS = [
1684
+ { value: "pending", label: texts.statusSelect.options.pending },
1685
+ { value: "active", label: texts.statusSelect.options.active },
1686
+ { value: "inactive", label: texts.statusSelect.options.inactive },
1687
+ { value: "rejected", label: texts.statusSelect.options.rejected }
1688
+ ];
1689
+ const AffiliateStatusSelect = ({
1690
+ value,
1691
+ onChange,
1692
+ label = texts.statusSelect.label
1693
+ }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
1694
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: label }),
1695
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select, { value, onValueChange: onChange, children: [
1696
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, {}) }),
1697
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: STATUS_OPTIONS.map((option) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: option.value, children: option.label }, option.value)) })
1698
+ ] })
1699
+ ] });
1700
+ const assignmentDurationSelectId = "affiliate-assignment-duration";
1701
+ const assignmentDurationSelectLabelId = `${assignmentDurationSelectId}-label`;
1702
+ const AssignmentDurationSelect = ({
1703
+ presetDurationDays,
1704
+ persistedDurationDays,
1705
+ onPresetDurationChange
1706
+ }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid max-w-[180px] gap-2", children: [
1707
+ /* @__PURE__ */ jsxRuntime.jsx(
1708
+ ui.Label,
1709
+ {
1710
+ id: assignmentDurationSelectLabelId,
1711
+ htmlFor: assignmentDurationSelectId,
1712
+ children: texts.form.fields.assignmentDurationDays
1713
+ }
1714
+ ),
1715
+ /* @__PURE__ */ jsxRuntime.jsxs(
1716
+ ui.Select,
1717
+ {
1718
+ value: persistedDurationDays == null ? String(presetDurationDays) : "persisted",
1719
+ onValueChange: (value) => {
1720
+ switch (value) {
1721
+ case "30":
1722
+ onPresetDurationChange(30);
1723
+ break;
1724
+ case "90":
1725
+ onPresetDurationChange(90);
1726
+ break;
1727
+ case "180":
1728
+ onPresetDurationChange(180);
1729
+ break;
1730
+ }
1731
+ },
1732
+ children: [
1733
+ /* @__PURE__ */ jsxRuntime.jsx(
1734
+ ui.Select.Trigger,
1735
+ {
1736
+ id: assignmentDurationSelectId,
1737
+ "aria-labelledby": assignmentDurationSelectLabelId,
1738
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, {})
1739
+ }
1740
+ ),
1741
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
1742
+ persistedDurationDays != null ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Item, { value: "persisted", children: [
1743
+ "Aktuell: ",
1744
+ persistedDurationDays,
1745
+ " Tage"
1746
+ ] }) : null,
1747
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "30", children: "30 Tage" }),
1748
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "90", children: "90 Tage" }),
1749
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "180", children: "180 Tage" })
1750
+ ] })
1751
+ ]
1752
+ }
1753
+ )
1754
+ ] });
1755
+ function ConsignmentTransferForm({
1756
+ affiliateId
1757
+ }) {
1758
+ const queryClient = reactQuery.useQueryClient();
1759
+ const [sourceLocationId, setSourceLocationId] = react.useState("");
1760
+ const [inventoryItemId, setInventoryItemId] = react.useState("");
1761
+ const [quantity, setQuantity] = react.useState(1);
1762
+ const [batchMode, setBatchMode] = react.useState(false);
1763
+ const [batchId, setBatchId] = react.useState("");
1764
+ const stock = reactQuery.useQuery({
1765
+ queryKey: [
1766
+ "affiliate",
1767
+ "source-stock",
1768
+ sourceLocationId,
1769
+ inventoryItemId
1770
+ ],
1771
+ queryFn: () => getSourceInventoryQuantity(inventoryItemId, sourceLocationId),
1772
+ enabled: Boolean(sourceLocationId && inventoryItemId)
1773
+ });
1774
+ const batches = reactQuery.useQuery({
1775
+ queryKey: [
1776
+ "affiliate",
1777
+ "source-batches",
1778
+ sourceLocationId,
1779
+ inventoryItemId
1780
+ ],
1781
+ queryFn: () => listSourceBatches(inventoryItemId, sourceLocationId),
1782
+ enabled: Boolean(batchMode && sourceLocationId && inventoryItemId)
1783
+ });
1784
+ const transfer = reactQuery.useMutation({
1785
+ mutationFn: (idempotencyKey) => transferConsignmentStock(affiliateId, {
1786
+ source_location_id: sourceLocationId,
1787
+ idempotency_key: idempotencyKey,
1788
+ lines: [
1789
+ {
1790
+ inventory_item_id: inventoryItemId,
1791
+ quantity,
1792
+ ...batchMode ? { batch_id: batchId } : {}
1793
+ }
1794
+ ]
1795
+ }),
1796
+ onSuccess: async () => {
1797
+ ui.toast.success("Kommissionsbestand wurde übertragen.");
1798
+ setInventoryItemId("");
1799
+ setBatchId("");
1800
+ await queryClient.invalidateQueries({
1801
+ queryKey: ["affiliate", "distribution", affiliateId]
1802
+ });
1803
+ },
1804
+ onError: (error) => ui.toast.error("Übertragung fehlgeschlagen.", {
1805
+ description: getErrorMessage(error)
1806
+ })
1807
+ });
1808
+ const submit = (event) => {
1809
+ event.preventDefault();
1810
+ transfer.mutate(crypto.randomUUID());
1811
+ };
1812
+ const invalid = !sourceLocationId || !inventoryItemId || quantity < 1 || batchMode && !batchId;
1813
+ return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submit, className: "grid gap-4", children: [
1814
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
1815
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
1816
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "consignment-source-location", children: "Quelllager" }),
1817
+ /* @__PURE__ */ jsxRuntime.jsx(
1818
+ ui.Input,
1819
+ {
1820
+ id: "consignment-source-location",
1821
+ required: true,
1822
+ value: sourceLocationId,
1823
+ onChange: (event) => setSourceLocationId(event.target.value),
1824
+ placeholder: "sloc_…"
1825
+ }
1826
+ )
1827
+ ] }),
1828
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
1829
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "consignment-inventory-item", children: "Artikel" }),
1830
+ /* @__PURE__ */ jsxRuntime.jsx(
1831
+ ui.Input,
1832
+ {
1833
+ id: "consignment-inventory-item",
1834
+ required: true,
1835
+ value: inventoryItemId,
1836
+ onChange: (event) => setInventoryItemId(event.target.value),
1837
+ placeholder: "iitem_…"
1838
+ }
1839
+ )
1840
+ ] })
1841
+ ] }),
1842
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
1843
+ "Aktueller Quellbestand:",
1844
+ " ",
1845
+ stock.isLoading ? "wird geladen …" : stock.data == null ? "–" : stock.data
1846
+ ] }),
1847
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [
1848
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
1849
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "consignment-transfer-quantity", children: "Menge" }),
1850
+ /* @__PURE__ */ jsxRuntime.jsx(
1851
+ ui.Input,
1852
+ {
1853
+ id: "consignment-transfer-quantity",
1854
+ type: "number",
1855
+ min: 1,
1856
+ step: 1,
1857
+ value: quantity,
1858
+ onChange: (event) => setQuantity(Number(event.target.value))
1859
+ }
1860
+ )
1861
+ ] }),
1862
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 pt-7 text-sm", children: [
1863
+ /* @__PURE__ */ jsxRuntime.jsx(
1864
+ "input",
1865
+ {
1866
+ type: "checkbox",
1867
+ checked: batchMode,
1868
+ onChange: (event) => setBatchMode(event.target.checked)
1869
+ }
1870
+ ),
1871
+ "Chargenmodus"
1872
+ ] })
1873
+ ] }),
1874
+ batchMode ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
1875
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "consignment-batch", children: "Charge / MHD" }),
1876
+ /* @__PURE__ */ jsxRuntime.jsxs(
1877
+ "select",
1878
+ {
1879
+ id: "consignment-batch",
1880
+ required: true,
1881
+ className: "bg-ui-bg-field border-ui-border-base rounded-md border px-3 py-2 text-sm",
1882
+ value: batchId,
1883
+ onChange: (event) => setBatchId(event.target.value),
1884
+ children: [
1885
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: batches.isLoading ? "Chargen werden geladen …" : "Charge auswählen" }),
1886
+ batches.data?.batches.map((batch) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: batch.id, children: [
1887
+ batch.name,
1888
+ " · MHD",
1889
+ " ",
1890
+ new Date(batch.expiry_date).toLocaleDateString("de-DE"),
1891
+ " ·",
1892
+ " ",
1893
+ batch.quantity,
1894
+ " Stück"
1895
+ ] }, batch.id))
1896
+ ]
1897
+ }
1898
+ ),
1899
+ batches.isError ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-error", children: "Chargen konnten nicht geladen werden. Ist Batch Management installiert?" }) : null
1900
+ ] }) : null,
1901
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", disabled: invalid || transfer.isPending, children: transfer.isPending ? "Wird übertragen …" : "Ware übertragen" }) })
1902
+ ] });
1903
+ }
1904
+ function AffiliateDistributionPanel({
1905
+ affiliateId
1906
+ }) {
1907
+ const queryClient = reactQuery.useQueryClient();
1908
+ const [regionId, setRegionId] = react.useState("");
1909
+ const [shippingOptionId, setShippingOptionId] = react.useState("");
1910
+ const queryKey = ["affiliate", "distribution", affiliateId];
1911
+ const distribution = reactQuery.useQuery({
1912
+ queryKey,
1913
+ queryFn: () => getAffiliateDistribution(affiliateId)
1914
+ });
1915
+ const settings = distribution.data?.distribution;
1916
+ const enable = reactQuery.useMutation({
1917
+ mutationFn: (enabled) => enableAffiliateDistribution(affiliateId, {
1918
+ enabled,
1919
+ ...enabled ? {
1920
+ region_id: regionId || settings?.region_id,
1921
+ shipping_option_id: shippingOptionId || settings?.shipping_option_id
1922
+ } : {}
1923
+ }),
1924
+ onSuccess: async (_data, enabled) => {
1925
+ ui.toast.success(
1926
+ enabled ? "Kommissionsvertrieb ist aktiviert." : "Kommissionsvertrieb ist deaktiviert."
1927
+ );
1928
+ await queryClient.invalidateQueries({ queryKey });
1929
+ },
1930
+ onError: (error, enabled) => ui.toast.error(
1931
+ enabled ? "Kommissionsvertrieb konnte nicht aktiviert werden." : "Kommissionsvertrieb konnte nicht deaktiviert werden.",
1932
+ {
1933
+ description: getErrorMessage(error)
1934
+ }
1935
+ )
1936
+ });
1937
+ const submit = (event) => {
1938
+ event.preventDefault();
1939
+ enable.mutate(true);
1940
+ };
1941
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { id: "affiliate-distribution", className: "grid gap-5", children: [
1942
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1943
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Kommissionsvertrieb" }),
1944
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Internes Partnerlager aktivieren und manuelle Sendungen buchen." })
1945
+ ] }),
1946
+ distribution.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Konfiguration wird geladen …" }) : distribution.isError ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: getErrorMessage(distribution.error) }) : settings ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1947
+ /* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "grid gap-2 text-sm md:grid-cols-2", children: [
1948
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1949
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-ui-fg-subtle", children: "Partnerlager" }),
1950
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: settings.stock_location_id })
1951
+ ] }),
1952
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1953
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-ui-fg-subtle", children: "Interner Sales Channel" }),
1954
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: settings.internal_sales_channel_id })
1955
+ ] }),
1956
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1957
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-ui-fg-subtle", children: "Region" }),
1958
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: settings.region_id })
1959
+ ] }),
1960
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1961
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-ui-fg-subtle", children: "Versandoption" }),
1962
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: settings.shipping_option_id })
1963
+ ] })
1964
+ ] }),
1965
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
1966
+ ui.Button,
1967
+ {
1968
+ type: "button",
1969
+ variant: settings.enabled ? "danger" : "primary",
1970
+ disabled: enable.isPending,
1971
+ onClick: () => enable.mutate(!settings.enabled),
1972
+ children: settings.enabled ? "Kommissionsvertrieb deaktivieren" : "Kommissionsvertrieb reaktivieren"
1973
+ }
1974
+ ) }),
1975
+ settings.enabled ? /* @__PURE__ */ jsxRuntime.jsx(ConsignmentTransferForm, { affiliateId }) : null
1976
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submit, className: "grid gap-4", children: [
1977
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
1978
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
1979
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "distribution-region", children: "Region" }),
1980
+ /* @__PURE__ */ jsxRuntime.jsx(
1981
+ ui.Input,
1982
+ {
1983
+ id: "distribution-region",
1984
+ required: true,
1985
+ value: regionId,
1986
+ onChange: (event) => setRegionId(event.target.value),
1987
+ placeholder: "reg_…"
1988
+ }
1989
+ )
1990
+ ] }),
1991
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
1992
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "distribution-shipping-option", children: "Interne Versandoption" }),
1993
+ /* @__PURE__ */ jsxRuntime.jsx(
1994
+ ui.Input,
1995
+ {
1996
+ id: "distribution-shipping-option",
1997
+ required: true,
1998
+ value: shippingOptionId,
1999
+ onChange: (event) => setShippingOptionId(event.target.value),
2000
+ placeholder: "so_…"
2001
+ }
2002
+ )
2003
+ ] })
2004
+ ] }),
2005
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
2006
+ ui.Button,
2007
+ {
2008
+ type: "submit",
2009
+ disabled: !regionId || !shippingOptionId || enable.isPending,
2010
+ children: enable.isPending ? "Wird aktiviert …" : "Kommissionsvertrieb aktivieren"
2011
+ }
2012
+ ) })
2013
+ ] })
2014
+ ] });
2015
+ }
2016
+ function initialMonth() {
2017
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
2018
+ }
2019
+ function ConsignmentReminderPanel({
2020
+ affiliateId
2021
+ }) {
2022
+ const [month, setMonth] = react.useState(initialMonth);
2023
+ const periodStart = `${month}-01`;
2024
+ const request = reactQuery.useMutation({
2025
+ mutationFn: () => requestConsignmentReminder(affiliateId, {
2026
+ period_start: periodStart
2027
+ }),
2028
+ onSuccess: () => {
2029
+ ui.toast.success("Reminder wurde angefordert.");
2030
+ },
2031
+ onError: (error) => {
2032
+ const message = getErrorMessage(error);
2033
+ ui.toast.error("Reminder konnte nicht angefordert werden.", {
2034
+ description: message
2035
+ });
2036
+ }
2037
+ });
2038
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "grid gap-4", children: [
2039
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2040
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Meldungs-Reminder" }),
2041
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Einen Reminder für einen frei gewählten Meldezeitraum anfordern." })
2042
+ ] }),
2043
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-3", children: [
2044
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
2045
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "affiliate-reminder-month", children: "Zeitraum" }),
2046
+ /* @__PURE__ */ jsxRuntime.jsx(
2047
+ ui.Input,
2048
+ {
2049
+ id: "affiliate-reminder-month",
2050
+ type: "month",
2051
+ value: month,
2052
+ onChange: (event) => setMonth(event.target.value)
2053
+ }
2054
+ )
2055
+ ] }),
2056
+ /* @__PURE__ */ jsxRuntime.jsx(
2057
+ ui.Button,
2058
+ {
2059
+ type: "button",
2060
+ onClick: () => request.mutate(),
2061
+ disabled: request.isPending || !/^\d{4}-\d{2}$/.test(month),
2062
+ children: request.isPending ? "Wird angefordert …" : "Reminder anfordern"
2063
+ }
2064
+ )
2065
+ ] })
2066
+ ] });
2067
+ }
2068
+ const resolveCouponCommandIdempotency = (current, intent, createKey = () => crypto.randomUUID()) => current?.intent === intent ? current : { intent, key: createKey() };
2069
+ const COUPON_PAGE_SIZE = 10;
2070
+ const CouponCodeChoice = ({
2071
+ prefix,
2072
+ label,
2073
+ mode,
2074
+ code,
2075
+ onModeChange,
2076
+ onCodeChange
2077
+ }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-3", children: [
2078
+ /* @__PURE__ */ jsxRuntime.jsxs(
2079
+ ui.RadioGroup,
2080
+ {
2081
+ "aria-label": label,
2082
+ value: mode,
2083
+ onValueChange: (value) => onModeChange(value),
2084
+ className: "flex flex-col gap-3 sm:flex-row sm:gap-6",
2085
+ children: [
2086
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2087
+ /* @__PURE__ */ jsxRuntime.jsx(ui.RadioGroup.Item, { id: `${prefix}-generated`, value: "generated" }),
2088
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: `${prefix}-generated`, children: texts.coupons.generated })
2089
+ ] }),
2090
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2091
+ /* @__PURE__ */ jsxRuntime.jsx(ui.RadioGroup.Item, { id: `${prefix}-custom`, value: "custom" }),
2092
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: `${prefix}-custom`, children: texts.coupons.custom })
2093
+ ] })
2094
+ ]
2095
+ }
2096
+ ),
2097
+ mode === "custom" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
2098
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: `${prefix}-code`, children: texts.coupons.customCodeLabel }),
2099
+ /* @__PURE__ */ jsxRuntime.jsx(
2100
+ ui.Input,
2101
+ {
2102
+ id: `${prefix}-code`,
2103
+ required: true,
2104
+ minLength: 3,
2105
+ maxLength: 32,
2106
+ pattern: "[A-Za-z0-9][A-Za-z0-9-]{2,31}",
2107
+ value: code,
2108
+ onChange: (event) => onCodeChange(event.target.value.toUpperCase()),
2109
+ placeholder: texts.coupons.customCodePlaceholder
2110
+ }
2111
+ ),
2112
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: texts.coupons.codeHint })
2113
+ ] }) : null
2114
+ ] });
2115
+ const actorLabel = (type, id) => {
2116
+ if (!type) {
2117
+ return texts.common.dash;
2118
+ }
2119
+ return texts.coupons.actor(texts.coupons.actorTypes[type], id);
2120
+ };
2121
+ const CouponDetails = ({ coupon }) => {
2122
+ const fields = [
2123
+ [texts.coupons.details.couponId, coupon.id],
2124
+ [
2125
+ texts.coupons.details.promotionId,
2126
+ coupon.promotion_id ?? texts.common.dash
2127
+ ],
2128
+ [
2129
+ texts.coupons.details.idempotencyKey,
2130
+ coupon.creation_idempotency_key ?? texts.common.dash
2131
+ ],
2132
+ [texts.coupons.details.created, formatDateTime(coupon.created_at)],
2133
+ [
2134
+ texts.coupons.details.createdBy,
2135
+ actorLabel(coupon.created_by_type, coupon.created_by_id)
2136
+ ],
2137
+ [texts.coupons.details.revoked, formatDateTime(coupon.revoked_at)],
2138
+ [
2139
+ texts.coupons.details.revokedBy,
2140
+ actorLabel(coupon.revoked_by_type, coupon.revoked_by_id)
2141
+ ]
2142
+ ];
2143
+ return /* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "grid gap-x-5 gap-y-3 text-sm sm:grid-cols-2 lg:grid-cols-3", children: [
2144
+ fields.map(([label, value]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid min-w-0 gap-0.5", children: [
2145
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-ui-fg-subtle", children: label }),
2146
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "break-all font-medium", children: value })
2147
+ ] }, label)),
2148
+ coupon.promotion_setup_error ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid min-w-0 gap-0.5 sm:col-span-2 lg:col-span-3", children: [
2149
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-ui-fg-subtle", children: texts.coupons.details.setupError }),
2150
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "text-ui-fg-error break-words font-medium", children: coupon.promotion_setup_error })
2151
+ ] }) : null
2152
+ ] });
2153
+ };
2154
+ const AffiliateCouponPanel = ({ affiliateId }) => {
2155
+ const [offset, setOffset] = react.useState(0);
2156
+ const [createMode, setCreateMode] = react.useState("generated");
2157
+ const [createCode, setCreateCode] = react.useState("");
2158
+ const [rotateCouponId, setRotateCouponId] = react.useState(null);
2159
+ const [rotateMode, setRotateMode] = react.useState("generated");
2160
+ const [rotateCode, setRotateCode] = react.useState("");
2161
+ const [revokeCoupon, setRevokeCoupon] = react.useState(
2162
+ null
2163
+ );
2164
+ const createIdempotency = react.useRef(null);
2165
+ const rotateIdempotency = react.useRef(null);
2166
+ const couponsQuery = useAffiliateCouponCodes(affiliateId, {
2167
+ limit: COUPON_PAGE_SIZE,
2168
+ offset
2169
+ });
2170
+ const createMutation = useCreateAffiliateCouponCode(affiliateId);
2171
+ const rotateMutation = useRotateAffiliateCouponCode(affiliateId);
2172
+ const revokeMutation = useRevokeAffiliateCouponCode(affiliateId);
2173
+ const reconcileMutation = useReconcileAffiliateCouponCode(affiliateId);
2174
+ const coupons = couponsQuery.data?.coupon_codes ?? [];
2175
+ const count = couponsQuery.data?.count ?? 0;
2176
+ const changeCreateMode = (mode) => {
2177
+ createIdempotency.current = null;
2178
+ setCreateMode(mode);
2179
+ };
2180
+ const changeCreateCode = (code) => {
2181
+ createIdempotency.current = null;
2182
+ setCreateCode(code);
2183
+ };
2184
+ const changeRotateMode = (mode) => {
2185
+ rotateIdempotency.current = null;
2186
+ setRotateMode(mode);
2187
+ };
2188
+ const changeRotateCode = (code) => {
2189
+ rotateIdempotency.current = null;
2190
+ setRotateCode(code);
2191
+ };
2192
+ const submitCreate = (event) => {
2193
+ event.preventDefault();
2194
+ const intent = createMode === "custom" ? `custom:${createCode.trim()}` : "generated";
2195
+ createIdempotency.current = resolveCouponCommandIdempotency(
2196
+ createIdempotency.current,
2197
+ intent
2198
+ );
2199
+ createMutation.mutate(
2200
+ {
2201
+ ...createMode === "custom" ? { code: createCode.trim() } : {},
2202
+ idempotency_key: createIdempotency.current.key
2203
+ },
2204
+ {
2205
+ onSuccess: () => {
2206
+ createIdempotency.current = null;
2207
+ setCreateCode("");
2208
+ ui.toast.success(texts.coupons.toasts.created);
2209
+ },
2210
+ onError: (error) => ui.toast.error(texts.coupons.toasts.createFailed, {
2211
+ description: getErrorMessage(error)
2212
+ })
2213
+ }
2214
+ );
2215
+ };
2216
+ const openRotation = (couponId) => {
2217
+ rotateIdempotency.current = null;
2218
+ setRotateCouponId(couponId);
2219
+ setRotateMode("generated");
2220
+ setRotateCode("");
2221
+ };
2222
+ const closeRotation = () => {
2223
+ rotateIdempotency.current = null;
2224
+ setRotateCouponId(null);
2225
+ setRotateCode("");
2226
+ };
2227
+ const submitRotation = (event) => {
2228
+ event.preventDefault();
2229
+ if (!rotateCouponId) {
2230
+ return;
2231
+ }
2232
+ const intent = `${rotateCouponId}:${rotateMode === "custom" ? `custom:${rotateCode.trim()}` : "generated"}`;
2233
+ rotateIdempotency.current = resolveCouponCommandIdempotency(
2234
+ rotateIdempotency.current,
2235
+ intent
2236
+ );
2237
+ rotateMutation.mutate(
2238
+ {
2239
+ couponId: rotateCouponId,
2240
+ payload: {
2241
+ ...rotateMode === "custom" ? { code: rotateCode.trim() } : {},
2242
+ idempotency_key: rotateIdempotency.current.key
2243
+ }
2244
+ },
2245
+ {
2246
+ onSuccess: () => {
2247
+ closeRotation();
2248
+ ui.toast.success(texts.coupons.toasts.rotated);
2249
+ },
2250
+ onError: (error) => ui.toast.error(texts.coupons.toasts.rotateFailed, {
2251
+ description: getErrorMessage(error)
2252
+ })
2253
+ }
2254
+ );
2255
+ };
2256
+ const revoke = () => {
2257
+ if (!revokeCoupon) {
2258
+ return;
2259
+ }
2260
+ revokeMutation.mutate(revokeCoupon.id, {
2261
+ onSuccess: () => {
2262
+ setRevokeCoupon(null);
2263
+ ui.toast.success(texts.coupons.toasts.revoked);
2264
+ },
2265
+ onError: (error) => ui.toast.error(texts.coupons.toasts.revokeFailed, {
2266
+ description: getErrorMessage(error)
2267
+ })
2268
+ });
2269
+ };
2270
+ const reconcile = (couponId) => {
2271
+ reconcileMutation.mutate(couponId, {
2272
+ onSuccess: (response) => ui.toast.success(texts.coupons.toasts.reconciled, {
2273
+ description: [
2274
+ texts.coupons.reconcileOutcome(
2275
+ texts.coupons.reconcileOutcomes[response.outcome]
2276
+ ),
2277
+ response.rotation_finalized ? texts.coupons.rotationFinalized : null
2278
+ ].filter(Boolean).join(" ")
2279
+ }),
2280
+ onError: (error) => ui.toast.error(texts.coupons.toasts.reconcileFailed, {
2281
+ description: getErrorMessage(error)
2282
+ })
2283
+ });
2284
+ };
2285
+ const renderCoupons = () => {
2286
+ if (couponsQuery.isLoading) {
2287
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.coupons.loading }) });
2288
+ }
2289
+ if (couponsQuery.isError) {
2290
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: texts.coupons.loadError(getErrorMessage(couponsQuery.error)) }) });
2291
+ }
2292
+ if (!coupons.length) {
2293
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.coupons.empty }) });
2294
+ }
2295
+ return coupons.map((coupon) => /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "grid gap-4 px-6 py-5", children: [
2296
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", children: [
2297
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
2298
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { weight: "plus", className: "break-all font-mono", children: coupon.code }),
2299
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: texts.coupons.statuses[coupon.status] })
2300
+ ] }),
2301
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
2302
+ /* @__PURE__ */ jsxRuntime.jsx(
2303
+ ui.Button,
2304
+ {
2305
+ type: "button",
2306
+ size: "small",
2307
+ variant: "secondary",
2308
+ disabled: rotateMutation.isPending || rotateCouponId === coupon.id || coupon.lifecycle_status === "revoked",
2309
+ onClick: () => openRotation(coupon.id),
2310
+ children: texts.coupons.rotate
2311
+ }
2312
+ ),
2313
+ /* @__PURE__ */ jsxRuntime.jsx(
2314
+ ui.Button,
2315
+ {
2316
+ type: "button",
2317
+ size: "small",
2318
+ variant: "secondary",
2319
+ disabled: reconcileMutation.isPending,
2320
+ onClick: () => reconcile(coupon.id),
2321
+ children: reconcileMutation.isPending ? texts.coupons.reconciling : texts.coupons.reconcile
2322
+ }
2323
+ ),
2324
+ /* @__PURE__ */ jsxRuntime.jsx(
2325
+ ui.Button,
2326
+ {
2327
+ type: "button",
2328
+ size: "small",
2329
+ variant: "danger",
2330
+ disabled: revokeMutation.isPending || coupon.lifecycle_status === "revoked",
2331
+ onClick: () => setRevokeCoupon(coupon),
2332
+ children: texts.coupons.revoke
2333
+ }
2334
+ )
2335
+ ] })
2336
+ ] }),
2337
+ /* @__PURE__ */ jsxRuntime.jsx(CouponDetails, { coupon }),
2338
+ rotateCouponId === coupon.id ? /* @__PURE__ */ jsxRuntime.jsxs(
2339
+ "form",
2340
+ {
2341
+ onSubmit: submitRotation,
2342
+ className: "border-ui-border-base grid gap-4 rounded-lg border p-4",
2343
+ children: [
2344
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2345
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: texts.coupons.rotateHeading(coupon.code) }),
2346
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: texts.coupons.rotateDescription })
2347
+ ] }),
2348
+ /* @__PURE__ */ jsxRuntime.jsx(
2349
+ CouponCodeChoice,
2350
+ {
2351
+ prefix: `rotate-${coupon.id}`,
2352
+ label: texts.coupons.rotateHeading(coupon.code),
2353
+ mode: rotateMode,
2354
+ code: rotateCode,
2355
+ onModeChange: changeRotateMode,
2356
+ onCodeChange: changeRotateCode
2357
+ }
2358
+ ),
2359
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2", children: [
2360
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "button", variant: "secondary", onClick: closeRotation, children: texts.coupons.cancel }),
2361
+ /* @__PURE__ */ jsxRuntime.jsx(
2362
+ ui.Button,
2363
+ {
2364
+ type: "submit",
2365
+ disabled: rotateMutation.isPending || rotateMode === "custom" && rotateCode.trim().length < 3,
2366
+ children: rotateMutation.isPending ? texts.coupons.rotating : texts.coupons.rotate
2367
+ }
2368
+ )
2369
+ ] })
2370
+ ]
2371
+ }
2372
+ ) : null
2373
+ ] }, coupon.id));
2374
+ };
2375
+ const showPagination = !couponsQuery.isLoading && !couponsQuery.isError && count > 0;
2376
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
2377
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
2378
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: texts.coupons.heading }),
2379
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.coupons.description })
2380
+ ] }),
2381
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submitCreate, className: "grid gap-4 px-6 py-5", children: [
2382
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: texts.coupons.createHeading }),
2383
+ /* @__PURE__ */ jsxRuntime.jsx(
2384
+ CouponCodeChoice,
2385
+ {
2386
+ prefix: "create-coupon",
2387
+ label: texts.coupons.createHeading,
2388
+ mode: createMode,
2389
+ code: createCode,
2390
+ onModeChange: changeCreateMode,
2391
+ onCodeChange: changeCreateCode
2392
+ }
2393
+ ),
2394
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
2395
+ ui.Button,
2396
+ {
2397
+ type: "submit",
2398
+ disabled: createMutation.isPending || createMode === "custom" && createCode.trim().length < 3,
2399
+ children: createMutation.isPending ? texts.coupons.creating : texts.coupons.create
2400
+ }
2401
+ ) })
2402
+ ] }),
2403
+ renderCoupons(),
2404
+ showPagination ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
2405
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-sm", children: texts.common.pageRange(offset + 1, offset + coupons.length, count) }),
2406
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
2407
+ /* @__PURE__ */ jsxRuntime.jsx(
2408
+ ui.Button,
2409
+ {
2410
+ type: "button",
2411
+ size: "small",
2412
+ variant: "secondary",
2413
+ disabled: offset === 0,
2414
+ onClick: () => setOffset((current) => Math.max(0, current - COUPON_PAGE_SIZE)),
2415
+ children: texts.common.back
2416
+ }
2417
+ ),
2418
+ /* @__PURE__ */ jsxRuntime.jsx(
2419
+ ui.Button,
2420
+ {
2421
+ type: "button",
2422
+ size: "small",
2423
+ variant: "secondary",
2424
+ disabled: offset + coupons.length >= count,
2425
+ onClick: () => setOffset((current) => current + COUPON_PAGE_SIZE),
2426
+ children: texts.common.next
2427
+ }
2428
+ )
2429
+ ] })
2430
+ ] }) : null,
2431
+ /* @__PURE__ */ jsxRuntime.jsx(
2432
+ ui.Prompt,
2433
+ {
2434
+ open: !!revokeCoupon,
2435
+ onOpenChange: (open) => {
2436
+ if (!open) {
2437
+ setRevokeCoupon(null);
2438
+ }
2439
+ },
2440
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Content, { children: [
2441
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Header, { children: [
2442
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Title, { children: texts.coupons.revokeHeading }),
2443
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Description, { children: revokeCoupon ? texts.coupons.revokeDescription(revokeCoupon.code) : "" })
2444
+ ] }),
2445
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Footer, { children: [
2446
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Cancel, { children: texts.coupons.cancel }),
2447
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Action, { onClick: revoke, children: texts.coupons.revoke })
2448
+ ] })
2449
+ ] })
2450
+ }
2451
+ )
2452
+ ] });
2453
+ };
2454
+ const SlugTable = ({
2455
+ affiliateId,
2456
+ slugs
2457
+ }) => {
2458
+ const mutation = useUpdateAffiliateSlug(affiliateId);
2459
+ const update = (payload) => mutation.mutate(payload, {
2460
+ onSuccess: () => ui.toast.success(texts.slugTable.toasts.updated),
2461
+ onError: (error) => ui.toast.error(texts.slugTable.toasts.updateFailed, {
2462
+ description: getErrorMessage(error)
2463
+ })
2464
+ });
2465
+ if (!slugs.length) {
2466
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.slugTable.empty });
2467
+ }
2468
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full text-left text-sm", children: [
2469
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "border-ui-border-base border-b", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "text-ui-fg-subtle", children: [
2470
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "py-3 pr-3 font-medium", children: texts.slugTable.columns.slug }),
2471
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "py-3 pr-3 font-medium", children: texts.slugTable.columns.state }),
2472
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "py-3 pr-3 font-medium", children: texts.slugTable.columns.primary }),
2473
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "py-3 text-right font-medium", children: texts.slugTable.columns.actions })
2474
+ ] }) }),
2475
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: slugs.map((slug) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "border-ui-border-base border-b", children: [
2476
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-3 pr-3 font-medium", children: slug.slug }),
2477
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-3 pr-3", children: slug.active ? texts.slugTable.active : texts.slugTable.inactive }),
2478
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-3 pr-3", children: slug.is_primary ? texts.slugTable.yes : texts.slugTable.no }),
2479
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-3 text-right", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2", children: [
2480
+ /* @__PURE__ */ jsxRuntime.jsx(
2481
+ ui.Button,
2482
+ {
2483
+ size: "small",
2484
+ variant: "secondary",
2485
+ disabled: mutation.isPending || slug.is_primary,
2486
+ onClick: () => update({ slugId: slug.id, is_primary: true }),
2487
+ children: texts.slugTable.makePrimary
2488
+ }
2489
+ ),
2490
+ /* @__PURE__ */ jsxRuntime.jsx(
2491
+ ui.Button,
2492
+ {
2493
+ size: "small",
2494
+ variant: "secondary",
2495
+ disabled: mutation.isPending || slug.is_primary,
2496
+ onClick: () => update({ slugId: slug.id, active: !slug.active }),
2497
+ children: slug.active ? texts.slugTable.deactivate : texts.slugTable.activate
2498
+ }
2499
+ )
2500
+ ] }) })
2501
+ ] }, slug.id)) })
2502
+ ] }) });
2503
+ };
2504
+ const resolveAssignmentDurationDays = ({
2505
+ presetDurationDays,
2506
+ persistedDurationDays
2507
+ }) => persistedDurationDays ?? presetDurationDays;
2508
+ const emptyFormState = {
2509
+ displayName: "",
2510
+ customerId: "",
2511
+ status: "active",
2512
+ commissionRate: 10,
2513
+ ownOrderDiscountRate: 0,
2514
+ customerDiscountOverride: "",
2515
+ referredByAffiliateId: "",
2516
+ referrerCommissionRate: 0,
2517
+ internalNotes: "",
2518
+ customerAssignmentMode: "lifetime",
2519
+ assignmentDurationDays: 30,
2520
+ persistedAssignmentDurationDays: null
2521
+ };
2522
+ const assignmentModeOptions = [
2523
+ ["lifetime", texts.form.assignmentModes.lifetime],
2524
+ ["time_limited", texts.form.assignmentModes.timeLimited],
2525
+ ["order_only", texts.form.assignmentModes.orderOnly]
2526
+ ];
2527
+ const AffiliateFormPage = (props) => {
2528
+ if (props.mode === "create") {
2529
+ return /* @__PURE__ */ jsxRuntime.jsx(CreateAffiliateForm, {});
2530
+ }
2531
+ return /* @__PURE__ */ jsxRuntime.jsx(EditAffiliateForm, { affiliateId: props.affiliateId });
2532
+ };
2533
+ const CreateAffiliateForm = () => {
2534
+ const navigate = reactRouterDom.useNavigate();
2535
+ const [form, setForm] = react.useState(emptyFormState);
2536
+ const [slug, setSlug] = react.useState("");
2537
+ const createMutation = useCreateAffiliate();
2538
+ const submit = (event) => {
2539
+ event.preventDefault();
2540
+ createMutation.mutate(
2541
+ {
2542
+ display_name: form.displayName,
2543
+ customer_id: form.customerId || null,
2544
+ status: form.status,
2545
+ commission_rate: form.commissionRate,
2546
+ own_order_discount_rate: form.ownOrderDiscountRate,
2547
+ customer_discount_rate_override: form.customerDiscountOverride === "" ? null : Number(form.customerDiscountOverride),
2548
+ slug: slug || null,
2549
+ referred_by_affiliate_id: form.referredByAffiliateId || null,
2550
+ referrer_commission_rate: form.referredByAffiliateId ? form.referrerCommissionRate : null,
2551
+ internal_notes: form.internalNotes || null,
2552
+ customer_assignment_mode: form.customerAssignmentMode,
2553
+ assignment_duration_days: form.customerAssignmentMode === "time_limited" ? resolveAssignmentDurationDays({
2554
+ presetDurationDays: form.assignmentDurationDays,
2555
+ persistedDurationDays: form.persistedAssignmentDurationDays
2556
+ }) : null
2557
+ },
2558
+ {
2559
+ onSuccess: () => {
2560
+ ui.toast.success(texts.form.toasts.created);
2561
+ navigate("/affiliates");
2562
+ },
2563
+ onError: (error) => {
2564
+ ui.toast.error(texts.form.toasts.createFailed, {
2565
+ description: getErrorMessage(error)
2566
+ });
2567
+ }
2568
+ }
2569
+ );
2570
+ };
2571
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
2572
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
2573
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: texts.form.createHeading }),
2574
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.form.createDescription })
2575
+ ] }),
2576
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submit, className: "grid gap-5 px-6 py-5", children: [
2577
+ /* @__PURE__ */ jsxRuntime.jsx(AffiliateFormFields, { form, onChange: setForm, excludeAffiliateId: void 0, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
2578
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: texts.form.initialSlugLabel }),
2579
+ /* @__PURE__ */ jsxRuntime.jsx(
2580
+ ui.Input,
2581
+ {
2582
+ value: slug,
2583
+ onChange: (event) => setSlug(event.target.value),
2584
+ placeholder: texts.form.initialSlugPlaceholder
2585
+ }
2586
+ )
2587
+ ] }) }),
2588
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", disabled: createMutation.isPending, children: createMutation.isPending ? texts.form.creating : texts.form.create }) })
2589
+ ] })
2590
+ ] });
2591
+ };
2592
+ const EditAffiliateForm = ({ affiliateId }) => {
2593
+ const affiliateQuery = useAffiliate(affiliateId);
2594
+ const affiliate2 = affiliateQuery.data?.affiliate;
2595
+ const [form, setForm] = react.useState(emptyFormState);
2596
+ const [newSlug, setNewSlug] = react.useState("");
2597
+ const initializedForId = react.useRef(void 0);
2598
+ react.useEffect(() => {
2599
+ if (!affiliate2 || initializedForId.current === affiliate2.id) {
2600
+ return;
2601
+ }
2602
+ initializedForId.current = affiliate2.id;
2603
+ setForm({
2604
+ displayName: affiliate2.display_name,
2605
+ customerId: affiliate2.customer_id ?? "",
2606
+ status: affiliate2.status,
2607
+ commissionRate: affiliate2.commission_rate,
2608
+ ownOrderDiscountRate: affiliate2.own_order_discount_rate,
2609
+ customerDiscountOverride: affiliate2.customer_discount_rate_override == null ? "" : String(affiliate2.customer_discount_rate_override),
2610
+ referredByAffiliateId: affiliate2.referred_by_affiliate_id ?? "",
2611
+ referrerCommissionRate: affiliate2.referrer_commission_rate ?? 0,
2612
+ internalNotes: affiliate2.internal_notes ?? "",
2613
+ customerAssignmentMode: affiliate2.customer_assignment_mode,
2614
+ // The API accepts any positive duration, while the operator UI presents
2615
+ // three approved presets. Preserve an unchanged arbitrary value so an
2616
+ // unrelated edit never shortens the configured assignment period.
2617
+ assignmentDurationDays: affiliate2.assignment_duration_days === 30 || affiliate2.assignment_duration_days === 90 || affiliate2.assignment_duration_days === 180 ? affiliate2.assignment_duration_days : 30,
2618
+ persistedAssignmentDurationDays: affiliate2.assignment_duration_days === 30 || affiliate2.assignment_duration_days === 90 || affiliate2.assignment_duration_days === 180 ? null : affiliate2.assignment_duration_days
2619
+ });
2620
+ }, [affiliate2]);
2621
+ const updateMutation = useUpdateAffiliate(affiliateId);
2622
+ const inactiveMutation = useSetAffiliateInactive(affiliateId);
2623
+ const createSlugMutation = useCreateAffiliateSlug(affiliateId);
2624
+ const submit = (event) => {
2625
+ event.preventDefault();
2626
+ updateMutation.mutate(
2627
+ {
2628
+ display_name: form.displayName,
2629
+ customer_id: form.customerId || null,
2630
+ status: form.status,
2631
+ commission_rate: form.commissionRate,
2632
+ own_order_discount_rate: form.ownOrderDiscountRate,
2633
+ customer_discount_rate_override: form.customerDiscountOverride === "" ? null : Number(form.customerDiscountOverride),
2634
+ referred_by_affiliate_id: form.referredByAffiliateId || null,
2635
+ referrer_commission_rate: form.referredByAffiliateId ? form.referrerCommissionRate : null,
2636
+ internal_notes: form.internalNotes || null,
2637
+ customer_assignment_mode: form.customerAssignmentMode,
2638
+ assignment_duration_days: form.customerAssignmentMode === "time_limited" ? resolveAssignmentDurationDays({
2639
+ presetDurationDays: form.assignmentDurationDays,
2640
+ persistedDurationDays: form.persistedAssignmentDurationDays
2641
+ }) : null
2642
+ },
2643
+ {
2644
+ onSuccess: () => ui.toast.success(texts.form.toasts.updated),
2645
+ onError: (error) => ui.toast.error(texts.form.toasts.updateFailed, {
2646
+ description: getErrorMessage(error)
2647
+ })
2648
+ }
2649
+ );
2650
+ };
2651
+ const submitSlug = (event) => {
2652
+ event.preventDefault();
2653
+ createSlugMutation.mutate(
2654
+ { slug: newSlug, is_primary: !affiliate2?.slugs?.length },
2655
+ {
2656
+ onSuccess: () => {
2657
+ ui.toast.success(texts.form.toasts.slugCreated);
2658
+ setNewSlug("");
2659
+ },
2660
+ onError: (error) => ui.toast.error(texts.form.toasts.slugCreateFailed, {
2661
+ description: getErrorMessage(error)
2662
+ })
2663
+ }
2664
+ );
2665
+ };
2666
+ if (!affiliateId) {
2667
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "grid gap-3", children: [
2668
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: texts.form.notFoundHeading }),
2669
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.form.notFoundBody }),
2670
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: "/affiliates", children: texts.form.backToList }) }) })
2671
+ ] });
2672
+ }
2673
+ if (affiliateQuery.isError) {
2674
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "grid gap-3", children: [
2675
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: texts.form.loadErrorHeading }),
2676
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: getErrorMessage(affiliateQuery.error) }),
2677
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: "/affiliates", children: texts.form.backToList }) }) })
2678
+ ] });
2679
+ }
2680
+ if (affiliateQuery.isLoading || !affiliate2) {
2681
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.form.loading }) });
2682
+ }
2683
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6", children: [
2684
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
2685
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-4 px-6 py-4", children: [
2686
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2687
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: affiliate2.display_name }),
2688
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.form.createdAt(formatDateTime(affiliate2.created_at)) })
2689
+ ] }),
2690
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
2691
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: "#affiliate-distribution", children: "Zum Kommissionsvertrieb" }) }),
2692
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: "/affiliates", children: texts.form.backToList }) }),
2693
+ /* @__PURE__ */ jsxRuntime.jsx(
2694
+ ui.Button,
2695
+ {
2696
+ variant: "danger",
2697
+ disabled: inactiveMutation.isPending || affiliate2.status === "inactive",
2698
+ onClick: () => inactiveMutation.mutate(void 0, {
2699
+ onSuccess: () => ui.toast.success(texts.form.toasts.setInactive),
2700
+ onError: (error) => ui.toast.error(texts.form.toasts.setInactiveFailed, {
2701
+ description: getErrorMessage(error)
2702
+ })
2703
+ }),
2704
+ children: texts.form.setInactive
2705
+ }
2706
+ )
2707
+ ] })
2708
+ ] }),
2709
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submit, className: "grid gap-5 px-6 py-5", children: [
2710
+ /* @__PURE__ */ jsxRuntime.jsx(AffiliateFormFields, { form, onChange: setForm, excludeAffiliateId: affiliateId }),
2711
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", disabled: updateMutation.isPending, children: updateMutation.isPending ? texts.form.saving : texts.form.save }) })
2712
+ ] })
2713
+ ] }),
2714
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
2715
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
2716
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: texts.form.slugs.heading }),
2717
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.form.slugs.description })
2718
+ ] }),
2719
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-5 px-6 py-5", children: [
2720
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submitSlug, className: "grid gap-3 md:grid-cols-[minmax(0,1fr)_auto]", children: [
2721
+ /* @__PURE__ */ jsxRuntime.jsx(
2722
+ ui.Input,
2723
+ {
2724
+ value: newSlug,
2725
+ onChange: (event) => setNewSlug(event.target.value),
2726
+ placeholder: texts.form.slugs.placeholder
2727
+ }
2728
+ ),
2729
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", disabled: createSlugMutation.isPending || !newSlug.trim(), children: texts.form.slugs.add })
2730
+ ] }),
2731
+ /* @__PURE__ */ jsxRuntime.jsx(SlugTable, { affiliateId: affiliate2.id, slugs: affiliate2.slugs ?? [] })
2732
+ ] })
2733
+ ] }),
2734
+ /* @__PURE__ */ jsxRuntime.jsx(AffiliateCouponPanel, { affiliateId: affiliate2.id }),
2735
+ /* @__PURE__ */ jsxRuntime.jsx(AffiliateDistributionPanel, { affiliateId: affiliate2.id }),
2736
+ /* @__PURE__ */ jsxRuntime.jsx(ConsignmentReminderPanel, { affiliateId: affiliate2.id })
2737
+ ] });
2738
+ };
2739
+ const AffiliateFormFields = ({
2740
+ form,
2741
+ onChange,
2742
+ excludeAffiliateId,
2743
+ children
2744
+ }) => {
2745
+ const set = (key, value) => onChange((current) => ({ ...current, [key]: value }));
2746
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2747
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
2748
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
2749
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: texts.form.fields.displayName }),
2750
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { required: true, value: form.displayName, onChange: (event) => set("displayName", event.target.value) })
2751
+ ] }),
2752
+ /* @__PURE__ */ jsxRuntime.jsx(CustomerReferenceField, { value: form.customerId, onChange: (value) => set("customerId", value) })
2753
+ ] }),
2754
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [
2755
+ /* @__PURE__ */ jsxRuntime.jsx(AffiliateStatusSelect, { value: form.status, onChange: (value) => set("status", value) }),
2756
+ /* @__PURE__ */ jsxRuntime.jsx(
2757
+ PercentRateInput,
2758
+ {
2759
+ label: texts.form.fields.commission,
2760
+ value: form.commissionRate,
2761
+ onChange: (value) => set("commissionRate", value)
2762
+ }
2763
+ ),
2764
+ /* @__PURE__ */ jsxRuntime.jsx(
2765
+ PercentRateInput,
2766
+ {
2767
+ label: texts.form.fields.ownDiscount,
2768
+ value: form.ownOrderDiscountRate,
2769
+ onChange: (value) => set("ownOrderDiscountRate", value)
2770
+ }
2771
+ )
2772
+ ] }),
2773
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
2774
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "affiliate-customer-discount", children: "Kundenrabatt-Abweichung % (leer = global)" }),
2775
+ /* @__PURE__ */ jsxRuntime.jsx(
2776
+ ui.Input,
2777
+ {
2778
+ id: "affiliate-customer-discount",
2779
+ type: "number",
2780
+ min: 0,
2781
+ max: 100,
2782
+ step: "0.01",
2783
+ value: form.customerDiscountOverride,
2784
+ onChange: (event) => set("customerDiscountOverride", event.target.value)
2785
+ }
2786
+ )
2787
+ ] }),
2788
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-3", children: [
2789
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2790
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: texts.form.fields.customerAssignmentMode }),
2791
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: texts.form.fields.customerAssignmentModeHint })
2792
+ ] }),
2793
+ /* @__PURE__ */ jsxRuntime.jsx(
2794
+ ui.RadioGroup,
2795
+ {
2796
+ "aria-label": texts.form.fields.customerAssignmentMode,
2797
+ value: form.customerAssignmentMode,
2798
+ onValueChange: (value) => set(
2799
+ "customerAssignmentMode",
2800
+ value
2801
+ ),
2802
+ className: "grid gap-3",
2803
+ children: assignmentModeOptions.map(([value, option]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
2804
+ /* @__PURE__ */ jsxRuntime.jsx(ui.RadioGroup.Item, { id: `assignment-mode-${value}`, value }),
2805
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-0.5", children: [
2806
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: `assignment-mode-${value}`, children: option.label }),
2807
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: option.description })
2808
+ ] })
2809
+ ] }, value))
2810
+ }
2811
+ ),
2812
+ form.customerAssignmentMode === "time_limited" ? /* @__PURE__ */ jsxRuntime.jsx(
2813
+ AssignmentDurationSelect,
2814
+ {
2815
+ presetDurationDays: form.assignmentDurationDays,
2816
+ persistedDurationDays: form.persistedAssignmentDurationDays,
2817
+ onPresetDurationChange: (assignmentDurationDays) => onChange((current) => ({
2818
+ ...current,
2819
+ assignmentDurationDays,
2820
+ persistedAssignmentDurationDays: null
2821
+ }))
2822
+ }
2823
+ ) : null
2824
+ ] }),
2825
+ children,
2826
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
2827
+ /* @__PURE__ */ jsxRuntime.jsx(
2828
+ AffiliateReferenceField,
2829
+ {
2830
+ label: texts.form.fields.referredBy,
2831
+ value: form.referredByAffiliateId,
2832
+ onChange: (value) => set("referredByAffiliateId", value),
2833
+ excludeAffiliateId
2834
+ }
2835
+ ),
2836
+ /* @__PURE__ */ jsxRuntime.jsx(
2837
+ PercentRateInput,
2838
+ {
2839
+ label: texts.form.fields.referrerCommission,
2840
+ value: form.referrerCommissionRate,
2841
+ onChange: (value) => set("referrerCommissionRate", value)
2842
+ }
2843
+ )
2844
+ ] }),
2845
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
2846
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: texts.form.fields.internalNotes }),
2847
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Textarea, { value: form.internalNotes, onChange: (event) => set("internalNotes", event.target.value) })
2848
+ ] })
2849
+ ] });
2850
+ };
2851
+ const AffiliateEditRoute = () => {
2852
+ const { id } = reactRouterDom.useParams();
2853
+ return /* @__PURE__ */ jsxRuntime.jsx(AffiliateFormPage, { mode: "edit", affiliateId: id });
2854
+ };
2855
+ const yearStart = () => `${(/* @__PURE__ */ new Date()).getFullYear()}-01-01`;
2856
+ const formatCurrencyTotals = (totals) => totals.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-1", children: totals.map((total) => /* @__PURE__ */ jsxRuntime.jsx("span", { children: total.currency_code ? formatAmount(total.amount, total.currency_code) : `${total.amount} (Währung unbekannt)` }, total.currency_code ?? "unknown-currency")) }) : "–";
2857
+ function AffiliateDistributionReportPage() {
2858
+ const [draftFrom, setDraftFrom] = react.useState(yearStart);
2859
+ const [draftTo, setDraftTo] = react.useState(getTodayInputDate);
2860
+ const [range, setRange] = react.useState({ from: draftFrom, to: draftTo });
2861
+ const report = reactQuery.useQuery({
2862
+ queryKey: ["affiliate", "distribution-report", range],
2863
+ queryFn: () => getAffiliateDistributionReports(range)
2864
+ });
2865
+ const submit = (event) => {
2866
+ event.preventDefault();
2867
+ setRange({ from: draftFrom, to: draftTo });
2868
+ };
2869
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "grid gap-5", children: [
2870
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2871
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Affiliate-Vertriebsreport" }),
2872
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Stationäre Kommissionsverkäufe und vermittelte Webshopumsätze getrennt auswerten." })
2873
+ ] }),
2874
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submit, className: "flex flex-wrap items-end gap-3", children: [
2875
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
2876
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "distribution-report-from", children: "Von" }),
2877
+ /* @__PURE__ */ jsxRuntime.jsx(
2878
+ ui.Input,
2879
+ {
2880
+ id: "distribution-report-from",
2881
+ type: "date",
2882
+ value: draftFrom,
2883
+ onChange: (event) => setDraftFrom(event.target.value)
2884
+ }
2885
+ )
2886
+ ] }),
2887
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
2888
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "distribution-report-to", children: "Bis" }),
2889
+ /* @__PURE__ */ jsxRuntime.jsx(
2890
+ ui.Input,
2891
+ {
2892
+ id: "distribution-report-to",
2893
+ type: "date",
2894
+ value: draftTo,
2895
+ onChange: (event) => setDraftTo(event.target.value)
2896
+ }
2897
+ )
2898
+ ] }),
2899
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", children: "Aktualisieren" })
2900
+ ] }),
2901
+ report.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Report wird geladen …" }) : report.isError ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: getErrorMessage(report.error) }) : !report.data?.affiliate_distribution_reports.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Keine Affiliates gefunden." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full text-left text-sm", children: [
2902
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "border-b", children: [
2903
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Affiliate" }),
2904
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Stationär" }),
2905
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Webshop" }),
2906
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Web-Provision" }),
2907
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Bestand" }),
2908
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Letzte Meldung" }),
2909
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Letzter Verkauf" }),
2910
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Inaktiv" }),
2911
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Offen / überfällig" })
2912
+ ] }) }),
2913
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: report.data.affiliate_distribution_reports.map((row) => {
2914
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2915
+ "tr",
2916
+ {
2917
+ className: row.inactive_months >= 4 ? "bg-ui-bg-error border-b" : "border-b",
2918
+ children: [
2919
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: row.display_name }),
2920
+ /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "p-2", children: [
2921
+ formatCurrencyTotals(
2922
+ row.currency_totals.map((total) => ({
2923
+ currency_code: total.currency_code,
2924
+ amount: total.consignment_sales_amount
2925
+ }))
2926
+ ),
2927
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: [
2928
+ row.consignment_sales_quantity,
2929
+ " Stück"
2930
+ ] })
2931
+ ] }),
2932
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: formatCurrencyTotals(
2933
+ row.currency_totals.map((total) => ({
2934
+ currency_code: total.currency_code,
2935
+ amount: total.affiliate_web_sales_amount
2936
+ }))
2937
+ ) }),
2938
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: formatCurrencyTotals(
2939
+ row.currency_totals.map((total) => ({
2940
+ currency_code: total.currency_code,
2941
+ amount: total.affiliate_web_commission_amount
2942
+ }))
2943
+ ) }),
2944
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: row.current_stock_quantity }),
2945
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: formatDateTime(row.last_reported_at) }),
2946
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: formatDateTime(row.last_sale_at) }),
2947
+ /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "p-2", children: [
2948
+ row.inactive_months,
2949
+ " Monate"
2950
+ ] }),
2951
+ /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "p-2", children: [
2952
+ row.open_reports,
2953
+ " / ",
2954
+ row.overdue_reports
2955
+ ] })
2956
+ ]
2957
+ },
2958
+ row.affiliate_id
2959
+ );
2960
+ }) })
2961
+ ] }) })
2962
+ ] });
2963
+ }
2964
+ const AffiliateDistributionReportRoute = () => /* @__PURE__ */ jsxRuntime.jsx(AffiliateDistributionReportPage, {});
2965
+ const config$4 = adminSdk.defineRouteConfig({
2966
+ label: "Vertriebsreport",
2967
+ rank: 85
2968
+ });
2969
+ function ConsignmentReportsPage() {
2970
+ const reports = reactQuery.useQuery({
2971
+ queryKey: ["affiliate", "consignment-reports"],
2972
+ queryFn: () => listAdminConsignmentReports({ limit: 100, offset: 0 })
2973
+ });
2974
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "grid gap-5", children: [
2975
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2976
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Kommissions-Monatsmeldungen" }),
2977
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Eingereichte Inventuren prüfen und abrechnen." })
2978
+ ] }),
2979
+ reports.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Monatsmeldungen werden geladen …" }) : reports.isError ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: getErrorMessage(reports.error) }) : !reports.data?.reports.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Keine Monatsmeldungen vorhanden." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full text-left text-sm", children: [
2980
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "border-b", children: [
2981
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Affiliate" }),
2982
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Zeitraum" }),
2983
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Status" }),
2984
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Abweichungen" }),
2985
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Draft / Order" }),
2986
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2" })
2987
+ ] }) }),
2988
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: reports.data.reports.map((report) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "border-b", children: [
2989
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: report.affiliate_id }),
2990
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: formatDate(report.period_start) }),
2991
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: report.status }),
2992
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: report.lines.filter(
2993
+ (line) => line.requires_admin_clarification || line.expected_quantity !== line.counted_quantity
2994
+ ).length }),
2995
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: report.order_id ?? report.draft_order_id ?? "–" }),
2996
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2 text-right", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
2997
+ reactRouterDom.Link,
2998
+ {
2999
+ to: `/affiliates/consignment-reports/${report.id}`,
3000
+ children: "Prüfen"
3001
+ }
3002
+ ) }) })
3003
+ ] }, report.id)) })
3004
+ ] }) })
3005
+ ] });
3006
+ }
3007
+ const ConsignmentReportsRoute = () => /* @__PURE__ */ jsxRuntime.jsx(ConsignmentReportsPage, {});
3008
+ const config$3 = adminSdk.defineRouteConfig({
3009
+ label: "Kommissionsmeldungen",
3010
+ rank: 83
3011
+ });
3012
+ const NewAffiliateRoute = () => /* @__PURE__ */ jsxRuntime.jsx(AffiliateFormPage, { mode: "create" });
3013
+ const config$2 = adminSdk.defineRouteConfig({
3014
+ label: texts.routes.newAffiliate
3015
+ });
3016
+ const useAffiliateReportOverview = (params) => reactQuery.useQuery({
3017
+ queryKey: affiliateKeys.reportOverview(params),
3018
+ queryFn: () => getAffiliateReportOverview(params)
3019
+ });
3020
+ const useAffiliateReportDetail = (affiliateId, params) => reactQuery.useQuery({
3021
+ queryKey: affiliateKeys.reportDetail(affiliateId, params),
3022
+ enabled: !!affiliateId,
3023
+ queryFn: () => getAffiliateReportDetail(affiliateId ?? "", params)
3024
+ });
3025
+ const AffiliateReportsPage = () => {
3026
+ const [from, setFrom] = react.useState(getMonthStartInputDate);
3027
+ const [to, setTo] = react.useState(getTodayInputDate);
3028
+ const reportQuery = useAffiliateReportOverview({ from, to });
3029
+ const affiliates = reportQuery.data?.report.affiliates ?? [];
3030
+ const renderBody = () => {
3031
+ if (reportQuery.isLoading) {
3032
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.reports.loading }) });
3033
+ }
3034
+ if (reportQuery.isError) {
3035
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.reports.loadError(getErrorMessage(reportQuery.error)) }) });
3036
+ }
3037
+ if (!affiliates.length) {
3038
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.reports.empty }) });
3039
+ }
3040
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full text-left text-sm", children: [
3041
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "border-ui-border-base border-b", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "text-ui-fg-subtle", children: [
3042
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: texts.reports.columns.affiliate }),
3043
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: texts.reports.columns.status }),
3044
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right font-medium", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: texts.reports.orderValueTooltip, children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "decoration-ui-fg-subtle underline decoration-dotted underline-offset-2", children: texts.reports.columns.orderValue }) }) }),
3045
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right font-medium", children: texts.reports.columns.commission }),
3046
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right font-medium", children: texts.reports.columns.orderCount }),
3047
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right font-medium", children: texts.reports.columns.action })
3048
+ ] }) }),
3049
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: affiliates.map((affiliate2) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "border-ui-border-base border-b", children: [
3050
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 font-medium", children: affiliate2.display_name }),
3051
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 capitalize", children: formatStatus(affiliate2.status) }),
3052
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 text-right tabular-nums", children: formatAmount(affiliate2.order_value_amount, affiliate2.currency_code) }),
3053
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 text-right font-medium tabular-nums", children: formatAmount(affiliate2.commission_amount, affiliate2.currency_code) }),
3054
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 text-right tabular-nums", children: affiliate2.order_count }),
3055
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 text-right", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: `/affiliates/reports/${affiliate2.affiliate_id}?from=${from}&to=${to}`, children: texts.reports.details }) }) })
3056
+ ] }, `${affiliate2.affiliate_id}-${affiliate2.currency_code}`)) })
3057
+ ] }) });
3058
+ };
3059
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
3060
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
3061
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: texts.reports.heading }),
3062
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.reports.description })
3063
+ ] }),
3064
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 px-6 py-4 md:grid-cols-[180px_180px_auto]", children: [
3065
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
3066
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: texts.reports.from }),
3067
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { type: "date", value: from, onChange: (event) => setFrom(event.target.value) })
3068
+ ] }),
3069
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
3070
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: texts.reports.to }),
3071
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { type: "date", value: to, onChange: (event) => setTo(event.target.value) })
3072
+ ] })
3073
+ ] }),
3074
+ renderBody()
3075
+ ] });
3076
+ };
3077
+ const AffiliateReportsRoute = () => /* @__PURE__ */ jsxRuntime.jsx(AffiliateReportsPage, {});
3078
+ const config$1 = adminSdk.defineRouteConfig({
3079
+ label: texts.routes.reports,
3080
+ rank: 82
3081
+ });
3082
+ function AffiliateProgramSettingsPage() {
3083
+ const queryClient = reactQuery.useQueryClient();
3084
+ const settings = reactQuery.useQuery({
3085
+ queryKey: ["affiliate", "program-settings"],
3086
+ queryFn: getAffiliateProgramSettings
3087
+ });
3088
+ const [commissionBase, setCommissionBase] = react.useState("after_discounts");
3089
+ const [referrerBase, setReferrerBase] = react.useState("order_base");
3090
+ const [stacking, setStacking] = react.useState(
3091
+ "allow_stacking"
3092
+ );
3093
+ const [discount, setDiscount] = react.useState(5);
3094
+ const [referralOverwritePolicy, setReferralOverwritePolicy] = react.useState("first_touch");
3095
+ const [referralWindowDays, setReferralWindowDays] = react.useState(30);
3096
+ react.useEffect(() => {
3097
+ const value = settings.data?.settings;
3098
+ if (!value) return;
3099
+ setCommissionBase(value.commission_base);
3100
+ setReferrerBase(value.referrer_base);
3101
+ setStacking(value.promotion_stacking);
3102
+ setDiscount(value.default_customer_discount_rate);
3103
+ setReferralOverwritePolicy(value.referral_overwrite_policy);
3104
+ setReferralWindowDays(value.referral_window_days);
3105
+ }, [settings.data]);
3106
+ const update = reactQuery.useMutation({
3107
+ mutationFn: () => {
3108
+ const current = settings.data?.settings;
3109
+ if (!current) throw new Error("Programmeinstellungen fehlen.");
3110
+ return updateAffiliateProgramSettings({
3111
+ expected_version: current.policy_version,
3112
+ commission_base: commissionBase,
3113
+ referrer_base: referrerBase,
3114
+ promotion_stacking: stacking,
3115
+ default_customer_discount_rate: discount,
3116
+ referral_overwrite_policy: referralOverwritePolicy,
3117
+ referral_window_days: referralWindowDays
3118
+ });
3119
+ },
3120
+ onSuccess: async (data) => {
3121
+ queryClient.setQueryData(["affiliate", "program-settings"], data);
3122
+ ui.toast.success("Programmeinstellungen wurden gespeichert.");
3123
+ },
3124
+ onError: (error) => ui.toast.error("Programmeinstellungen konnten nicht gespeichert werden.", {
3125
+ description: getErrorMessage(error)
3126
+ })
3127
+ });
3128
+ const submit = (event) => {
3129
+ event.preventDefault();
3130
+ update.mutate();
3131
+ };
3132
+ if (settings.isLoading) {
3133
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Einstellungen werden geladen …" }) });
3134
+ }
3135
+ if (settings.isError || !settings.data) {
3136
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: getErrorMessage(settings.error) }) });
3137
+ }
3138
+ const previewOrderBase = commissionBase === "before_discounts" ? 100 : 90;
3139
+ const previewDirect = previewOrderBase * 0.1;
3140
+ const previewReferrerBase = referrerBase === "order_base" ? previewOrderBase : previewOrderBase - previewDirect;
3141
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "grid gap-5", children: [
3142
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3143
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Affiliate-Programmeinstellungen" }),
3144
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
3145
+ "Globale Berechnungs- und Rabattstrategie · Version",
3146
+ " ",
3147
+ settings.data.settings.policy_version
3148
+ ] })
3149
+ ] }),
3150
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submit, className: "grid gap-5", children: [
3151
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
3152
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
3153
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "commission-base", children: "Provisionsbasis" }),
3154
+ /* @__PURE__ */ jsxRuntime.jsxs(
3155
+ "select",
3156
+ {
3157
+ id: "commission-base",
3158
+ className: "bg-ui-bg-field border-ui-border-base rounded-md border px-3 py-2 text-sm",
3159
+ value: commissionBase,
3160
+ onChange: (event) => setCommissionBase(
3161
+ event.target.value
3162
+ ),
3163
+ children: [
3164
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "before_discounts", children: "Vor Rabatten" }),
3165
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "after_discounts", children: "Nach Rabatten" })
3166
+ ]
3167
+ }
3168
+ )
3169
+ ] }),
3170
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
3171
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "referral-overwrite-policy", children: "Referral-Regel" }),
3172
+ /* @__PURE__ */ jsxRuntime.jsxs(
3173
+ ui.Select,
3174
+ {
3175
+ value: referralOverwritePolicy,
3176
+ onValueChange: (value) => setReferralOverwritePolicy(value),
3177
+ children: [
3178
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { id: "referral-overwrite-policy", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, {}) }),
3179
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
3180
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "first_touch", children: "First Touch" }),
3181
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "last_touch", children: "Last Touch" })
3182
+ ] })
3183
+ ]
3184
+ }
3185
+ ),
3186
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: referralOverwritePolicy === "first_touch" ? "First Touch: Beispiel A-Link, danach B-Coupon; A bleibt." : "Last Touch: Beispiel A-Link, danach B-Coupon; B ersetzt A." })
3187
+ ] }),
3188
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
3189
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "referral-window-days", children: "Referral-Frist in Tagen" }),
3190
+ /* @__PURE__ */ jsxRuntime.jsx(
3191
+ ui.Input,
3192
+ {
3193
+ id: "referral-window-days",
3194
+ type: "number",
3195
+ min: 1,
3196
+ step: 1,
3197
+ required: true,
3198
+ value: referralWindowDays,
3199
+ onChange: (event) => setReferralWindowDays(Number(event.target.value))
3200
+ }
3201
+ ),
3202
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: "Referral-Signale können innerhalb dieser positiven Frist ersetzt werden." })
3203
+ ] }),
3204
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
3205
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "referrer-base", children: "Vermittlerbasis" }),
3206
+ /* @__PURE__ */ jsxRuntime.jsxs(
3207
+ "select",
3208
+ {
3209
+ id: "referrer-base",
3210
+ className: "bg-ui-bg-field border-ui-border-base rounded-md border px-3 py-2 text-sm",
3211
+ value: referrerBase,
3212
+ onChange: (event) => setReferrerBase(event.target.value),
3213
+ children: [
3214
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "order_base", children: "Order-Basis" }),
3215
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "after_direct_commission", children: "Nach direkter Provision" })
3216
+ ]
3217
+ }
3218
+ )
3219
+ ] }),
3220
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
3221
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "promotion-stacking", children: "Promotion-Kombination" }),
3222
+ /* @__PURE__ */ jsxRuntime.jsxs(
3223
+ "select",
3224
+ {
3225
+ id: "promotion-stacking",
3226
+ className: "bg-ui-bg-field border-ui-border-base rounded-md border px-3 py-2 text-sm",
3227
+ value: stacking,
3228
+ onChange: (event) => setStacking(event.target.value),
3229
+ children: [
3230
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "allow_stacking", children: "Kombination erlauben" }),
3231
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "exclusive", children: "Exklusiv" })
3232
+ ]
3233
+ }
3234
+ )
3235
+ ] }),
3236
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
3237
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "default-customer-discount", children: "Standard-Kundenrabatt %" }),
3238
+ /* @__PURE__ */ jsxRuntime.jsx(
3239
+ ui.Input,
3240
+ {
3241
+ id: "default-customer-discount",
3242
+ type: "number",
3243
+ min: 0,
3244
+ max: 100,
3245
+ step: "0.01",
3246
+ value: discount,
3247
+ onChange: (event) => setDiscount(Number(event.target.value))
3248
+ }
3249
+ )
3250
+ ] })
3251
+ ] }),
3252
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
3253
+ "Vorschau bei 100 € Warenwert, 10 € Rabatt und 10 % Provision: Affiliate-Basis ",
3254
+ previewOrderBase.toFixed(2),
3255
+ " €, direkte Provision",
3256
+ " ",
3257
+ previewDirect.toFixed(2),
3258
+ " €, Vermittlerbasis",
3259
+ " ",
3260
+ previewReferrerBase.toFixed(2),
3261
+ " €."
3262
+ ] }),
3263
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", disabled: update.isPending, children: update.isPending ? "Wird gespeichert …" : "Speichern" }) })
3264
+ ] })
3265
+ ] });
3266
+ }
3267
+ const AffiliateProgramSettingsRoute = () => /* @__PURE__ */ jsxRuntime.jsx(AffiliateProgramSettingsPage, {});
3268
+ const config = adminSdk.defineRouteConfig({
3269
+ label: "Affiliate-Einstellungen",
3270
+ rank: 84
3271
+ });
3272
+ function ConsignmentReportReviewPage({
3273
+ reportId
3274
+ }) {
3275
+ const queryClient = reactQuery.useQueryClient();
3276
+ const [lines, setLines] = react.useState({});
3277
+ const [adminNotes, setAdminNotes] = react.useState("");
3278
+ const queryKey = ["affiliate", "consignment-report", reportId];
3279
+ const report = reactQuery.useQuery({
3280
+ queryKey,
3281
+ enabled: Boolean(reportId),
3282
+ queryFn: () => retrieveAdminConsignmentReport(reportId ?? "")
3283
+ });
3284
+ react.useEffect(() => {
3285
+ const current2 = report.data?.report;
3286
+ if (!current2) return;
3287
+ setLines(
3288
+ Object.fromEntries(
3289
+ current2.lines.map((line) => [
3290
+ line.id,
3291
+ {
3292
+ quantity: line.confirmed_sale_quantity,
3293
+ reason: line.non_sale_reason ?? ""
3294
+ }
3295
+ ])
3296
+ )
3297
+ );
3298
+ }, [report.data]);
3299
+ const approve = reactQuery.useMutation({
3300
+ mutationFn: (current2) => approveAdminConsignmentReport(current2.id, {
3301
+ expected_version: current2.version,
3302
+ confirmed_lines: current2.lines.map((line) => ({
3303
+ report_line_id: line.id,
3304
+ confirmed_sale_quantity: lines[line.id]?.quantity ?? line.confirmed_sale_quantity,
3305
+ non_sale_reason: lines[line.id]?.reason || null
3306
+ }))
3307
+ }),
3308
+ onSuccess: async () => {
3309
+ ui.toast.success("Monatsmeldung wurde freigegeben.");
3310
+ await queryClient.invalidateQueries({ queryKey });
3311
+ await queryClient.invalidateQueries({
3312
+ queryKey: ["affiliate", "consignment-reports"]
3313
+ });
3314
+ },
3315
+ onError: (error) => ui.toast.error("Freigabe fehlgeschlagen.", {
3316
+ description: getErrorMessage(error)
3317
+ })
3318
+ });
3319
+ const reject = reactQuery.useMutation({
3320
+ mutationFn: (current2) => rejectAdminConsignmentReport(current2.id, { admin_notes: adminNotes }),
3321
+ onSuccess: async () => {
3322
+ ui.toast.success("Monatsmeldung wurde abgelehnt.");
3323
+ await queryClient.invalidateQueries({ queryKey });
3324
+ },
3325
+ onError: (error) => ui.toast.error("Ablehnung fehlgeschlagen.", {
3326
+ description: getErrorMessage(error)
3327
+ })
3328
+ });
3329
+ if (!reportId) {
3330
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Report-ID fehlt." }) });
3331
+ }
3332
+ if (report.isLoading) {
3333
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Monatsmeldung wird geladen …" }) });
3334
+ }
3335
+ if (report.isError || !report.data) {
3336
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: getErrorMessage(report.error) }) });
3337
+ }
3338
+ const current = report.data.report;
3339
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "grid gap-5", children: [
3340
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-4", children: [
3341
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3342
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Monatsmeldung prüfen" }),
3343
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
3344
+ formatDate(current.period_start),
3345
+ " · ",
3346
+ current.status,
3347
+ " · Version",
3348
+ " ",
3349
+ current.version
3350
+ ] })
3351
+ ] }),
3352
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: "/affiliates/consignment-reports", children: "Zur Liste" }) })
3353
+ ] }),
3354
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full text-left text-sm", children: [
3355
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "border-b", children: [
3356
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Artikel / Charge" }),
3357
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Erwartet" }),
3358
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Gezählt" }),
3359
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Vorschlag" }),
3360
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Abrechenbar" }),
3361
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Nicht-Verkaufsmenge" }),
3362
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Nicht-Verkauf" }),
3363
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "p-2", children: "Betrag" })
3364
+ ] }) }),
3365
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: current.lines.map((line) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "border-b", children: [
3366
+ /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "p-2", children: [
3367
+ line.inventory_item_id,
3368
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: line.batch_id ?? "ohne Charge" })
3369
+ ] }),
3370
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: line.expected_quantity }),
3371
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: line.counted_quantity }),
3372
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: line.proposed_sale_quantity }),
3373
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
3374
+ ui.Input,
3375
+ {
3376
+ type: "number",
3377
+ min: 0,
3378
+ step: 1,
3379
+ value: lines[line.id]?.quantity ?? 0,
3380
+ onChange: (event) => setLines((value) => ({
3381
+ ...value,
3382
+ [line.id]: {
3383
+ ...value[line.id] ?? { reason: "" },
3384
+ quantity: Number(event.target.value)
3385
+ }
3386
+ }))
3387
+ }
3388
+ ) }),
3389
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: Math.max(
3390
+ 0,
3391
+ line.proposed_sale_quantity - (lines[line.id]?.quantity ?? 0)
3392
+ ) }),
3393
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
3394
+ "select",
3395
+ {
3396
+ className: "bg-ui-bg-field border-ui-border-base rounded-md border px-2 py-2",
3397
+ value: lines[line.id]?.reason ?? "",
3398
+ onChange: (event) => setLines((value) => ({
3399
+ ...value,
3400
+ [line.id]: {
3401
+ ...value[line.id] ?? { quantity: 0 },
3402
+ reason: event.target.value
3403
+ }
3404
+ })),
3405
+ children: [
3406
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Verkauf" }),
3407
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "damaged", children: "Beschädigt" }),
3408
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "expired", children: "Abgelaufen" }),
3409
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "returned", children: "Retourniert" }),
3410
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "correction", children: "Korrektur" })
3411
+ ]
3412
+ }
3413
+ ) }),
3414
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-2", children: line.currency_code ? formatAmount(
3415
+ (lines[line.id]?.quantity ?? 0) * (line.discounted_line_total != null && line.proposed_sale_quantity > 0 ? line.discounted_line_total / line.proposed_sale_quantity : line.unit_price_snapshot ?? 0),
3416
+ line.currency_code
3417
+ ) : "–" })
3418
+ ] }, line.id)) })
3419
+ ] }) }),
3420
+ current.status === "submitted" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4", children: [
3421
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
3422
+ ui.Button,
3423
+ {
3424
+ onClick: () => approve.mutate(current),
3425
+ disabled: approve.isPending,
3426
+ children: approve.isPending ? "Wird freigegeben …" : "Freigeben"
3427
+ }
3428
+ ) }),
3429
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
3430
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "consignment-reject-notes", children: "Begründung für Ablehnung" }),
3431
+ /* @__PURE__ */ jsxRuntime.jsx(
3432
+ ui.Textarea,
3433
+ {
3434
+ id: "consignment-reject-notes",
3435
+ value: adminNotes,
3436
+ onChange: (event) => setAdminNotes(event.target.value)
3437
+ }
3438
+ ),
3439
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
3440
+ ui.Button,
3441
+ {
3442
+ variant: "danger",
3443
+ onClick: () => reject.mutate(current),
3444
+ disabled: !adminNotes.trim() || reject.isPending,
3445
+ children: reject.isPending ? "Wird abgelehnt …" : "Ablehnen"
3446
+ }
3447
+ ) })
3448
+ ] })
3449
+ ] }) : null
3450
+ ] });
3451
+ }
3452
+ const ConsignmentReportReviewRoute = () => {
3453
+ const { id } = reactRouterDom.useParams();
3454
+ return /* @__PURE__ */ jsxRuntime.jsx(ConsignmentReportReviewPage, { reportId: id });
3455
+ };
3456
+ const REPORT_ENTRIES_PAGE_SIZE = 100;
3457
+ const AffiliateReportDetailPage = ({
3458
+ affiliateId
3459
+ }) => {
3460
+ const [searchParams] = reactRouterDom.useSearchParams();
3461
+ const [from, setFrom] = react.useState(
3462
+ () => searchParams.get("from") ?? getMonthStartInputDate()
3463
+ );
3464
+ const [to, setTo] = react.useState(
3465
+ () => searchParams.get("to") ?? getTodayInputDate()
3466
+ );
3467
+ const [offset, setOffset] = react.useState(0);
3468
+ const reportQuery = useAffiliateReportDetail(affiliateId, {
3469
+ from,
3470
+ to,
3471
+ limit: REPORT_ENTRIES_PAGE_SIZE,
3472
+ offset
3473
+ });
3474
+ const report = reportQuery.data?.report;
3475
+ const entries = report?.entries ?? [];
3476
+ const entriesCount = report?.entries_count ?? 0;
3477
+ const handleFromChange = (value) => {
3478
+ setFrom(value);
3479
+ setOffset(0);
3480
+ };
3481
+ const handleToChange = (value) => {
3482
+ setTo(value);
3483
+ setOffset(0);
3484
+ };
3485
+ const showPagination = !reportQuery.isLoading && !reportQuery.isError && entriesCount > 0;
3486
+ const renderEntries = () => {
3487
+ if (reportQuery.isLoading) {
3488
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.reportDetail.loading }) });
3489
+ }
3490
+ if (reportQuery.isError) {
3491
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.reportDetail.loadError(getErrorMessage(reportQuery.error)) }) });
3492
+ }
3493
+ if (!entries.length) {
3494
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.reportDetail.empty }) });
3495
+ }
3496
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full text-left text-sm", children: [
3497
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "border-ui-border-base border-b", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "text-ui-fg-subtle", children: [
3498
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: texts.reportDetail.columns.occurredAt }),
3499
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: texts.reportDetail.columns.type }),
3500
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: texts.reportDetail.columns.order }),
3501
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: texts.reportDetail.columns.customer }),
3502
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right font-medium", children: texts.reportDetail.columns.base }),
3503
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right font-medium", children: texts.reportDetail.columns.rate }),
3504
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right font-medium", children: texts.reportDetail.columns.commission })
3505
+ ] }) }),
3506
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: entries.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "border-ui-border-base border-b", children: [
3507
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3", children: formatDateTime(entry.occurred_at) }),
3508
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3", children: formatCommissionEntryType(entry.type) }),
3509
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3", children: entry.order_id ? /* @__PURE__ */ jsxRuntime.jsx(
3510
+ reactRouterDom.Link,
3511
+ {
3512
+ to: `/orders/${entry.order_id}`,
3513
+ className: "text-ui-fg-interactive hover:underline",
3514
+ children: entry.order_display_id ? `#${entry.order_display_id}` : texts.reportDetail.viewOrder
3515
+ }
3516
+ ) : texts.common.dash }),
3517
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3", children: entry.customer_id ? /* @__PURE__ */ jsxRuntime.jsx(
3518
+ reactRouterDom.Link,
3519
+ {
3520
+ to: `/customers/${entry.customer_id}`,
3521
+ className: "text-ui-fg-interactive hover:underline",
3522
+ children: entry.customer_name ?? texts.common.dash
3523
+ }
3524
+ ) : texts.common.dash }),
3525
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 text-right tabular-nums", children: formatAmount(entry.base_amount_net, entry.currency_code) }),
3526
+ /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "px-6 py-3 text-right tabular-nums", children: [
3527
+ entry.commission_rate,
3528
+ "%"
3529
+ ] }),
3530
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 text-right font-medium tabular-nums", children: formatAmount(entry.commission_amount, entry.currency_code) })
3531
+ ] }, entry.id)) })
3532
+ ] }) });
3533
+ };
3534
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6", children: [
3535
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
3536
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
3537
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: report?.affiliate.display_name ?? texts.reportDetail.fallbackHeading }),
3538
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.reportDetail.description })
3539
+ ] }),
3540
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 px-6 py-4 md:grid-cols-[180px_180px]", children: [
3541
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
3542
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: texts.reportDetail.from }),
3543
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { type: "date", value: from, onChange: (event) => handleFromChange(event.target.value) })
3544
+ ] }),
3545
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
3546
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: texts.reportDetail.to }),
3547
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { type: "date", value: to, onChange: (event) => handleToChange(event.target.value) })
3548
+ ] })
3549
+ ] }),
3550
+ report?.totals.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-3 px-6 py-4 md:grid-cols-3", children: report.totals.map((total) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-ui-border-base rounded-md border p-3", children: [
3551
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle uppercase", children: total.currency_code }),
3552
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: formatAmount(total.total_commission_amount, total.currency_code) }),
3553
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle text-xs", children: [
3554
+ texts.reportDetail.orderValue,
3555
+ ": ",
3556
+ formatAmount(total.order_value_amount, total.currency_code)
3557
+ ] }),
3558
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: texts.reportDetail.orderCount(total.order_count) }),
3559
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: texts.reportDetail.entryCount(total.entry_count) }),
3560
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: texts.reports.attributedOrdersTooltip, children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "text-ui-fg-subtle text-left text-xs underline decoration-dotted underline-offset-2", children: texts.reportDetail.attributedOrders(total.attributed_order_count) }) }),
3561
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: texts.reports.commissionedOrdersTooltip, children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "text-ui-fg-subtle text-left text-xs underline decoration-dotted underline-offset-2", children: texts.reportDetail.commissionedOrders(total.commissioned_order_count) }) })
3562
+ ] }, total.currency_code)) }) : null
3563
+ ] }),
3564
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
3565
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: texts.reportDetail.journalEntries }) }),
3566
+ renderEntries(),
3567
+ showPagination ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
3568
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-sm", children: texts.common.pageRange(offset + 1, offset + entries.length, entriesCount) }),
3569
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
3570
+ /* @__PURE__ */ jsxRuntime.jsx(
3571
+ ui.Button,
3572
+ {
3573
+ size: "small",
3574
+ variant: "secondary",
3575
+ disabled: offset === 0,
3576
+ onClick: () => setOffset((current) => Math.max(0, current - REPORT_ENTRIES_PAGE_SIZE)),
3577
+ children: texts.common.back
3578
+ }
3579
+ ),
3580
+ /* @__PURE__ */ jsxRuntime.jsx(
3581
+ ui.Button,
3582
+ {
3583
+ size: "small",
3584
+ variant: "secondary",
3585
+ disabled: offset + entries.length >= entriesCount,
3586
+ onClick: () => setOffset((current) => current + REPORT_ENTRIES_PAGE_SIZE),
3587
+ children: texts.common.next
3588
+ }
3589
+ )
3590
+ ] })
3591
+ ] }) : null
3592
+ ] })
3593
+ ] });
3594
+ };
3595
+ const AffiliateReportDetailRoute = () => {
3596
+ const { id } = reactRouterDom.useParams();
3597
+ return /* @__PURE__ */ jsxRuntime.jsx(AffiliateReportDetailPage, { affiliateId: id });
3598
+ };
3599
+ const widgetModule = { widgets: [
3600
+ {
3601
+ Component: CustomerAffiliateWidget,
3602
+ zone: ["customer.details.side.after"],
3603
+ widgetId: "Widget-3af7"
3604
+ },
3605
+ {
3606
+ Component: OrderAffiliateAttributionWidget,
3607
+ zone: ["order.details.side.after"],
3608
+ widgetId: "Widget-52d0"
3609
+ }
3610
+ ] };
3611
+ const routeModule = {
3612
+ routes: [
3613
+ {
3614
+ Component: AffiliatesRoute,
3615
+ path: "/affiliates",
3616
+ handle: { label: config$6.label, translationNs: config$6.translationNs }
3617
+ },
3618
+ {
3619
+ Component: AffiliateApplicationsRoute,
3620
+ path: "/affiliates/applications",
3621
+ handle: { label: config$5.label, translationNs: config$5.translationNs }
3622
+ },
3623
+ {
3624
+ Component: AffiliateEditRoute,
3625
+ path: "/affiliates/:id"
3626
+ },
3627
+ {
3628
+ Component: AffiliateDistributionReportRoute,
3629
+ path: "/affiliates/distribution-reports",
3630
+ handle: { label: config$4.label, translationNs: config$4.translationNs }
3631
+ },
3632
+ {
3633
+ Component: ConsignmentReportsRoute,
3634
+ path: "/affiliates/consignment-reports",
3635
+ handle: { label: config$3.label, translationNs: config$3.translationNs }
3636
+ },
3637
+ {
3638
+ Component: NewAffiliateRoute,
3639
+ path: "/affiliates/new",
3640
+ handle: { label: config$2.label, translationNs: config$2.translationNs }
3641
+ },
3642
+ {
3643
+ Component: AffiliateReportsRoute,
3644
+ path: "/affiliates/reports",
3645
+ handle: { label: config$1.label, translationNs: config$1.translationNs }
3646
+ },
3647
+ {
3648
+ Component: AffiliateProgramSettingsRoute,
3649
+ path: "/affiliates/settings",
3650
+ handle: { label: config.label, translationNs: config.translationNs }
3651
+ },
3652
+ {
3653
+ Component: ConsignmentReportReviewRoute,
3654
+ path: "/affiliates/consignment-reports/:id"
3655
+ },
3656
+ {
3657
+ Component: AffiliateReportDetailRoute,
3658
+ path: "/affiliates/reports/:id"
3659
+ }
3660
+ ]
3661
+ };
3662
+ const menuItemModule = {
3663
+ menuItems: [
3664
+ {
3665
+ label: config$6.label,
3666
+ icon: void 0,
3667
+ path: "/affiliates",
3668
+ nested: void 0,
3669
+ rank: 81,
3670
+ translationNs: void 0
3671
+ },
3672
+ {
3673
+ label: config$5.label,
3674
+ icon: void 0,
3675
+ path: "/affiliates/applications",
3676
+ nested: void 0,
3677
+ rank: 80,
3678
+ translationNs: void 0
3679
+ },
3680
+ {
3681
+ label: config$3.label,
3682
+ icon: void 0,
3683
+ path: "/affiliates/consignment-reports",
3684
+ nested: void 0,
3685
+ rank: 83,
3686
+ translationNs: void 0
3687
+ },
3688
+ {
3689
+ label: config$4.label,
3690
+ icon: void 0,
3691
+ path: "/affiliates/distribution-reports",
3692
+ nested: void 0,
3693
+ rank: 85,
3694
+ translationNs: void 0
3695
+ },
3696
+ {
3697
+ label: config$2.label,
3698
+ icon: void 0,
3699
+ path: "/affiliates/new",
3700
+ nested: void 0,
3701
+ rank: void 0,
3702
+ translationNs: void 0
3703
+ },
3704
+ {
3705
+ label: config$1.label,
3706
+ icon: void 0,
3707
+ path: "/affiliates/reports",
3708
+ nested: void 0,
3709
+ rank: 82,
3710
+ translationNs: void 0
3711
+ },
3712
+ {
3713
+ label: config.label,
3714
+ icon: void 0,
3715
+ path: "/affiliates/settings",
3716
+ nested: void 0,
3717
+ rank: 84,
3718
+ translationNs: void 0
3719
+ }
3720
+ ]
3721
+ };
3722
+ const formModule = { customFields: {} };
3723
+ const displayModule = {
3724
+ displays: {}
3725
+ };
3726
+ const i18nModule = { resources: {} };
3727
+ const cellRendererModule = {};
3728
+ const layoutModule = { layouts: [] };
3729
+ const plugin = {
3730
+ widgetModule,
3731
+ routeModule,
3732
+ menuItemModule,
3733
+ formModule,
3734
+ displayModule,
3735
+ i18nModule,
3736
+ cellRendererModule,
3737
+ layoutModule
3738
+ };
3739
+ module.exports = plugin;