@mohasinac/appkit 4.59.0 โ†’ 4.60.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 (531) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +62 -5
  3. package/dist/_internal/client/features/maintenance/views/MaintenanceDashboardView.js +2 -2
  4. package/dist/_internal/server/features/brands/actions.js +78 -4
  5. package/dist/_internal/server/features/checkout/actions.d.ts +36 -13
  6. package/dist/_internal/server/features/checkout/actions.js +668 -183
  7. package/dist/_internal/server/features/checkout/supplier-identity.js +18 -1
  8. package/dist/_internal/server/features/gateway-events/index.d.ts +10 -0
  9. package/dist/_internal/server/features/gateway-events/index.js +10 -0
  10. package/dist/_internal/server/features/gateway-events/ingest.d.ts +127 -0
  11. package/dist/_internal/server/features/gateway-events/ingest.js +188 -0
  12. package/dist/_internal/server/features/gateway-events/redact.d.ts +66 -0
  13. package/dist/_internal/server/features/gateway-events/redact.js +151 -0
  14. package/dist/_internal/server/features/grievance/actions.d.ts +63 -0
  15. package/dist/_internal/server/features/grievance/actions.js +173 -0
  16. package/dist/_internal/server/features/grievance/index.d.ts +8 -0
  17. package/dist/_internal/server/features/grievance/index.js +8 -0
  18. package/dist/_internal/server/features/grouped/data.js +28 -3
  19. package/dist/_internal/server/features/lottery/actions.js +99 -4
  20. package/dist/_internal/server/features/order-completion/actions.d.ts +92 -0
  21. package/dist/_internal/server/features/order-completion/actions.js +336 -0
  22. package/dist/_internal/server/features/order-completion/index.d.ts +7 -0
  23. package/dist/_internal/server/features/order-completion/index.js +7 -0
  24. package/dist/_internal/server/features/orders/actions.d.ts +13 -0
  25. package/dist/_internal/server/features/orders/actions.js +56 -2
  26. package/dist/_internal/server/features/orders/adapters.js +26 -0
  27. package/dist/_internal/server/features/orders/cancellation-refund.d.ts +116 -0
  28. package/dist/_internal/server/features/orders/cancellation-refund.js +466 -0
  29. package/dist/_internal/server/features/orders/index.d.ts +2 -1
  30. package/dist/_internal/server/features/orders/index.js +1 -1
  31. package/dist/_internal/server/features/orders/service.d.ts +60 -0
  32. package/dist/_internal/server/features/orders/service.js +74 -0
  33. package/dist/_internal/server/features/payment-cases/disputes.d.ts +76 -0
  34. package/dist/_internal/server/features/payment-cases/disputes.js +241 -0
  35. package/dist/_internal/server/features/payment-cases/index.d.ts +9 -0
  36. package/dist/_internal/server/features/payment-cases/index.js +9 -0
  37. package/dist/_internal/server/features/payouts/actions.d.ts +28 -3
  38. package/dist/_internal/server/features/payouts/actions.js +84 -10
  39. package/dist/_internal/server/features/points/adjust.d.ts +61 -0
  40. package/dist/_internal/server/features/points/adjust.js +121 -0
  41. package/dist/_internal/server/features/points/award.d.ts +65 -0
  42. package/dist/_internal/server/features/points/award.js +145 -0
  43. package/dist/_internal/server/features/price-index/market.d.ts +33 -0
  44. package/dist/_internal/server/features/price-index/market.js +54 -1
  45. package/dist/_internal/server/features/products/data.js +40 -22
  46. package/dist/_internal/server/features/protection-claims/actions.d.ts +72 -0
  47. package/dist/_internal/server/features/protection-claims/actions.js +228 -0
  48. package/dist/_internal/server/features/protection-claims/index.d.ts +8 -0
  49. package/dist/_internal/server/features/protection-claims/index.js +8 -0
  50. package/dist/_internal/server/features/refunds/actions.d.ts +20 -0
  51. package/dist/_internal/server/features/refunds/actions.js +259 -17
  52. package/dist/_internal/server/features/refunds/capacity.d.ts +29 -0
  53. package/dist/_internal/server/features/refunds/capacity.js +37 -0
  54. package/dist/_internal/server/features/reviews/actions.js +3 -2
  55. package/dist/_internal/server/features/search/index-build/sources.js +20 -1
  56. package/dist/_internal/server/features/shipping/reconcile.d.ts +10 -1
  57. package/dist/_internal/server/features/shipping/reconcile.js +34 -3
  58. package/dist/_internal/server/features/shipping/reimbursement.d.ts +117 -0
  59. package/dist/_internal/server/features/shipping/reimbursement.js +223 -0
  60. package/dist/_internal/server/features/stores/tabs.d.ts +19 -4
  61. package/dist/_internal/server/features/stores/tabs.js +24 -4
  62. package/dist/_internal/server/functions/https.d.ts +31 -1
  63. package/dist/_internal/server/functions/https.js +52 -1
  64. package/dist/_internal/server/functions/scheduled.d.ts +72 -1
  65. package/dist/_internal/server/functions/scheduled.js +124 -1
  66. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +62 -16
  67. package/dist/_internal/server/jobs/core/boxRollupReconcile.d.ts +46 -0
  68. package/dist/_internal/server/jobs/core/boxRollupReconcile.js +142 -0
  69. package/dist/_internal/server/jobs/core/dailyDataCleanup.js +149 -1
  70. package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +14 -0
  71. package/dist/_internal/server/jobs/core/dailyStatusDigest.js +79 -1
  72. package/dist/_internal/server/jobs/core/index.d.ts +2 -0
  73. package/dist/_internal/server/jobs/core/index.js +2 -0
  74. package/dist/_internal/server/jobs/core/marketPriceRollup.d.ts +64 -0
  75. package/dist/_internal/server/jobs/core/marketPriceRollup.js +153 -0
  76. package/dist/_internal/server/jobs/core/obligationSweep.d.ts +36 -0
  77. package/dist/_internal/server/jobs/core/obligationSweep.js +503 -0
  78. package/dist/_internal/server/jobs/core/paymentReconcile.d.ts +79 -0
  79. package/dist/_internal/server/jobs/core/paymentReconcile.js +614 -0
  80. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +34 -0
  81. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +35 -4
  82. package/dist/_internal/server/jobs/handlers/boxRollupReconcile.d.ts +2 -0
  83. package/dist/_internal/server/jobs/handlers/boxRollupReconcile.js +15 -0
  84. package/dist/_internal/server/jobs/handlers/countersReconcileNow.d.ts +65 -0
  85. package/dist/_internal/server/jobs/handlers/countersReconcileNow.js +89 -0
  86. package/dist/_internal/server/jobs/handlers/index.d.ts +5 -0
  87. package/dist/_internal/server/jobs/handlers/index.js +5 -0
  88. package/dist/_internal/server/jobs/handlers/marketPriceRollup.d.ts +2 -0
  89. package/dist/_internal/server/jobs/handlers/marketPriceRollup.js +2 -0
  90. package/dist/_internal/server/jobs/handlers/obligationSweep.d.ts +2 -0
  91. package/dist/_internal/server/jobs/handlers/obligationSweep.js +15 -0
  92. package/dist/_internal/server/jobs/handlers/onShipmentAllocationSync.js +30 -1
  93. package/dist/_internal/server/jobs/handlers/paymentReconcile.d.ts +2 -0
  94. package/dist/_internal/server/jobs/handlers/paymentReconcile.js +6 -0
  95. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  96. package/dist/_internal/shared/actions/action-registry.js +71 -0
  97. package/dist/_internal/shared/checkout/coupon-discount.d.ts +73 -0
  98. package/dist/_internal/shared/checkout/coupon-discount.js +103 -0
  99. package/dist/_internal/shared/checkout/lanes.d.ts +42 -0
  100. package/dist/_internal/shared/checkout/lanes.js +46 -0
  101. package/dist/_internal/shared/checkout/order-math.d.ts +149 -1
  102. package/dist/_internal/shared/checkout/order-math.js +251 -3
  103. package/dist/_internal/shared/checkout/refundable-base.d.ts +116 -0
  104. package/dist/_internal/shared/checkout/refundable-base.js +122 -0
  105. package/dist/_internal/shared/features/auctions/config.d.ts +14 -1
  106. package/dist/_internal/shared/features/auctions/config.js +30 -1
  107. package/dist/_internal/shared/features/brands/schema.d.ts +42 -0
  108. package/dist/_internal/shared/features/brands/schema.js +18 -0
  109. package/dist/_internal/shared/features/orders/refund-copy.d.ts +22 -0
  110. package/dist/_internal/shared/features/orders/refund-copy.js +34 -0
  111. package/dist/_internal/shared/features/print/label-profiles.d.ts +198 -0
  112. package/dist/_internal/shared/features/print/label-profiles.js +350 -0
  113. package/dist/_internal/shared/features/products/to-product-item.js +33 -5
  114. package/dist/_internal/shared/fees/calculator.d.ts +31 -2
  115. package/dist/_internal/shared/fees/calculator.js +103 -5
  116. package/dist/_internal/shared/fees/india-states.d.ts +195 -0
  117. package/dist/_internal/shared/fees/india-states.js +190 -0
  118. package/dist/_internal/shared/fees/payout-hold.d.ts +80 -0
  119. package/dist/_internal/shared/fees/payout-hold.js +74 -0
  120. package/dist/_internal/shared/obligations/deadlines.d.ts +94 -0
  121. package/dist/_internal/shared/obligations/deadlines.js +155 -0
  122. package/dist/_internal/shared/shipping/quote.d.ts +96 -0
  123. package/dist/_internal/shared/shipping/quote.js +67 -0
  124. package/dist/_internal/shared/time/business-days.d.ts +91 -0
  125. package/dist/_internal/shared/time/business-days.js +185 -0
  126. package/dist/cli/index.js +0 -2
  127. package/dist/client.d.ts +9 -4
  128. package/dist/client.js +26 -3
  129. package/dist/configs/tailwind.d.ts +0 -12
  130. package/dist/configs/tailwind.js +28 -15
  131. package/dist/constants/api-endpoints.d.ts +51 -9
  132. package/dist/constants/api-endpoints.js +23 -7
  133. package/dist/constants/index.d.ts +1 -1
  134. package/dist/constants/index.js +1 -1
  135. package/dist/contracts/index.d.ts +1 -1
  136. package/dist/contracts/payment.d.ts +131 -0
  137. package/dist/features/about/components/AboutView.js +1 -1
  138. package/dist/features/about/components/DeveloperView.js +5 -2
  139. package/dist/features/about/components/FAQPageView.js +2 -1
  140. package/dist/features/about/components/FeesView.js +2 -1
  141. package/dist/features/about/components/HelpPageView.js +2 -1
  142. package/dist/features/about/components/HowAuctionsWorkView.js +2 -1
  143. package/dist/features/about/components/HowCheckoutWorksView.js +2 -1
  144. package/dist/features/about/components/HowEmiWorksView.js +2 -1
  145. package/dist/features/about/components/HowOffersWorkView.js +2 -1
  146. package/dist/features/about/components/HowOrdersWorkView.js +2 -1
  147. package/dist/features/about/components/HowPayoutsWorkView.js +2 -1
  148. package/dist/features/about/components/HowPreOrdersWorkView.js +2 -1
  149. package/dist/features/about/components/HowReviewsWorkView.js +2 -1
  150. package/dist/features/about/components/PolicyPageView.js +2 -1
  151. package/dist/features/about/components/ProseHero.d.ts +73 -0
  152. package/dist/features/about/components/ProseHero.js +18 -0
  153. package/dist/features/about/components/SecurityPrivacyView.js +2 -1
  154. package/dist/features/about/components/ShippingPolicyView.js +2 -1
  155. package/dist/features/about/components/TrackOrderView.js +2 -1
  156. package/dist/features/about/components/UnauthorizedView.js +1 -1
  157. package/dist/features/about/index.d.ts +2 -0
  158. package/dist/features/about/index.js +5 -0
  159. package/dist/features/account/actions/address-actions.d.ts +26 -2
  160. package/dist/features/account/actions/address-actions.js +25 -1
  161. package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
  162. package/dist/features/account/components/NotificationPreferencesPanel.js +1 -1
  163. package/dist/features/account/components/UserOrdersView.js +17 -0
  164. package/dist/features/account/components/UserSidebar.js +1 -1
  165. package/dist/features/account/hooks/useNotifications.js +29 -0
  166. package/dist/features/admin/actions/admin-coupon-actions.js +36 -0
  167. package/dist/features/admin/components/AdminBeysView.js +2 -2
  168. package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
  169. package/dist/features/admin/components/AdminBrandEditorView.js +32 -2
  170. package/dist/features/admin/components/AdminBundleEditorView.js +1 -1
  171. package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
  172. package/dist/features/admin/components/AdminCategoriesView.js +1 -1
  173. package/dist/features/admin/components/AdminCategoryEditorView.js +73 -17
  174. package/dist/features/admin/components/AdminContactEditorView.js +1 -1
  175. package/dist/features/admin/components/AdminCouponEditorView.js +26 -1
  176. package/dist/features/admin/components/AdminFaqEditorView.js +1 -1
  177. package/dist/features/admin/components/AdminFaqsView.js +2 -2
  178. package/dist/features/admin/components/AdminGatewayEventsView.d.ts +54 -0
  179. package/dist/features/admin/components/AdminGatewayEventsView.js +209 -0
  180. package/dist/features/admin/components/AdminGuideHubView.js +1 -1
  181. package/dist/features/admin/components/AdminMoneyCasesView.d.ts +60 -0
  182. package/dist/features/admin/components/AdminMoneyCasesView.js +287 -0
  183. package/dist/features/admin/components/AdminPartsView.js +2 -2
  184. package/dist/features/admin/components/AdminProductsView.js +75 -4
  185. package/dist/features/admin/components/AdminSidebar.js +2 -2
  186. package/dist/features/admin/components/AdminSiteSettingsView.js +65 -7
  187. package/dist/features/admin/components/AdminSublistingCategoriesView.js +2 -0
  188. package/dist/features/admin/components/AdminSupportTicketDetailView.js +1 -1
  189. package/dist/features/admin/components/AdminSupportTicketsView.js +2 -2
  190. package/dist/features/admin/components/AdminTesterChecklistView.js +2 -2
  191. package/dist/features/admin/components/AdminUsersView.js +72 -1
  192. package/dist/features/admin/components/AdminViewCards.d.ts +3 -1
  193. package/dist/features/admin/components/AdminViewCards.js +4 -4
  194. package/dist/features/admin/components/DataListingView.d.ts +28 -0
  195. package/dist/features/admin/components/DataListingView.js +175 -14
  196. package/dist/features/admin/components/DataTable.d.ts +12 -1
  197. package/dist/features/admin/components/DataTable.js +3 -3
  198. package/dist/features/admin/components/OwnerQuickActions.d.ts +1 -1
  199. package/dist/features/admin/components/OwnerQuickActions.js +60 -2
  200. package/dist/features/admin/components/RecordOwnerQuickActions.d.ts +1 -1
  201. package/dist/features/admin/components/RecordOwnerQuickActions.js +61 -14
  202. package/dist/features/admin/components/analytics/AdminStatCard.d.ts +35 -1
  203. package/dist/features/admin/components/analytics/AdminStatCard.js +77 -2
  204. package/dist/features/admin/components/index.d.ts +4 -0
  205. package/dist/features/admin/components/index.js +6 -0
  206. package/dist/features/admin/constants/filter-tabs.d.ts +156 -0
  207. package/dist/features/admin/constants/filter-tabs.js +106 -0
  208. package/dist/features/admin/repository/site-settings.repository.d.ts +22 -0
  209. package/dist/features/admin/repository/site-settings.repository.js +82 -1
  210. package/dist/features/admin/schemas/admin-editor-forms.d.ts +21 -0
  211. package/dist/features/admin/schemas/admin-editor-forms.js +44 -0
  212. package/dist/features/admin/schemas/firestore.js +5 -1
  213. package/dist/features/admin/schemas/site-settings-form.js +30 -1
  214. package/dist/features/auctions/actions/bid-actions.js +13 -1
  215. package/dist/features/auctions/components/AuctionBottomActions.js +2 -2
  216. package/dist/features/auctions/components/AuctionDetailPageView.js +3 -1
  217. package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -0
  218. package/dist/features/auctions/components/MarketplaceAuctionCard.js +5 -3
  219. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +8 -1
  220. package/dist/features/auctions/components/PlaceBidFormClient.js +2 -2
  221. package/dist/features/audit-log/schemas/firestore.d.ts +28 -0
  222. package/dist/features/audit-log/schemas/firestore.js +14 -0
  223. package/dist/features/auth/permissions/constants.d.ts +17 -1
  224. package/dist/features/auth/permissions/constants.js +18 -0
  225. package/dist/features/blog/components/BlogPostView.js +1 -1
  226. package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +1 -1
  227. package/dist/features/categories/api/[id]/description-template/route.js +107 -1
  228. package/dist/features/categories/components/BrandDetailPageView.js +2 -2
  229. package/dist/features/categories/components/BreadcrumbTrail.js +7 -1
  230. package/dist/features/categories/components/BundleDetailView.js +1 -1
  231. package/dist/features/categories/components/CategoryDetailPageView.js +2 -2
  232. package/dist/features/categories/components/CategoryForm.js +12 -1
  233. package/dist/features/categories/components/CategoryTemplatePicker.js +38 -5
  234. package/dist/features/categories/repository/market-price-index.repository.d.ts +123 -0
  235. package/dist/features/categories/repository/market-price-index.repository.js +105 -0
  236. package/dist/features/categories/repository/price-observation.repository.d.ts +166 -0
  237. package/dist/features/categories/repository/price-observation.repository.js +132 -0
  238. package/dist/features/events/components/EventSaleBanner.js +1 -1
  239. package/dist/features/events/components/EventStatusBadge.js +1 -1
  240. package/dist/features/faq/components/FAQAccordion.js +2 -1
  241. package/dist/features/faq/components/FAQCategorySidebar.js +2 -2
  242. package/dist/features/faq/components/FAQHelpfulButtons.js +4 -4
  243. package/dist/features/faq/components/RelatedFAQs.js +1 -1
  244. package/dist/features/filters/FilterFacetSection.js +1 -1
  245. package/dist/features/filters/SwitchFilter.js +1 -1
  246. package/dist/features/gateway-events/repository/gateway-events.repository.d.ts +117 -0
  247. package/dist/features/gateway-events/repository/gateway-events.repository.js +274 -0
  248. package/dist/features/gateway-events/schemas/firestore.d.ts +217 -0
  249. package/dist/features/gateway-events/schemas/firestore.js +125 -0
  250. package/dist/features/gateway-events/schemas/index.d.ts +81 -0
  251. package/dist/features/gateway-events/schemas/index.js +79 -0
  252. package/dist/features/grouped/components/GroupedListingDetailView.js +1 -1
  253. package/dist/features/homepage/components/AnnouncementBar.js +37 -1
  254. package/dist/features/homepage/components/AnnouncementBar.style.css +30 -0
  255. package/dist/features/homepage/components/BrandsSection.js +1 -1
  256. package/dist/features/homepage/components/CTABannerSection.js +1 -1
  257. package/dist/features/homepage/components/FeaturedBundlesSection.js +14 -24
  258. package/dist/features/homepage/components/HeroCarousel.js +2 -2
  259. package/dist/features/homepage/components/SocialPostCard.js +1 -1
  260. package/dist/features/inventory/components/AdminMoveLedgerView.d.ts +7 -0
  261. package/dist/features/inventory/components/AdminMoveLedgerView.js +157 -0
  262. package/dist/features/inventory/components/AdminStorageBoxesView.d.ts +11 -0
  263. package/dist/features/inventory/components/AdminStorageBoxesView.js +168 -0
  264. package/dist/features/inventory/components/ReceiveStationView.d.ts +39 -0
  265. package/dist/features/inventory/components/ReceiveStationView.js +413 -0
  266. package/dist/features/inventory/components/operator-message.d.ts +29 -0
  267. package/dist/features/inventory/components/operator-message.js +41 -0
  268. package/dist/features/inventory/index.d.ts +13 -0
  269. package/dist/features/inventory/index.js +28 -0
  270. package/dist/features/inventory/repository/inventory-movements.repository.d.ts +97 -0
  271. package/dist/features/inventory/repository/inventory-movements.repository.js +186 -0
  272. package/dist/features/inventory/repository/inventory-placements.repository.d.ts +179 -0
  273. package/dist/features/inventory/repository/inventory-placements.repository.js +345 -0
  274. package/dist/features/inventory/repository/storage-boxes.repository.d.ts +83 -0
  275. package/dist/features/inventory/repository/storage-boxes.repository.js +127 -0
  276. package/dist/features/inventory/schemas/firestore.d.ts +197 -0
  277. package/dist/features/inventory/schemas/firestore.js +159 -0
  278. package/dist/features/layout/AppLayoutShell.d.ts +12 -1
  279. package/dist/features/layout/AppLayoutShell.js +25 -12
  280. package/dist/features/layout/AutoBreadcrumbs.js +162 -3
  281. package/dist/features/layout/AutoBreadcrumbs.style.css +45 -0
  282. package/dist/features/layout/BottomNavbar.d.ts +1 -1
  283. package/dist/features/layout/BottomNavbar.js +2 -2
  284. package/dist/features/media/MediaAvatar.js +1 -1
  285. package/dist/features/media/MediaImage.d.ts +24 -4
  286. package/dist/features/media/MediaImage.js +44 -4
  287. package/dist/features/media/MediaLightbox.js +1 -1
  288. package/dist/features/media/modals/ImageCropModal.js +1 -1
  289. package/dist/features/media/modals/ImageEditor.js +1 -1
  290. package/dist/features/media/upload/MediaUploadField.js +91 -3
  291. package/dist/features/obligations/constants/policies.d.ts +92 -0
  292. package/dist/features/obligations/constants/policies.js +378 -0
  293. package/dist/features/obligations/repository/obligations.repository.d.ts +68 -0
  294. package/dist/features/obligations/repository/obligations.repository.js +158 -0
  295. package/dist/features/obligations/schemas/firestore.d.ts +160 -0
  296. package/dist/features/obligations/schemas/firestore.js +87 -0
  297. package/dist/features/orders/actions/order-actions.d.ts +43 -4
  298. package/dist/features/orders/actions/order-actions.js +60 -3
  299. package/dist/features/orders/actions/refund-actions.js +70 -10
  300. package/dist/features/orders/components/OrderCompletionPanel.d.ts +27 -0
  301. package/dist/features/orders/components/OrderCompletionPanel.js +192 -0
  302. package/dist/features/orders/components/OrdersList.js +2 -2
  303. package/dist/features/orders/components/RefundHistoryTable.js +3 -1
  304. package/dist/features/orders/components/index.d.ts +2 -0
  305. package/dist/features/orders/components/index.js +6 -0
  306. package/dist/features/orders/constants/transitions.d.ts +59 -0
  307. package/dist/features/orders/constants/transitions.js +104 -0
  308. package/dist/features/orders/repository/orders.repository.d.ts +109 -6
  309. package/dist/features/orders/repository/orders.repository.js +464 -21
  310. package/dist/features/orders/schemas/firestore.d.ts +270 -0
  311. package/dist/features/orders/schemas/firestore.js +24 -0
  312. package/dist/features/orders/schemas/index.d.ts +61 -0
  313. package/dist/features/orders/schemas/index.js +22 -0
  314. package/dist/features/orders/types/index.d.ts +79 -0
  315. package/dist/features/parts/components/PartsIndexPageView.js +8 -1
  316. package/dist/features/payment-cases/repository/payment-cases.repository.d.ts +170 -0
  317. package/dist/features/payment-cases/repository/payment-cases.repository.js +342 -0
  318. package/dist/features/payment-cases/schemas/firestore.d.ts +229 -0
  319. package/dist/features/payment-cases/schemas/firestore.js +105 -0
  320. package/dist/features/payment-cases/schemas/resolve-form.d.ts +97 -0
  321. package/dist/features/payment-cases/schemas/resolve-form.js +114 -0
  322. package/dist/features/payments/repository/payout.repository.d.ts +0 -1
  323. package/dist/features/payments/repository/payout.repository.js +0 -1
  324. package/dist/features/points/repository/points.repository.d.ts +122 -0
  325. package/dist/features/points/repository/points.repository.js +304 -1
  326. package/dist/features/points/schemas/adjust-form.d.ts +58 -0
  327. package/dist/features/points/schemas/adjust-form.js +63 -0
  328. package/dist/features/points/schemas/firestore.d.ts +108 -1
  329. package/dist/features/points/schemas/firestore.js +84 -0
  330. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
  331. package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
  332. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
  333. package/dist/features/price-index/components/PriceIndexToolView.d.ts +23 -4
  334. package/dist/features/price-index/components/PriceIndexToolView.js +71 -6
  335. package/dist/features/price-index/market-stats.d.ts +82 -0
  336. package/dist/features/price-index/market-stats.js +230 -0
  337. package/dist/features/print/components/BoxLabelPrintView.d.ts +25 -0
  338. package/dist/features/print/components/BoxLabelPrintView.js +66 -0
  339. package/dist/features/print/components/LabelProfilePicker.d.ts +13 -0
  340. package/dist/features/print/components/LabelProfilePicker.js +38 -0
  341. package/dist/features/print/components/labels.d.ts +117 -0
  342. package/dist/features/print/components/labels.js +258 -0
  343. package/dist/features/print/components/labels.style.css +46 -0
  344. package/dist/features/print/index.d.ts +6 -0
  345. package/dist/features/print/index.js +16 -0
  346. package/dist/features/products/columns/productTableColumns.js +5 -4
  347. package/dist/features/products/components/MarketplaceBundleCard.js +2 -2
  348. package/dist/features/products/components/MarketplacePrizeDrawCard.js +6 -6
  349. package/dist/features/products/components/PrizeDrawCollage.js +1 -1
  350. package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
  351. package/dist/features/products/components/PrizeRevealModal.js +2 -2
  352. package/dist/features/products/components/ProductDetailPageView.js +18 -4
  353. package/dist/features/products/components/ProductForm.js +34 -23
  354. package/dist/features/products/components/ProductGrid.js +86 -62
  355. package/dist/features/products/constants/card-badges.d.ts +97 -0
  356. package/dist/features/products/constants/card-badges.js +100 -0
  357. package/dist/features/products/repository/products.repository.js +34 -1
  358. package/dist/features/products/schemas/firestore.d.ts +46 -3
  359. package/dist/features/products/schemas/firestore.js +21 -0
  360. package/dist/features/promotions/components/PromotionsView.js +1 -1
  361. package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +32 -0
  362. package/dist/features/promotions/repository/claimed-coupons.repository.js +58 -0
  363. package/dist/features/promotions/schemas/firestore.js +30 -3
  364. package/dist/features/promotions/schemas/index.d.ts +6 -6
  365. package/dist/features/protection-claims/repository/protection-claims.repository.d.ts +54 -0
  366. package/dist/features/protection-claims/repository/protection-claims.repository.js +174 -0
  367. package/dist/features/protection-claims/schemas/firestore.d.ts +168 -0
  368. package/dist/features/protection-claims/schemas/firestore.js +130 -0
  369. package/dist/features/reviews/actions/review-actions.js +3 -2
  370. package/dist/features/reviews/components/ReviewModal.js +1 -1
  371. package/dist/features/reviews/components/ReviewsList.js +1 -1
  372. package/dist/features/reviews/schemas/firestore.d.ts +20 -1
  373. package/dist/features/scams/constants/scam-types.d.ts +2 -0
  374. package/dist/features/scams/constants/scam-types.js +18 -0
  375. package/dist/features/scams/schemas/report-form.d.ts +3 -3
  376. package/dist/features/scams/schemas/report-form.js +26 -1
  377. package/dist/features/search/components/Search.js +9 -9
  378. package/dist/features/search/components/SearchResultsClient.js +18 -3
  379. package/dist/features/search/components/SearchResultsPageView.d.ts +1 -6
  380. package/dist/features/search/components/SearchResultsPageView.js +2 -2
  381. package/dist/features/seller/actions/offer-actions.js +16 -1
  382. package/dist/features/seller/actions/seller-actions.js +14 -3
  383. package/dist/features/seller/components/CategoryInlineSelect.js +75 -11
  384. package/dist/features/seller/components/PrintCenterView.d.ts +5 -0
  385. package/dist/features/seller/components/PrintCenterView.js +41 -30
  386. package/dist/features/seller/components/SellerOrdersView.js +1 -1
  387. package/dist/features/seller/components/SellerPayoutRequestView.js +16 -5
  388. package/dist/features/seller/components/SellerPayoutSettingsView.js +1 -1
  389. package/dist/features/seller/components/SellerProductShell.d.ts +16 -0
  390. package/dist/features/seller/components/SellerProductShell.js +123 -13
  391. package/dist/features/seller/components/SellerReviewsView.js +4 -2
  392. package/dist/features/seller/components/SellerSidebar.js +1 -1
  393. package/dist/features/seller/schemas/coupon-form.d.ts +26 -0
  394. package/dist/features/seller/schemas/coupon-form.js +27 -0
  395. package/dist/features/seller/schemas/product-form.d.ts +3 -0
  396. package/dist/features/seller/schemas/product-form.js +5 -0
  397. package/dist/features/shell/FormShell.js +2 -2
  398. package/dist/features/shell/QuickFormDrawer.js +14 -0
  399. package/dist/features/shell/SectionForm.d.ts +19 -0
  400. package/dist/features/shell/SectionForm.js +133 -9
  401. package/dist/features/shell/SectionForm.style.css +54 -0
  402. package/dist/features/shipments/components/AdminShipmentsView.js +16 -1
  403. package/dist/features/shipments/repository/shipment-items.repository.d.ts +9 -0
  404. package/dist/features/shipments/repository/shipment-items.repository.js +14 -0
  405. package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
  406. package/dist/features/sticker-trace/components/StickerCommitPanel.js +1 -1
  407. package/dist/features/store-extensions/schemas/report-review-form.d.ts +8 -8
  408. package/dist/features/stores/components/StoreGuideHubView.js +1 -1
  409. package/dist/features/stores/components/StoreHeader.js +1 -1
  410. package/dist/features/support/schemas/firestore.d.ts +73 -2
  411. package/dist/features/support/schemas/firestore.js +59 -0
  412. package/dist/index.d.ts +16 -24
  413. package/dist/index.js +38 -47
  414. package/dist/next/ErrorBoundary.js +1 -1
  415. package/dist/next/routing/route-map.d.ts +24 -0
  416. package/dist/next/routing/route-map.js +40 -0
  417. package/dist/providers/payment-manual/index.d.ts +15 -1
  418. package/dist/providers/payment-manual/index.js +37 -0
  419. package/dist/providers/payment-phonepe/index.d.ts +14 -1
  420. package/dist/providers/payment-phonepe/index.js +36 -0
  421. package/dist/providers/payment-razorpay/index.d.ts +44 -1
  422. package/dist/providers/payment-razorpay/index.js +99 -0
  423. package/dist/providers/shipping-shiprocket/index.d.ts +18 -0
  424. package/dist/providers/shipping-shiprocket/index.js +18 -0
  425. package/dist/schemas/registry.d.ts +44 -11
  426. package/dist/server-entry.d.ts +2 -0
  427. package/dist/server-entry.js +27 -0
  428. package/dist/server.d.ts +42 -0
  429. package/dist/server.js +93 -0
  430. package/dist/styles.css +418 -149
  431. package/dist/tailwind-utilities.css +1 -1
  432. package/dist/tokens/themes/default-dark.js +15 -12
  433. package/dist/tokens/themes/default-light.js +14 -11
  434. package/dist/tokens/tokens.css +106 -23
  435. package/dist/ui/DataTable.style.css +4 -4
  436. package/dist/ui/columns/cell-renderers.d.ts +20 -0
  437. package/dist/ui/columns/cell-renderers.js +26 -1
  438. package/dist/ui/columns/index.d.ts +1 -1
  439. package/dist/ui/columns/index.js +4 -1
  440. package/dist/ui/components/Badge.style.css +1 -1
  441. package/dist/ui/components/Breadcrumb.js +19 -1
  442. package/dist/ui/components/BulkActionBar.style.css +4 -4
  443. package/dist/ui/components/Button.style.css +1 -1
  444. package/dist/ui/components/Checkbox.style.css +1 -1
  445. package/dist/ui/components/FallbackShell.js +1 -1
  446. package/dist/ui/components/Form.d.ts +14 -1
  447. package/dist/ui/components/Form.js +2 -2
  448. package/dist/ui/components/FormField.js +18 -4
  449. package/dist/ui/components/FormField.style.css +43 -3
  450. package/dist/ui/components/GatedPrice.style.css +1 -1
  451. package/dist/ui/components/IconButton.style.css +1 -1
  452. package/dist/ui/components/Input.js +22 -1
  453. package/dist/ui/components/Input.style.css +38 -22
  454. package/dist/ui/components/ListingFilterDrawer.js +1 -1
  455. package/dist/ui/components/ListingToolbar.js +1 -1
  456. package/dist/ui/components/PaginatedSelect.js +0 -0
  457. package/dist/ui/components/Radio.style.css +1 -1
  458. package/dist/ui/components/Repeater.d.ts +53 -0
  459. package/dist/ui/components/Repeater.js +7 -0
  460. package/dist/ui/components/RichTextEditor.d.ts +20 -1
  461. package/dist/ui/components/RichTextEditor.js +23 -2
  462. package/dist/ui/components/Select.style.css +7 -21
  463. package/dist/ui/components/Semantic.js +27 -3
  464. package/dist/ui/components/TagInput.style.css +4 -9
  465. package/dist/ui/components/Textarea.style.css +4 -18
  466. package/dist/ui/components/Toggle.style.css +1 -1
  467. package/dist/ui/components/Typography.d.ts +3 -3
  468. package/dist/ui/components/Typography.js +15 -15
  469. package/dist/ui/components/ViewToggle.style.css +4 -4
  470. package/dist/ui/forms/AddAnotherToggle.d.ts +16 -0
  471. package/dist/ui/forms/AddAnotherToggle.js +56 -0
  472. package/dist/ui/forms/ColorPickerField.js +15 -2
  473. package/dist/ui/forms/FieldCheckbox.js +15 -2
  474. package/dist/ui/forms/FieldInput.d.ts +22 -1
  475. package/dist/ui/forms/FieldInput.js +19 -5
  476. package/dist/ui/forms/FieldLabel.d.ts +35 -0
  477. package/dist/ui/forms/FieldLabel.js +70 -0
  478. package/dist/ui/forms/FieldSelect.d.ts +4 -1
  479. package/dist/ui/forms/FieldSelect.js +27 -5
  480. package/dist/ui/forms/FieldTextarea.d.ts +4 -1
  481. package/dist/ui/forms/FieldTextarea.js +22 -5
  482. package/dist/ui/forms/FormShell.d.ts +17 -1
  483. package/dist/ui/forms/FormShell.js +36 -2
  484. package/dist/ui/forms/FormShell.style.css +41 -33
  485. package/dist/ui/forms/index.d.ts +5 -1
  486. package/dist/ui/forms/index.js +2 -0
  487. package/dist/ui/icons/icon-registry.d.ts +1 -0
  488. package/dist/ui/icons/icon-registry.js +17 -1
  489. package/dist/utils/id-generators.d.ts +80 -0
  490. package/dist/utils/id-generators.js +93 -0
  491. package/dist/utils/search-txt-builders.d.ts +12 -0
  492. package/dist/utils/search-txt-builders.js +17 -0
  493. package/package.json +1 -1
  494. package/scripts/seed-cli.mjs +106 -5
  495. package/dist/features/loyalty/api/admin/loyalty/grant/route.d.ts +0 -12
  496. package/dist/features/loyalty/api/admin/loyalty/grant/route.js +0 -38
  497. package/dist/features/loyalty/api/balance/route.d.ts +0 -12
  498. package/dist/features/loyalty/api/balance/route.js +0 -41
  499. package/dist/features/loyalty/api/earn/route.d.ts +0 -12
  500. package/dist/features/loyalty/api/earn/route.js +0 -38
  501. package/dist/features/loyalty/api/history/route.d.ts +0 -12
  502. package/dist/features/loyalty/api/history/route.js +0 -58
  503. package/dist/features/loyalty/api/redeem/route.d.ts +0 -12
  504. package/dist/features/loyalty/api/redeem/route.js +0 -38
  505. package/dist/features/loyalty/columns/index.d.ts +0 -17
  506. package/dist/features/loyalty/columns/index.js +0 -40
  507. package/dist/features/loyalty/components/CoinsDisplay.d.ts +0 -12
  508. package/dist/features/loyalty/components/CoinsDisplay.js +0 -10
  509. package/dist/features/loyalty/components/index.d.ts +0 -1
  510. package/dist/features/loyalty/components/index.js +0 -1
  511. package/dist/features/loyalty/hooks/useLoyaltyBalance.d.ts +0 -4
  512. package/dist/features/loyalty/hooks/useLoyaltyBalance.js +0 -25
  513. package/dist/features/loyalty/index.d.ts +0 -6
  514. package/dist/features/loyalty/index.js +0 -6
  515. package/dist/features/loyalty/manifest.d.ts +0 -2
  516. package/dist/features/loyalty/manifest.js +0 -16
  517. package/dist/features/loyalty/messages/en.json +0 -19
  518. package/dist/features/loyalty/repository/loyalty.repository.d.ts +0 -8
  519. package/dist/features/loyalty/repository/loyalty.repository.js +0 -22
  520. package/dist/features/loyalty/schemas/index.d.ts +0 -85
  521. package/dist/features/loyalty/schemas/index.js +0 -40
  522. package/dist/features/loyalty/server.d.ts +0 -6
  523. package/dist/features/loyalty/server.js +0 -6
  524. package/dist/features/loyalty/types/index.d.ts +0 -31
  525. package/dist/features/loyalty/types/index.js +0 -1
  526. package/dist/features/loyalty/types/loyalty-config.d.ts +0 -7
  527. package/dist/features/loyalty/types/loyalty-config.js +0 -1
  528. package/dist/features/loyalty/types/loyalty-math.d.ts +0 -9
  529. package/dist/features/loyalty/types/loyalty-math.js +0 -27
  530. package/dist/features/products/components/ProductOwnerQuickActions.d.ts +0 -11
  531. package/dist/features/products/components/ProductOwnerQuickActions.js +0 -120
@@ -0,0 +1,168 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /*
4
+ * WHY: The box registry needs the same listing chrome every other admin
5
+ * surface has โ€” search, sort, pagination, a create affordance and a row
6
+ * you can open โ€” and `DataListingView` is that, for ~86 configs.
7
+ * WHAT: The config, plus the inline create drawer.
8
+ *
9
+ * @tag domain:inventory
10
+ * @tag layer:feature
11
+ * @tag access:client
12
+ */
13
+ import React from "react";
14
+ import { DataListingView, } from "../../admin/components/DataListingView.js";
15
+ import { Button, Div, Form, FieldInput, FieldSelect, Stack, Text } from "../../../ui/index.js";
16
+ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints.js";
17
+ import { apiClient } from "../../../http/index.js";
18
+ import { sortBy } from "../../../constants/sort.js";
19
+ import { sieveFilter, SIEVE_OP } from "../../../utils/sieve-builder.js";
20
+ import { operatorMessage } from "./operator-message.js";
21
+ import { BoxTypeValues } from "../schemas/firestore.js";
22
+ const TYPE_OPTIONS = BoxTypeValues.map((v) => ({ value: v, label: v }));
23
+ /**
24
+ * The register-a-box form.
25
+ *
26
+ * Exported because it is a PAGE now, not a drawer โ€” see the note on
27
+ * `primaryAction` below for why create could not share the row affordance.
28
+ */
29
+ export function RegisterBoxForm({ onCreated }) {
30
+ const [code, setCode] = React.useState("");
31
+ const [boxType, setBoxType] = React.useState(BoxTypeValues[0]);
32
+ const [location, setLocation] = React.useState("");
33
+ const [busy, setBusy] = React.useState(false);
34
+ return (_jsx(Form, { onSubmit: (e) => e.preventDefault(), className: "space-y-4", children: ({ setFieldError, clearErrors }) => (_jsxs(Stack, { gap: "md", children: [_jsx(FieldInput, { name: "code", label: "Box code", required: true, value: code, onChange: setCode, placeholder: "AC60-03", hint: "Exactly what is written on the physical label." }), _jsx(FieldSelect, { name: "boxType", label: "Box type", value: boxType, onChange: setBoxType, options: TYPE_OPTIONS }), _jsx(FieldInput, { name: "location", label: "Where it is", value: location, onChange: setLocation, placeholder: "Shelf B, second from top" }), _jsx(Text, { size: "xs", color: "muted", children: "A second box with the same code is the same box \u2014 the registry refuses a duplicate rather than merging two." }), _jsx(Div, { children: _jsx(Button, { type: "submit", variant: "primary", isLoading: busy, onClick: async () => {
35
+ clearErrors();
36
+ if (!code.trim()) {
37
+ setFieldError("code", "A box needs a code");
38
+ return;
39
+ }
40
+ setBusy(true);
41
+ try {
42
+ await apiClient.post(ADMIN_ENDPOINTS.INVENTORY_BOXES, {
43
+ code: code.trim(),
44
+ boxType,
45
+ ...(location.trim() ? { location: location.trim() } : {}),
46
+ });
47
+ onCreated();
48
+ }
49
+ catch (err) {
50
+ /*
51
+ * ๐Ÿ›‘ `operatorMessage`, not a raw fallback. This read
52
+ * `toUserMessage(undefined, undefined, { fallback: e.message })`
53
+ * under a comment claiming it never showed server text โ€”
54
+ * which is false twice over: `undefined` for the code means
55
+ * the map never runs, so the fallback is ALWAYS what shows.
56
+ * A comment asserting the fix while the code reintroduces the
57
+ * bug is how Root Cause #86 would come back, and how the seed
58
+ * `listingType` bug survived its own fix one field over.
59
+ *
60
+ * The split is on status: the 409 here ("Box AC60-03 already
61
+ * exists") is authored for this operator and must survive; a
62
+ * 5xx terminates in a constant.
63
+ */
64
+ setFieldError("code", operatorMessage(err));
65
+ }
66
+ finally {
67
+ setBusy(false);
68
+ }
69
+ }, children: "Register box" }) })] })) }));
70
+ }
71
+ export function AdminStorageBoxesView() {
72
+ const config = React.useMemo(() => ({
73
+ portal: "admin",
74
+ title: "Storage Boxes",
75
+ subtitle: "The physical AC60 and AC30 boxes, and what is in each one. A box is here because somebody labelled it.",
76
+ /*
77
+ * `search.fields` names what the index actually matches โ€” `searchTxt` is
78
+ * built from the code, the type and the location, and nothing else. A
79
+ * placeholder promising more is the empty-list lie the admin products
80
+ * toolbar was making about SKUs.
81
+ */
82
+ search: {
83
+ placeholder: "Search by code or location",
84
+ fields: ["code", "boxType", "location"],
85
+ },
86
+ emptyLabel: "No boxes registered yet",
87
+ filterKeys: ["status", "boxType"],
88
+ /*
89
+ * ๐Ÿ›‘ REQUIRED โ€” see the long note in `AdminMoveLedgerView`. Its absence
90
+ * threw `TypeError: buildFilters is not a function` during render, so
91
+ * `/admin/inventory/boxes` never painted.
92
+ *
93
+ * Joined with "," because sievejs reads a comma as AND: status AND
94
+ * boxType, which is what two independent chip groups mean. A "|" would
95
+ * be an OR-group on ONE field and is what `sieveMultiEq` is for โ€”
96
+ * getting that backwards is how a filter silently widens instead of
97
+ * narrowing.
98
+ */
99
+ buildFilters: (state) => {
100
+ const parts = [];
101
+ if (state.status)
102
+ parts.push(sieveFilter("status", SIEVE_OP.EQ, state.status));
103
+ if (state.boxType)
104
+ parts.push(sieveFilter("boxType", SIEVE_OP.EQ, state.boxType));
105
+ return parts.join(",") || undefined;
106
+ },
107
+ /*
108
+ * Sorted by `ordinal`, not `code`. A string sort puts AC60-10 before
109
+ * AC60-2 โ€” the kind of ordering that makes an operator distrust the
110
+ * screen โ€” which is why `ordinal` is denormalised on write.
111
+ */
112
+ defaultSort: "ordinal",
113
+ sortOptions: [
114
+ { value: "ordinal", label: "Box number" },
115
+ { value: sortBy("createdAt", "DESC"), label: "Newest" },
116
+ { value: sortBy("code", "ASC"), label: "Code Aโ€“Z" },
117
+ ],
118
+ queryKey: ["admin", "inventory", "boxes"],
119
+ endpoint: ADMIN_ENDPOINTS.INVENTORY_BOXES,
120
+ mapRows: (response) => (response.items ?? []).map((box) => ({
121
+ id: box.id,
122
+ primary: box.code,
123
+ secondary: [
124
+ box.boxType,
125
+ box.location,
126
+ /*
127
+ * The ADVISORY rollup, labelled as of its own timestamp rather
128
+ * than presented as live. A scheduled job writes it, so a box
129
+ * that changed an hour ago reads stale โ€” saying "as of" is the
130
+ * difference between a cache and a lie.
131
+ */
132
+ box.rollup
133
+ ? `${box.rollup.totalUnits} units in ${box.rollup.distinctProducts} products`
134
+ : "not yet counted",
135
+ ]
136
+ .filter(Boolean)
137
+ .join(" ยท "),
138
+ status: box.status,
139
+ updatedAt: box.updatedAt ? String(box.updatedAt) : "",
140
+ })),
141
+ /*
142
+ * A real detail page, so `DataListingView` gives every row the direct
143
+ * edit affordance it composes โ€” and a middle-clickable link, which is
144
+ * how an operator opens three boxes to compare them.
145
+ */
146
+ rowHrefTemplate: "/admin/inventory/boxes/{id}",
147
+ /*
148
+ * ๐Ÿ›‘ `href`, NOT `renderEditor`, and the two are mutually exclusive
149
+ * here for a reason I nearly got wrong.
150
+ *
151
+ * `DataListingView` resolves its row affordances in the order
152
+ * `onRowClick` โ†’ `renderEditor` โ†’ `rowHrefTemplate`. So a config
153
+ * carrying BOTH a create drawer and a row href gets the drawer on every
154
+ * row: the pencil calls `openEditPanel(row.id)` and a create form that
155
+ * ignores the id opens, presented as an edit of that box. A create form
156
+ * reached through an edit affordance is the clickable-card lie in a new
157
+ * shape.
158
+ *
159
+ * So create is a PAGE (`primaryAction.href`, which exists for exactly
160
+ * this per the config's own docs) and the row href is the detail page.
161
+ * Registering a box is also not a one-field drawer job โ€” it is the
162
+ * moment the code is chosen, and the code is immutable afterwards
163
+ * because the document id derives from it.
164
+ */
165
+ primaryAction: { label: "Register box", href: "/admin/inventory/boxes/new" },
166
+ }), []);
167
+ return _jsx(DataListingView, { config: config });
168
+ }
@@ -0,0 +1,39 @@
1
+ /** A shipment item that can actually be received โ€” i.e. one that is linked. */
2
+ export interface ReceivableItem {
3
+ itemId: string;
4
+ productId: string;
5
+ productTitle: string;
6
+ /**
7
+ * ๐Ÿ›‘ THE MACHINE KEY, and what a scan actually produces. `id-generators.ts`'s
8
+ * reconciliation header is explicit that `barcodeId` is "what a CODE128
9
+ * symbol encodes and what a scan resolves" while `sku` is the human key โ€”
10
+ * so a station indexed on `sku` alone would fail to recognise its own
11
+ * printed labels. I built it that way first.
12
+ */
13
+ barcodeId: string | null;
14
+ /** The human key. Accepted too, because an operator reads and types this. */
15
+ sku: string | null;
16
+ /** What the shipment says arrived, for the operator to count against. */
17
+ expectedQuantity: number;
18
+ }
19
+ /** An item with no `linkedProductId` โ€” nameable, but not receivable. */
20
+ export interface UnlinkedItem {
21
+ itemId: string;
22
+ lotId: string;
23
+ title: string;
24
+ }
25
+ export interface BoxOption {
26
+ id: string;
27
+ code: string;
28
+ location: string | null;
29
+ }
30
+ export interface ReceiveStationViewProps {
31
+ shipmentId: string;
32
+ shipmentNumber: string;
33
+ items: ReceivableItem[];
34
+ unlinked: UnlinkedItem[];
35
+ boxes: BoxOption[];
36
+ itemsFailed: boolean;
37
+ boxesFailed: boolean;
38
+ }
39
+ export declare function ReceiveStationView({ shipmentId, shipmentNumber, items, unlinked, boxes, itemsFailed, boxesFailed, }: ReceiveStationViewProps): import("react").JSX.Element;
@@ -0,0 +1,413 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * The receive station โ€” a scan loop that ends in one commit.
5
+ *
6
+ * A crate arrives, someone opens it, and for the next twenty minutes their
7
+ * hands are full and their eyes are on the item, not the screen. Everything
8
+ * here follows from that.
9
+ *
10
+ * THE LOOP: scan `BOX-AC60-03` โ†’ that box is ARMED
11
+ * scan `LIR-DRAN-4F2A1` โ†’ one unit of that product staged into it
12
+ * scan it again โ†’ the same row increments
13
+ * scan another `BOX-โ€ฆ` โ†’ the armed box changes, rows keep theirs
14
+ * press Commit โ†’ the whole table lands in one transaction
15
+ *
16
+ * ๐Ÿ›‘ ARMING IS WHAT MAKES THIS WORTH BUILDING. Thirty items into one box is
17
+ * thirty-one scans, not sixty. A station that asked for the box every time
18
+ * would be slower than a clipboard, which is what it is replacing.
19
+ *
20
+ * ๐Ÿ›‘ RECEIVING NEVER TOUCHES `stockQuantity`. The shipment-link route already
21
+ * does, and doing both double-counts. This answers "where is it", never "how
22
+ * many exist".
23
+ */
24
+ import { useEffect, useMemo, useRef, useState } from "react";
25
+ import { Alert, Badge, Button, Card, CardBody, Div, Heading, Row, Stack, Table, Tbody, Td, Text, TextLink, Th, Thead, Tr, } from "../../../ui/index.js";
26
+ import { QuantityStepper } from "../../../ui/components/QuantityStepper.js";
27
+ import { FieldInput } from "../../../ui/forms/index.js";
28
+ import { Form } from "../../../ui/components/Form.js";
29
+ import { useToast } from "../../../ui/components/Toast.js";
30
+ import { apiClient } from "../../../http/index.js";
31
+ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints.js";
32
+ import { ROUTES } from "../../../next/routing/route-map.js";
33
+ import { operatorMessage } from "./operator-message.js";
34
+ import { MAX_RECEIVE_ROWS } from "../schemas/firestore.js";
35
+ /** `boxId::productId` โ€” the staging table's natural key, mirroring `placementId`. */
36
+ function rowKey(boxId, productId) {
37
+ return `${boxId}::${productId}`;
38
+ }
39
+ export function ReceiveStationView({ shipmentId, shipmentNumber, items, unlinked, boxes, itemsFailed, boxesFailed, }) {
40
+ const { showToast } = useToast();
41
+ const [armed, setArmed] = useState(null);
42
+ const [rows, setRows] = useState({});
43
+ /*
44
+ * ๐Ÿ›‘ THE REF IS THE AUTHORITY; `rows` only renders.
45
+ *
46
+ * Two reasons, and the second is a correctness bug I wrote before noticing.
47
+ *
48
+ * 1. A scanner types faster than React re-renders. Two `LIR-โ€ฆ` scans can
49
+ * both submit before a single render commits, and a handler reading the
50
+ * state `rows` from its closure would see the pre-first-scan value for
51
+ * both โ€” the second overwrites the first and one item silently vanishes
52
+ * from the table while the operator's echo says it landed twice.
53
+ *
54
+ * 2. A `setRows(prev => โ€ฆ)` updater does NOT run synchronously. It is
55
+ * evaluated during the next render, so `stage()` cannot learn from it
56
+ * whether it actually staged anything โ€” a flag mutated inside the updater
57
+ * is still `false` when `stage()` returns, which made every refused scan
58
+ * report success.
59
+ *
60
+ * Writing the ref first and passing a plain value to `setRows` makes both
61
+ * problems go away: the decision is always against the real current table.
62
+ */
63
+ const rowsRef = useRef({});
64
+ /** Mutates the authoritative table and schedules the render. */
65
+ function applyRows(next) {
66
+ const computed = next(rowsRef.current);
67
+ rowsRef.current = computed;
68
+ setRows(computed);
69
+ }
70
+ const [scan, setScan] = useState("");
71
+ const [committing, setCommitting] = useState(false);
72
+ /** The in-flight guard. State renders the spinner; this prevents the second send. */
73
+ const committingRef = useRef(false);
74
+ /*
75
+ * The last scan, echoed large. A toast per scan would be thirty toasts
76
+ * stacked over the table, and the operator is not reading them โ€” they need
77
+ * one glance to confirm the beep meant something.
78
+ */
79
+ const [lastScan, setLastScan] = useState(null);
80
+ const scanRef = useRef(null);
81
+ /*
82
+ * ONE index, keyed by both codes.
83
+ *
84
+ * `barcodeId` is what the symbol on the label encodes; `sku` is what is
85
+ * printed beneath it in human-readable text. An operator with a peeled or
86
+ * smudged label types the SKU, so refusing it would make the station useless
87
+ * for exactly the items most likely to need typing. Both resolve to the same
88
+ * product, so accepting both costs nothing and the ambiguity is only
89
+ * apparent โ€” the two formats cannot collide (`LIR-XXXXXXXX`, two segments
90
+ * of fixed width, versus `LIR-TOKEN-SUFFIX`, three).
91
+ */
92
+ const byCode = useMemo(() => {
93
+ const m = new Map();
94
+ for (const it of items) {
95
+ if (it.barcodeId)
96
+ m.set(it.barcodeId.toUpperCase(), it);
97
+ if (it.sku)
98
+ m.set(it.sku.toUpperCase(), it);
99
+ }
100
+ return m;
101
+ }, [items]);
102
+ /*
103
+ * ๐Ÿ›‘ Box codes are matched WITH AND WITHOUT the `BOX-` prefix.
104
+ *
105
+ * The stored `code` is `AC60-03` (`generateStorageBoxId` slugifies exactly
106
+ * that) while the printed label's symbol encodes `BOX-AC60-03` โ€” the prefix
107
+ * exists so the station knows what KIND of thing it just read without a
108
+ * lookup. Indexing only the stored spelling would mean the station does not
109
+ * recognise the labels this project prints, which is the same mistake as
110
+ * indexing items on `sku`.
111
+ */
112
+ const byBoxCode = useMemo(() => {
113
+ const m = new Map();
114
+ for (const b of boxes) {
115
+ const code = b.code.toUpperCase();
116
+ m.set(code, b);
117
+ m.set(`BOX-${code}`, b);
118
+ }
119
+ return m;
120
+ }, [boxes]);
121
+ const staged = Object.values(rows);
122
+ const atCap = staged.length >= MAX_RECEIVE_ROWS;
123
+ const totalUnits = staged.reduce((n, r) => n + r.quantity, 0);
124
+ /*
125
+ * ๐Ÿ›‘ THE FIELD IS RE-FOCUSED AFTER EVERY SCAN AND EVERY ROW EDIT.
126
+ *
127
+ * A barcode scanner is a keyboard that types very fast and presses Enter.
128
+ * If focus has moved โ€” because the operator nudged a stepper, or a toast
129
+ * took it, or they clicked the table โ€” the barcode goes wherever focus is:
130
+ * into a quantity box as digits, or nowhere at all. Nothing on screen says
131
+ * so, and the operator has already put the item in the box. That is the
132
+ * failure this effect exists to prevent, and it is why the dependency list
133
+ * includes the row count.
134
+ */
135
+ useEffect(() => {
136
+ scanRef.current?.focus();
137
+ }, [lastScan, staged.length, armed?.id]);
138
+ /**
139
+ * Stages one unit, and REPORTS WHETHER IT DID.
140
+ *
141
+ * ๐Ÿ›‘ THE RETURN VALUE IS THE POINT. This used to return void and bail with a
142
+ * bare `return prev` at the cap, while every caller announced "+1 โ€ฆ" the
143
+ * moment it had called โ€” so a refused scan rendered as a successful one. At
144
+ * a station where the operator trusts the echo instead of re-reading the
145
+ * table, that is the single worst bug this screen could have: the item is
146
+ * physically in the box and the record says it is not.
147
+ *
148
+ * The cap is checked against `rowsRef`, never the caller's `atCap`: that
149
+ * value comes from the last render and a scanner fires two submits before
150
+ * React commits one, so it is routinely a row stale. The ref is the only
151
+ * authority. `atCap` survives solely to render the informational notice,
152
+ * where being one render behind costs nothing.
153
+ */
154
+ function stage(item, box, by = 1) {
155
+ const key = rowKey(box.id, item.productId);
156
+ const current = rowsRef.current;
157
+ const existing = current[key];
158
+ /*
159
+ * ๐Ÿ›‘ A REPEAT SCAN INCREMENTS; it does not add a second row. Twenty scans
160
+ * of one product would otherwise be twenty rows, and the 50-row
161
+ * transaction ceiling would be hit by a single item while the operator
162
+ * thinks they have barely started. An increment is therefore allowed even
163
+ * at the cap โ€” it adds no row.
164
+ */
165
+ if (existing) {
166
+ applyRows((c) => ({
167
+ ...c,
168
+ [key]: { ...existing, quantity: existing.quantity + by },
169
+ }));
170
+ return true;
171
+ }
172
+ // A NEW row at the cap is refused, and the caller is told so.
173
+ if (Object.keys(current).length >= MAX_RECEIVE_ROWS)
174
+ return false;
175
+ applyRows((c) => ({
176
+ ...c,
177
+ [key]: {
178
+ boxId: box.id,
179
+ boxCode: box.code,
180
+ productId: item.productId,
181
+ productTitle: item.productTitle,
182
+ code: item.barcodeId ?? item.sku,
183
+ quantity: by,
184
+ },
185
+ }));
186
+ return true;
187
+ }
188
+ /**
189
+ * A box the prefetched list does not contain, resolved from the server.
190
+ *
191
+ * ๐Ÿ›‘ THIS EXISTS BECAUSE THE PREFETCHED LIST IS CAPPED AT 50 (Rule #6 โ€” every
192
+ * list endpoint paginates). Without this, registering a 51st box produces a
193
+ * station that reads its label, finds nothing, and says "unrecognised code"
194
+ * about a box that plainly exists โ€” the silent-ceiling failure this codebase
195
+ * keeps paying for, where a bounded read is mistaken for the whole set.
196
+ *
197
+ * It runs only on a miss, so the common case stays instant and a box-arm is
198
+ * roughly one scan in twenty anyway.
199
+ */
200
+ async function resolveBoxFromServer(code) {
201
+ const bare = code.startsWith("BOX-") ? code.slice(4) : code;
202
+ try {
203
+ const res = await apiClient.get(`${ADMIN_ENDPOINTS.INVENTORY_BOXES}?filters=code==${encodeURIComponent(bare)}&pageSize=1`);
204
+ const hit = res?.items?.[0];
205
+ return hit
206
+ ? { id: hit.id, code: hit.code, location: hit.location ?? null }
207
+ : null;
208
+ }
209
+ catch {
210
+ // A failed lookup must not masquerade as "no such box" โ€” the caller
211
+ // distinguishes the two in what it tells the operator.
212
+ return null;
213
+ }
214
+ }
215
+ async function handleScan(raw) {
216
+ const code = raw.trim().toUpperCase();
217
+ setScan("");
218
+ if (!code)
219
+ return;
220
+ const box = byBoxCode.get(code);
221
+ if (box) {
222
+ setArmed(box);
223
+ setLastScan({ text: `Armed ${box.code}`, tone: "ok" });
224
+ return;
225
+ }
226
+ /*
227
+ * A `BOX-` prefix is an unambiguous declaration of intent, so a miss here
228
+ * is worth a round trip rather than an "unrecognised code" โ€” the operator
229
+ * is holding a box with a label this project printed.
230
+ */
231
+ if (code.startsWith("BOX-")) {
232
+ const fetched = await resolveBoxFromServer(code);
233
+ if (fetched) {
234
+ setArmed(fetched);
235
+ setLastScan({ text: `Armed ${fetched.code}`, tone: "ok" });
236
+ return;
237
+ }
238
+ setLastScan({
239
+ text: `No box registered as ${code.slice(4)} โ€” register it first`,
240
+ tone: "bad",
241
+ });
242
+ return;
243
+ }
244
+ const item = byCode.get(code);
245
+ if (item) {
246
+ if (!armed) {
247
+ /*
248
+ * ๐Ÿ›‘ REFUSED, not staged into a guessed box. Defaulting to "the last
249
+ * box" or "the only box" would put stock somewhere nobody chose, and
250
+ * a placement is only worth having if it is where the thing actually
251
+ * is. The operator loses one scan; a wrong shelf loses the item.
252
+ */
253
+ setLastScan({
254
+ text: `No box armed โ€” scan a box code before ${item.productTitle}`,
255
+ tone: "bad",
256
+ });
257
+ return;
258
+ }
259
+ /*
260
+ * ๐Ÿ›‘ THE ECHO FOLLOWS WHAT `stage` ACTUALLY DID. There is no pre-check
261
+ * against `atCap` here any more: that value comes from the last render
262
+ * and a scanner outruns it, so a scan could be refused by `stage`'s
263
+ * authoritative check and still echo "+1". `stage` owns the decision
264
+ * and reports it; this only phrases it.
265
+ */
266
+ if (stage(item, armed)) {
267
+ setLastScan({
268
+ text: `+1 ${item.productTitle} โ†’ ${armed.code}`,
269
+ tone: "ok",
270
+ });
271
+ }
272
+ else {
273
+ setLastScan({
274
+ text: `Table is full at ${MAX_RECEIVE_ROWS} rows โ€” commit, then carry on`,
275
+ tone: "bad",
276
+ });
277
+ }
278
+ return;
279
+ }
280
+ /*
281
+ * ๐Ÿ›‘ AN UNKNOWN CODE IS AN ERROR, NEVER A NO-OP. Clearing the field and
282
+ * saying nothing is the worst available behaviour: the operator hears the
283
+ * scanner beep, sees the field empty, and assumes it counted. This is the
284
+ * one case that also gets a toast, because it is rare and does need eyes.
285
+ */
286
+ setLastScan({ text: `Unrecognised code: ${code}`, tone: "bad" });
287
+ showToast(`${code} is not a box code or a SKU in this shipment. Check the label, or add the item by hand below.`, "error");
288
+ }
289
+ async function commit() {
290
+ /*
291
+ * ๐Ÿ›‘ SENT FROM THE REF, not the rendered `staged`. Pressing Commit
292
+ * immediately after the last scan โ€” which is exactly what an operator
293
+ * does โ€” can run this handler before the render carrying that scan has
294
+ * committed, and the rendered array would then be one row short. The
295
+ * dropped row is the last thing scanned, so it is also the one the
296
+ * operator remembers putting in the box.
297
+ */
298
+ /*
299
+ * ๐Ÿ›‘ A REF GUARD, even though `<Button isLoading>` already sets
300
+ * `disabled` and refuses its own click handler. Both of those read the
301
+ * `committing` STATE, which is one render stale โ€” and the cost of the gap
302
+ * here is not a wasted request, it is the entire crate received twice,
303
+ * with a second ledger row per item saying it arrived again. Two lines
304
+ * against a double-counted shipment is not a close call.
305
+ */
306
+ if (committingRef.current)
307
+ return;
308
+ const sending = Object.values(rowsRef.current);
309
+ if (sending.length === 0)
310
+ return;
311
+ committingRef.current = true;
312
+ setCommitting(true);
313
+ try {
314
+ const res = await apiClient.post(ADMIN_ENDPOINTS.INVENTORY_RECEIVE, {
315
+ shipmentId,
316
+ rows: sending.map((r) => ({
317
+ boxId: r.boxId,
318
+ productId: r.productId,
319
+ quantity: r.quantity,
320
+ })),
321
+ });
322
+ /*
323
+ * The SERVER's counts are quoted when it returns them. It is the only
324
+ * party that knows what actually landed, and `sending` is merely what we
325
+ * asked for โ€” reporting our own request back as a result is how a UI
326
+ * ends up confidently describing a write that differed.
327
+ */
328
+ const units = sending.reduce((n, r) => n + r.quantity, 0);
329
+ showToast(`Received ${res?.units ?? units} units across ${res?.rows ?? sending.length} rows`, "success");
330
+ /*
331
+ * ๐Ÿ›‘ ONLY THE SENT ROWS ARE REMOVED โ€” not the whole table.
332
+ *
333
+ * The scan field keeps focus through the request, so the operator
334
+ * carries on scanning while it is in flight; that is the designed
335
+ * behaviour, not an edge case. A blanket clear would throw those scans
336
+ * away, and they are the ones nobody can reconstruct โ€” the items are
337
+ * already in the boxes and the echo said they counted.
338
+ *
339
+ * The quantity is compared, not just the key: a row scanned again
340
+ * mid-flight has a HIGHER quantity than what was sent, so the delta has
341
+ * to survive. Subtracting what was sent and keeping the remainder is the
342
+ * only version that neither loses nor double-counts.
343
+ *
344
+ * Cleared only on success. A failed commit keeps the table exactly as it
345
+ * was โ€” the server refused as a unit, so nothing landed.
346
+ */
347
+ applyRows((c) => {
348
+ const next = { ...c };
349
+ for (const r of sending) {
350
+ const key = rowKey(r.boxId, r.productId);
351
+ const live = next[key];
352
+ if (!live)
353
+ continue;
354
+ const remainder = live.quantity - r.quantity;
355
+ if (remainder > 0)
356
+ next[key] = { ...live, quantity: remainder };
357
+ else
358
+ delete next[key];
359
+ }
360
+ return next;
361
+ });
362
+ setLastScan({ text: "Committed. Carry on.", tone: "ok" });
363
+ }
364
+ catch (err) {
365
+ showToast(operatorMessage(err), "error");
366
+ }
367
+ finally {
368
+ committingRef.current = false;
369
+ setCommitting(false);
370
+ }
371
+ }
372
+ return (_jsxs(Stack, { gap: "comfortable", children: [_jsxs(Div, { children: [_jsx(Heading, { level: 1, size: "2xl", children: "Receive into boxes" }), _jsxs(Text, { color: "muted", children: ["Shipment ", shipmentNumber, ". Scan a box code to arm it, then scan items."] })] }), boxesFailed && (_jsx(Alert, { variant: "warning", children: "Could not load the box list. Scanning a box code will not arm anything until this page is reloaded \u2014 the list is unknown, not empty." })), itemsFailed && (_jsx(Alert, { variant: "warning", children: "Could not load this shipment's items. Scanning a SKU will not match anything until this page is reloaded." })), !boxesFailed && boxes.length === 0 && (_jsx(Alert, { variant: "warning", children: "There are no storage boxes registered yet. Register one before receiving \u2014 stock has to go somewhere named." })), _jsx(Card, { variant: armed ? "default" : "outlined", children: _jsx(CardBody, { children: _jsxs(Row, { align: "center", justify: "between", gap: "md", wrap: true, children: [_jsxs(Div, { children: [_jsx(Text, { size: "xs", color: "muted", transform: "uppercase", children: "Armed box" }), armed ? (_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Heading, { level: 2, size: "2xl", children: armed.code }), armed.location && (_jsx(Badge, { variant: "info", children: armed.location }))] })) : (_jsx(Heading, { level: 2, size: "xl", color: "muted", children: "Nothing armed \u2014 scan a box code" }))] }), armed && (_jsx(Button, { variant: "ghost", onClick: () => setArmed(null), children: "Disarm" }))] }) }) }), _jsx(Card, { variant: "default", children: _jsxs(CardBody, { children: [_jsx(Form, { onSubmit: (e) => {
373
+ e.preventDefault();
374
+ void handleScan(scan);
375
+ }, children: _jsx(FieldInput, { name: "scan", label: "Scan",
376
+ // `inputRef`, not `ref` โ€” see FieldInput's own note on why the
377
+ // inner control is named rather than implied.
378
+ inputRef: scanRef, value: scan, onChange: setScan, autoComplete: "off", placeholder: "BOX-\u2026 to arm a box \u00B7 LIR-\u2026 to add an item", hint: armed
379
+ ? `Items scanned now go into ${armed.code}`
380
+ : "Scan a box code first" }) }), lastScan && (_jsx(Div, { children: _jsx(Text, { size: "lg", weight: "semibold", color: lastScan.tone === "ok" ? "success" : "error", role: "status", "aria-live": "polite", children: lastScan.text }) }))] }) }), _jsx(Card, { variant: "default", children: _jsxs(CardBody, { children: [_jsxs(Row, { align: "center", justify: "between", wrap: true, gap: "sm", children: [_jsxs(Heading, { level: 2, size: "lg", children: ["Staged (", staged.length, "/", MAX_RECEIVE_ROWS, ")"] }), _jsxs(Row, { align: "center", gap: "sm", children: [_jsxs(Text, { size: "sm", color: "muted", children: [totalUnits, " unit", totalUnits === 1 ? "" : "s"] }), _jsxs(Button, { variant: "primary", isLoading: committing, disabled: staged.length === 0, onClick: () => void commit(), children: ["Commit ", staged.length > 0 ? `${staged.length} rows` : ""] })] })] }), atCap && (_jsxs(Alert, { variant: "info", children: [MAX_RECEIVE_ROWS, " rows is one transaction's worth. Commit these, then keep scanning \u2014 a bigger table would have to be written in pieces, and a half-received crate is worse than two commits."] })), staged.length === 0 ? (_jsx(Text, { color: "muted", children: "Nothing staged. Scan a box, then scan items into it." })) : (_jsxs(Table, { children: [_jsx(Thead, { children: _jsxs(Tr, { children: [_jsx(Th, { children: "Box" }), _jsx(Th, { children: "Product" }), _jsx(Th, { children: "Code" }), _jsx(Th, { children: "Qty" }), _jsx(Th, { children: "Remove" })] }) }), _jsx(Tbody, { children: staged.map((r) => {
381
+ const key = rowKey(r.boxId, r.productId);
382
+ return (_jsxs(Tr, { children: [_jsx(Td, { children: _jsx(Badge, { variant: "info", children: r.boxCode }) }), _jsx(Td, { children: r.productTitle }), _jsx(Td, { children: _jsx(Text, { numeric: true, size: "sm", children: r.code ?? "โ€”" }) }), _jsx(Td, { children: _jsx(QuantityStepper, { value: r.quantity, min: 1, max: 9999,
383
+ // Many steppers on one page, so a bare "Quantity"
384
+ // names none of them.
385
+ ariaLabel: `Quantity of ${r.productTitle} for ${r.boxCode}`, onChange: (n) => applyRows((c) => ({
386
+ ...c,
387
+ [key]: { ...c[key], quantity: n },
388
+ })) }) }), _jsx(Td, { children: _jsx(Button, { variant: "ghost", onClick: () => applyRows((c) => {
389
+ const next = { ...c };
390
+ delete next[key];
391
+ return next;
392
+ }), children: "Remove" }) })] }, key));
393
+ }) })] }))] }) }), _jsx(Card, { variant: "default", children: _jsxs(CardBody, { children: [_jsx(Heading, { level: 2, size: "lg", children: "Add by hand" }), items.length === 0 ? (_jsx(Text, { color: "muted", children: "No linked items on this shipment yet." })) : (_jsxs(Table, { children: [_jsx(Thead, { children: _jsxs(Tr, { children: [_jsx(Th, { children: "Item" }), _jsx(Th, { children: "Scan code" }), _jsx(Th, { children: "Expected" }), _jsx(Th, { children: "Add" })] }) }), _jsx(Tbody, { children: items.map((it) => (_jsxs(Tr, { children: [_jsx(Td, { children: it.productTitle }), _jsx(Td, { children: _jsx(Text, { numeric: true, size: "sm", children: it.barcodeId ??
394
+ it.sku ??
395
+ "no code yet โ€” add by hand, or run the SKU backfill" }) }), _jsx(Td, { children: it.expectedQuantity }), _jsx(Td, { children: _jsx(Button, { variant: "outline",
396
+ // Disabled only for the one condition that is stable
397
+ // between renders. The cap is `stage`'s call, and it
398
+ // reports โ€” the same reasoning as the scan path.
399
+ disabled: !armed, onClick: () => {
400
+ if (!armed)
401
+ return;
402
+ const ok = stage(it, armed);
403
+ setLastScan(ok
404
+ ? {
405
+ text: `+1 ${it.productTitle} โ†’ ${armed.code}`,
406
+ tone: "ok",
407
+ }
408
+ : {
409
+ text: `Table is full at ${MAX_RECEIVE_ROWS} rows โ€” commit first`,
410
+ tone: "bad",
411
+ });
412
+ }, children: armed ? `Into ${armed.code}` : "Arm a box first" }) })] }, it.itemId))) })] }))] }) }), unlinked.length > 0 && (_jsxs(Alert, { variant: "warning", children: [_jsxs(Text, { weight: "semibold", children: [unlinked.length, " item", unlinked.length === 1 ? "" : "s", " cannot be received yet"] }), _jsx(Text, { size: "sm", children: "A box placement records a product, so each of these needs linking to a listing first:" }), _jsx(Stack, { gap: "xs", children: unlinked.map((u) => (_jsxs(Text, { size: "sm", children: ["\u00B7 ", u.title, " \u2014", " ", _jsx(TextLink, { href: String(ROUTES.ADMIN.SHIPMENT_LOT_ITEMS(shipmentId, u.lotId)), children: "link it in its lot" })] }, u.itemId))) })] }))] }));
413
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * A failed inventory write, as something an operator can act on.
3
+ *
4
+ * ๐Ÿ›‘ THIS EXISTS BECAUSE `toUserMessage(undefined, undefined, { fallback })`
5
+ * IS THE `?? error` ANTI-PATTERN. Passing `undefined` for the code means the
6
+ * mapping never runs and the server's own message is always what shows โ€” which
7
+ * is precisely the bug Rule #9 records: an unmapped failure printed server
8
+ * internals to the user, and a Node `MODULE_NOT_FOUND` with a require stack was
9
+ * rendered inside a modal. I wrote that call before noticing.
10
+ *
11
+ * But the inventory screens genuinely need the server's words for the 4xx
12
+ * cases, and that is not a contradiction. "AC60-03 is retired", "holds 2,
13
+ * cannot move 5" and "duplicate box code" are messages written FOR the
14
+ * operator, naming the box and the count, and they tell them what to do next.
15
+ * `GENERIC_USER_MESSAGE` in their place turns a recoverable mistake into a
16
+ * mystery at a station where someone is holding the physical item.
17
+ *
18
+ * So the rule is split on STATUS, which is the thing that actually
19
+ * distinguishes the two:
20
+ *
21
+ * ยท 4xx โ€” deliberate, authored, operator-facing. Show it.
22
+ * ยท 5xx (and anything with no status) โ€” unplanned. Route through the code map,
23
+ * which terminates in a constant and never returns server text. `mapToHttpError`
24
+ * also scrubs 5xx messages in production, so the server text may well already
25
+ * be generic by the time it arrives โ€” but relying on that would make this
26
+ * correct only in production, and the station is also used against a local
27
+ * server.
28
+ */
29
+ export declare function operatorMessage(err: unknown, t?: (key: string) => string): string;