@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.
- package/dist/.build-stamp.json +3 -3
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +62 -5
- package/dist/_internal/client/features/maintenance/views/MaintenanceDashboardView.js +2 -2
- package/dist/_internal/server/features/brands/actions.js +78 -4
- package/dist/_internal/server/features/checkout/actions.d.ts +36 -13
- package/dist/_internal/server/features/checkout/actions.js +668 -183
- package/dist/_internal/server/features/checkout/supplier-identity.js +18 -1
- package/dist/_internal/server/features/gateway-events/index.d.ts +10 -0
- package/dist/_internal/server/features/gateway-events/index.js +10 -0
- package/dist/_internal/server/features/gateway-events/ingest.d.ts +127 -0
- package/dist/_internal/server/features/gateway-events/ingest.js +188 -0
- package/dist/_internal/server/features/gateway-events/redact.d.ts +66 -0
- package/dist/_internal/server/features/gateway-events/redact.js +151 -0
- package/dist/_internal/server/features/grievance/actions.d.ts +63 -0
- package/dist/_internal/server/features/grievance/actions.js +173 -0
- package/dist/_internal/server/features/grievance/index.d.ts +8 -0
- package/dist/_internal/server/features/grievance/index.js +8 -0
- package/dist/_internal/server/features/grouped/data.js +28 -3
- package/dist/_internal/server/features/lottery/actions.js +99 -4
- package/dist/_internal/server/features/order-completion/actions.d.ts +92 -0
- package/dist/_internal/server/features/order-completion/actions.js +336 -0
- package/dist/_internal/server/features/order-completion/index.d.ts +7 -0
- package/dist/_internal/server/features/order-completion/index.js +7 -0
- package/dist/_internal/server/features/orders/actions.d.ts +13 -0
- package/dist/_internal/server/features/orders/actions.js +56 -2
- package/dist/_internal/server/features/orders/adapters.js +26 -0
- package/dist/_internal/server/features/orders/cancellation-refund.d.ts +116 -0
- package/dist/_internal/server/features/orders/cancellation-refund.js +466 -0
- package/dist/_internal/server/features/orders/index.d.ts +2 -1
- package/dist/_internal/server/features/orders/index.js +1 -1
- package/dist/_internal/server/features/orders/service.d.ts +60 -0
- package/dist/_internal/server/features/orders/service.js +74 -0
- package/dist/_internal/server/features/payment-cases/disputes.d.ts +76 -0
- package/dist/_internal/server/features/payment-cases/disputes.js +241 -0
- package/dist/_internal/server/features/payment-cases/index.d.ts +9 -0
- package/dist/_internal/server/features/payment-cases/index.js +9 -0
- package/dist/_internal/server/features/payouts/actions.d.ts +28 -3
- package/dist/_internal/server/features/payouts/actions.js +84 -10
- package/dist/_internal/server/features/points/adjust.d.ts +61 -0
- package/dist/_internal/server/features/points/adjust.js +121 -0
- package/dist/_internal/server/features/points/award.d.ts +65 -0
- package/dist/_internal/server/features/points/award.js +145 -0
- package/dist/_internal/server/features/price-index/market.d.ts +33 -0
- package/dist/_internal/server/features/price-index/market.js +54 -1
- package/dist/_internal/server/features/products/data.js +40 -22
- package/dist/_internal/server/features/protection-claims/actions.d.ts +72 -0
- package/dist/_internal/server/features/protection-claims/actions.js +228 -0
- package/dist/_internal/server/features/protection-claims/index.d.ts +8 -0
- package/dist/_internal/server/features/protection-claims/index.js +8 -0
- package/dist/_internal/server/features/refunds/actions.d.ts +20 -0
- package/dist/_internal/server/features/refunds/actions.js +259 -17
- package/dist/_internal/server/features/refunds/capacity.d.ts +29 -0
- package/dist/_internal/server/features/refunds/capacity.js +37 -0
- package/dist/_internal/server/features/reviews/actions.js +3 -2
- package/dist/_internal/server/features/search/index-build/sources.js +20 -1
- package/dist/_internal/server/features/shipping/reconcile.d.ts +10 -1
- package/dist/_internal/server/features/shipping/reconcile.js +34 -3
- package/dist/_internal/server/features/shipping/reimbursement.d.ts +117 -0
- package/dist/_internal/server/features/shipping/reimbursement.js +223 -0
- package/dist/_internal/server/features/stores/tabs.d.ts +19 -4
- package/dist/_internal/server/features/stores/tabs.js +24 -4
- package/dist/_internal/server/functions/https.d.ts +31 -1
- package/dist/_internal/server/functions/https.js +52 -1
- package/dist/_internal/server/functions/scheduled.d.ts +72 -1
- package/dist/_internal/server/functions/scheduled.js +124 -1
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +62 -16
- package/dist/_internal/server/jobs/core/boxRollupReconcile.d.ts +46 -0
- package/dist/_internal/server/jobs/core/boxRollupReconcile.js +142 -0
- package/dist/_internal/server/jobs/core/dailyDataCleanup.js +149 -1
- package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +14 -0
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +79 -1
- package/dist/_internal/server/jobs/core/index.d.ts +2 -0
- package/dist/_internal/server/jobs/core/index.js +2 -0
- package/dist/_internal/server/jobs/core/marketPriceRollup.d.ts +64 -0
- package/dist/_internal/server/jobs/core/marketPriceRollup.js +153 -0
- package/dist/_internal/server/jobs/core/obligationSweep.d.ts +36 -0
- package/dist/_internal/server/jobs/core/obligationSweep.js +503 -0
- package/dist/_internal/server/jobs/core/paymentReconcile.d.ts +79 -0
- package/dist/_internal/server/jobs/core/paymentReconcile.js +614 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +34 -0
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +35 -4
- package/dist/_internal/server/jobs/handlers/boxRollupReconcile.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/boxRollupReconcile.js +15 -0
- package/dist/_internal/server/jobs/handlers/countersReconcileNow.d.ts +65 -0
- package/dist/_internal/server/jobs/handlers/countersReconcileNow.js +89 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +5 -0
- package/dist/_internal/server/jobs/handlers/index.js +5 -0
- package/dist/_internal/server/jobs/handlers/marketPriceRollup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/marketPriceRollup.js +2 -0
- package/dist/_internal/server/jobs/handlers/obligationSweep.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/obligationSweep.js +15 -0
- package/dist/_internal/server/jobs/handlers/onShipmentAllocationSync.js +30 -1
- package/dist/_internal/server/jobs/handlers/paymentReconcile.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/paymentReconcile.js +6 -0
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +71 -0
- package/dist/_internal/shared/checkout/coupon-discount.d.ts +73 -0
- package/dist/_internal/shared/checkout/coupon-discount.js +103 -0
- package/dist/_internal/shared/checkout/lanes.d.ts +42 -0
- package/dist/_internal/shared/checkout/lanes.js +46 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +149 -1
- package/dist/_internal/shared/checkout/order-math.js +251 -3
- package/dist/_internal/shared/checkout/refundable-base.d.ts +116 -0
- package/dist/_internal/shared/checkout/refundable-base.js +122 -0
- package/dist/_internal/shared/features/auctions/config.d.ts +14 -1
- package/dist/_internal/shared/features/auctions/config.js +30 -1
- package/dist/_internal/shared/features/brands/schema.d.ts +42 -0
- package/dist/_internal/shared/features/brands/schema.js +18 -0
- package/dist/_internal/shared/features/orders/refund-copy.d.ts +22 -0
- package/dist/_internal/shared/features/orders/refund-copy.js +34 -0
- package/dist/_internal/shared/features/print/label-profiles.d.ts +198 -0
- package/dist/_internal/shared/features/print/label-profiles.js +350 -0
- package/dist/_internal/shared/features/products/to-product-item.js +33 -5
- package/dist/_internal/shared/fees/calculator.d.ts +31 -2
- package/dist/_internal/shared/fees/calculator.js +103 -5
- package/dist/_internal/shared/fees/india-states.d.ts +195 -0
- package/dist/_internal/shared/fees/india-states.js +190 -0
- package/dist/_internal/shared/fees/payout-hold.d.ts +80 -0
- package/dist/_internal/shared/fees/payout-hold.js +74 -0
- package/dist/_internal/shared/obligations/deadlines.d.ts +94 -0
- package/dist/_internal/shared/obligations/deadlines.js +155 -0
- package/dist/_internal/shared/shipping/quote.d.ts +96 -0
- package/dist/_internal/shared/shipping/quote.js +67 -0
- package/dist/_internal/shared/time/business-days.d.ts +91 -0
- package/dist/_internal/shared/time/business-days.js +185 -0
- package/dist/cli/index.js +0 -2
- package/dist/client.d.ts +9 -4
- package/dist/client.js +26 -3
- package/dist/configs/tailwind.d.ts +0 -12
- package/dist/configs/tailwind.js +28 -15
- package/dist/constants/api-endpoints.d.ts +51 -9
- package/dist/constants/api-endpoints.js +23 -7
- package/dist/constants/index.d.ts +1 -1
- package/dist/constants/index.js +1 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/payment.d.ts +131 -0
- package/dist/features/about/components/AboutView.js +1 -1
- package/dist/features/about/components/DeveloperView.js +5 -2
- package/dist/features/about/components/FAQPageView.js +2 -1
- package/dist/features/about/components/FeesView.js +2 -1
- package/dist/features/about/components/HelpPageView.js +2 -1
- package/dist/features/about/components/HowAuctionsWorkView.js +2 -1
- package/dist/features/about/components/HowCheckoutWorksView.js +2 -1
- package/dist/features/about/components/HowEmiWorksView.js +2 -1
- package/dist/features/about/components/HowOffersWorkView.js +2 -1
- package/dist/features/about/components/HowOrdersWorkView.js +2 -1
- package/dist/features/about/components/HowPayoutsWorkView.js +2 -1
- package/dist/features/about/components/HowPreOrdersWorkView.js +2 -1
- package/dist/features/about/components/HowReviewsWorkView.js +2 -1
- package/dist/features/about/components/PolicyPageView.js +2 -1
- package/dist/features/about/components/ProseHero.d.ts +73 -0
- package/dist/features/about/components/ProseHero.js +18 -0
- package/dist/features/about/components/SecurityPrivacyView.js +2 -1
- package/dist/features/about/components/ShippingPolicyView.js +2 -1
- package/dist/features/about/components/TrackOrderView.js +2 -1
- package/dist/features/about/components/UnauthorizedView.js +1 -1
- package/dist/features/about/index.d.ts +2 -0
- package/dist/features/about/index.js +5 -0
- package/dist/features/account/actions/address-actions.d.ts +26 -2
- package/dist/features/account/actions/address-actions.js +25 -1
- package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
- package/dist/features/account/components/NotificationPreferencesPanel.js +1 -1
- package/dist/features/account/components/UserOrdersView.js +17 -0
- package/dist/features/account/components/UserSidebar.js +1 -1
- package/dist/features/account/hooks/useNotifications.js +29 -0
- package/dist/features/admin/actions/admin-coupon-actions.js +36 -0
- package/dist/features/admin/components/AdminBeysView.js +2 -2
- package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
- package/dist/features/admin/components/AdminBrandEditorView.js +32 -2
- package/dist/features/admin/components/AdminBundleEditorView.js +1 -1
- package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
- package/dist/features/admin/components/AdminCategoriesView.js +1 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +73 -17
- package/dist/features/admin/components/AdminContactEditorView.js +1 -1
- package/dist/features/admin/components/AdminCouponEditorView.js +26 -1
- package/dist/features/admin/components/AdminFaqEditorView.js +1 -1
- package/dist/features/admin/components/AdminFaqsView.js +2 -2
- package/dist/features/admin/components/AdminGatewayEventsView.d.ts +54 -0
- package/dist/features/admin/components/AdminGatewayEventsView.js +209 -0
- package/dist/features/admin/components/AdminGuideHubView.js +1 -1
- package/dist/features/admin/components/AdminMoneyCasesView.d.ts +60 -0
- package/dist/features/admin/components/AdminMoneyCasesView.js +287 -0
- package/dist/features/admin/components/AdminPartsView.js +2 -2
- package/dist/features/admin/components/AdminProductsView.js +75 -4
- package/dist/features/admin/components/AdminSidebar.js +2 -2
- package/dist/features/admin/components/AdminSiteSettingsView.js +65 -7
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +2 -0
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +1 -1
- package/dist/features/admin/components/AdminSupportTicketsView.js +2 -2
- package/dist/features/admin/components/AdminTesterChecklistView.js +2 -2
- package/dist/features/admin/components/AdminUsersView.js +72 -1
- package/dist/features/admin/components/AdminViewCards.d.ts +3 -1
- package/dist/features/admin/components/AdminViewCards.js +4 -4
- package/dist/features/admin/components/DataListingView.d.ts +28 -0
- package/dist/features/admin/components/DataListingView.js +175 -14
- package/dist/features/admin/components/DataTable.d.ts +12 -1
- package/dist/features/admin/components/DataTable.js +3 -3
- package/dist/features/admin/components/OwnerQuickActions.d.ts +1 -1
- package/dist/features/admin/components/OwnerQuickActions.js +60 -2
- package/dist/features/admin/components/RecordOwnerQuickActions.d.ts +1 -1
- package/dist/features/admin/components/RecordOwnerQuickActions.js +61 -14
- package/dist/features/admin/components/analytics/AdminStatCard.d.ts +35 -1
- package/dist/features/admin/components/analytics/AdminStatCard.js +77 -2
- package/dist/features/admin/components/index.d.ts +4 -0
- package/dist/features/admin/components/index.js +6 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +156 -0
- package/dist/features/admin/constants/filter-tabs.js +106 -0
- package/dist/features/admin/repository/site-settings.repository.d.ts +22 -0
- package/dist/features/admin/repository/site-settings.repository.js +82 -1
- package/dist/features/admin/schemas/admin-editor-forms.d.ts +21 -0
- package/dist/features/admin/schemas/admin-editor-forms.js +44 -0
- package/dist/features/admin/schemas/firestore.js +5 -1
- package/dist/features/admin/schemas/site-settings-form.js +30 -1
- package/dist/features/auctions/actions/bid-actions.js +13 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +2 -2
- package/dist/features/auctions/components/AuctionDetailPageView.js +3 -1
- package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -0
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +5 -3
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +8 -1
- package/dist/features/auctions/components/PlaceBidFormClient.js +2 -2
- package/dist/features/audit-log/schemas/firestore.d.ts +28 -0
- package/dist/features/audit-log/schemas/firestore.js +14 -0
- package/dist/features/auth/permissions/constants.d.ts +17 -1
- package/dist/features/auth/permissions/constants.js +18 -0
- package/dist/features/blog/components/BlogPostView.js +1 -1
- package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +1 -1
- package/dist/features/categories/api/[id]/description-template/route.js +107 -1
- package/dist/features/categories/components/BrandDetailPageView.js +2 -2
- package/dist/features/categories/components/BreadcrumbTrail.js +7 -1
- package/dist/features/categories/components/BundleDetailView.js +1 -1
- package/dist/features/categories/components/CategoryDetailPageView.js +2 -2
- package/dist/features/categories/components/CategoryForm.js +12 -1
- package/dist/features/categories/components/CategoryTemplatePicker.js +38 -5
- package/dist/features/categories/repository/market-price-index.repository.d.ts +123 -0
- package/dist/features/categories/repository/market-price-index.repository.js +105 -0
- package/dist/features/categories/repository/price-observation.repository.d.ts +166 -0
- package/dist/features/categories/repository/price-observation.repository.js +132 -0
- package/dist/features/events/components/EventSaleBanner.js +1 -1
- package/dist/features/events/components/EventStatusBadge.js +1 -1
- package/dist/features/faq/components/FAQAccordion.js +2 -1
- package/dist/features/faq/components/FAQCategorySidebar.js +2 -2
- package/dist/features/faq/components/FAQHelpfulButtons.js +4 -4
- package/dist/features/faq/components/RelatedFAQs.js +1 -1
- package/dist/features/filters/FilterFacetSection.js +1 -1
- package/dist/features/filters/SwitchFilter.js +1 -1
- package/dist/features/gateway-events/repository/gateway-events.repository.d.ts +117 -0
- package/dist/features/gateway-events/repository/gateway-events.repository.js +274 -0
- package/dist/features/gateway-events/schemas/firestore.d.ts +217 -0
- package/dist/features/gateway-events/schemas/firestore.js +125 -0
- package/dist/features/gateway-events/schemas/index.d.ts +81 -0
- package/dist/features/gateway-events/schemas/index.js +79 -0
- package/dist/features/grouped/components/GroupedListingDetailView.js +1 -1
- package/dist/features/homepage/components/AnnouncementBar.js +37 -1
- package/dist/features/homepage/components/AnnouncementBar.style.css +30 -0
- package/dist/features/homepage/components/BrandsSection.js +1 -1
- package/dist/features/homepage/components/CTABannerSection.js +1 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +14 -24
- package/dist/features/homepage/components/HeroCarousel.js +2 -2
- package/dist/features/homepage/components/SocialPostCard.js +1 -1
- package/dist/features/inventory/components/AdminMoveLedgerView.d.ts +7 -0
- package/dist/features/inventory/components/AdminMoveLedgerView.js +157 -0
- package/dist/features/inventory/components/AdminStorageBoxesView.d.ts +11 -0
- package/dist/features/inventory/components/AdminStorageBoxesView.js +168 -0
- package/dist/features/inventory/components/ReceiveStationView.d.ts +39 -0
- package/dist/features/inventory/components/ReceiveStationView.js +413 -0
- package/dist/features/inventory/components/operator-message.d.ts +29 -0
- package/dist/features/inventory/components/operator-message.js +41 -0
- package/dist/features/inventory/index.d.ts +13 -0
- package/dist/features/inventory/index.js +28 -0
- package/dist/features/inventory/repository/inventory-movements.repository.d.ts +97 -0
- package/dist/features/inventory/repository/inventory-movements.repository.js +186 -0
- package/dist/features/inventory/repository/inventory-placements.repository.d.ts +179 -0
- package/dist/features/inventory/repository/inventory-placements.repository.js +345 -0
- package/dist/features/inventory/repository/storage-boxes.repository.d.ts +83 -0
- package/dist/features/inventory/repository/storage-boxes.repository.js +127 -0
- package/dist/features/inventory/schemas/firestore.d.ts +197 -0
- package/dist/features/inventory/schemas/firestore.js +159 -0
- package/dist/features/layout/AppLayoutShell.d.ts +12 -1
- package/dist/features/layout/AppLayoutShell.js +25 -12
- package/dist/features/layout/AutoBreadcrumbs.js +162 -3
- package/dist/features/layout/AutoBreadcrumbs.style.css +45 -0
- package/dist/features/layout/BottomNavbar.d.ts +1 -1
- package/dist/features/layout/BottomNavbar.js +2 -2
- package/dist/features/media/MediaAvatar.js +1 -1
- package/dist/features/media/MediaImage.d.ts +24 -4
- package/dist/features/media/MediaImage.js +44 -4
- package/dist/features/media/MediaLightbox.js +1 -1
- package/dist/features/media/modals/ImageCropModal.js +1 -1
- package/dist/features/media/modals/ImageEditor.js +1 -1
- package/dist/features/media/upload/MediaUploadField.js +91 -3
- package/dist/features/obligations/constants/policies.d.ts +92 -0
- package/dist/features/obligations/constants/policies.js +378 -0
- package/dist/features/obligations/repository/obligations.repository.d.ts +68 -0
- package/dist/features/obligations/repository/obligations.repository.js +158 -0
- package/dist/features/obligations/schemas/firestore.d.ts +160 -0
- package/dist/features/obligations/schemas/firestore.js +87 -0
- package/dist/features/orders/actions/order-actions.d.ts +43 -4
- package/dist/features/orders/actions/order-actions.js +60 -3
- package/dist/features/orders/actions/refund-actions.js +70 -10
- package/dist/features/orders/components/OrderCompletionPanel.d.ts +27 -0
- package/dist/features/orders/components/OrderCompletionPanel.js +192 -0
- package/dist/features/orders/components/OrdersList.js +2 -2
- package/dist/features/orders/components/RefundHistoryTable.js +3 -1
- package/dist/features/orders/components/index.d.ts +2 -0
- package/dist/features/orders/components/index.js +6 -0
- package/dist/features/orders/constants/transitions.d.ts +59 -0
- package/dist/features/orders/constants/transitions.js +104 -0
- package/dist/features/orders/repository/orders.repository.d.ts +109 -6
- package/dist/features/orders/repository/orders.repository.js +464 -21
- package/dist/features/orders/schemas/firestore.d.ts +270 -0
- package/dist/features/orders/schemas/firestore.js +24 -0
- package/dist/features/orders/schemas/index.d.ts +61 -0
- package/dist/features/orders/schemas/index.js +22 -0
- package/dist/features/orders/types/index.d.ts +79 -0
- package/dist/features/parts/components/PartsIndexPageView.js +8 -1
- package/dist/features/payment-cases/repository/payment-cases.repository.d.ts +170 -0
- package/dist/features/payment-cases/repository/payment-cases.repository.js +342 -0
- package/dist/features/payment-cases/schemas/firestore.d.ts +229 -0
- package/dist/features/payment-cases/schemas/firestore.js +105 -0
- package/dist/features/payment-cases/schemas/resolve-form.d.ts +97 -0
- package/dist/features/payment-cases/schemas/resolve-form.js +114 -0
- package/dist/features/payments/repository/payout.repository.d.ts +0 -1
- package/dist/features/payments/repository/payout.repository.js +0 -1
- package/dist/features/points/repository/points.repository.d.ts +122 -0
- package/dist/features/points/repository/points.repository.js +304 -1
- package/dist/features/points/schemas/adjust-form.d.ts +58 -0
- package/dist/features/points/schemas/adjust-form.js +63 -0
- package/dist/features/points/schemas/firestore.d.ts +108 -1
- package/dist/features/points/schemas/firestore.js +84 -0
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
- package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
- package/dist/features/price-index/components/PriceIndexToolView.d.ts +23 -4
- package/dist/features/price-index/components/PriceIndexToolView.js +71 -6
- package/dist/features/price-index/market-stats.d.ts +82 -0
- package/dist/features/price-index/market-stats.js +230 -0
- package/dist/features/print/components/BoxLabelPrintView.d.ts +25 -0
- package/dist/features/print/components/BoxLabelPrintView.js +66 -0
- package/dist/features/print/components/LabelProfilePicker.d.ts +13 -0
- package/dist/features/print/components/LabelProfilePicker.js +38 -0
- package/dist/features/print/components/labels.d.ts +117 -0
- package/dist/features/print/components/labels.js +258 -0
- package/dist/features/print/components/labels.style.css +46 -0
- package/dist/features/print/index.d.ts +6 -0
- package/dist/features/print/index.js +16 -0
- package/dist/features/products/columns/productTableColumns.js +5 -4
- package/dist/features/products/components/MarketplaceBundleCard.js +2 -2
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +6 -6
- package/dist/features/products/components/PrizeDrawCollage.js +1 -1
- package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
- package/dist/features/products/components/PrizeRevealModal.js +2 -2
- package/dist/features/products/components/ProductDetailPageView.js +18 -4
- package/dist/features/products/components/ProductForm.js +34 -23
- package/dist/features/products/components/ProductGrid.js +86 -62
- package/dist/features/products/constants/card-badges.d.ts +97 -0
- package/dist/features/products/constants/card-badges.js +100 -0
- package/dist/features/products/repository/products.repository.js +34 -1
- package/dist/features/products/schemas/firestore.d.ts +46 -3
- package/dist/features/products/schemas/firestore.js +21 -0
- package/dist/features/promotions/components/PromotionsView.js +1 -1
- package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +32 -0
- package/dist/features/promotions/repository/claimed-coupons.repository.js +58 -0
- package/dist/features/promotions/schemas/firestore.js +30 -3
- package/dist/features/promotions/schemas/index.d.ts +6 -6
- package/dist/features/protection-claims/repository/protection-claims.repository.d.ts +54 -0
- package/dist/features/protection-claims/repository/protection-claims.repository.js +174 -0
- package/dist/features/protection-claims/schemas/firestore.d.ts +168 -0
- package/dist/features/protection-claims/schemas/firestore.js +130 -0
- package/dist/features/reviews/actions/review-actions.js +3 -2
- package/dist/features/reviews/components/ReviewModal.js +1 -1
- package/dist/features/reviews/components/ReviewsList.js +1 -1
- package/dist/features/reviews/schemas/firestore.d.ts +20 -1
- package/dist/features/scams/constants/scam-types.d.ts +2 -0
- package/dist/features/scams/constants/scam-types.js +18 -0
- package/dist/features/scams/schemas/report-form.d.ts +3 -3
- package/dist/features/scams/schemas/report-form.js +26 -1
- package/dist/features/search/components/Search.js +9 -9
- package/dist/features/search/components/SearchResultsClient.js +18 -3
- package/dist/features/search/components/SearchResultsPageView.d.ts +1 -6
- package/dist/features/search/components/SearchResultsPageView.js +2 -2
- package/dist/features/seller/actions/offer-actions.js +16 -1
- package/dist/features/seller/actions/seller-actions.js +14 -3
- package/dist/features/seller/components/CategoryInlineSelect.js +75 -11
- package/dist/features/seller/components/PrintCenterView.d.ts +5 -0
- package/dist/features/seller/components/PrintCenterView.js +41 -30
- package/dist/features/seller/components/SellerOrdersView.js +1 -1
- package/dist/features/seller/components/SellerPayoutRequestView.js +16 -5
- package/dist/features/seller/components/SellerPayoutSettingsView.js +1 -1
- package/dist/features/seller/components/SellerProductShell.d.ts +16 -0
- package/dist/features/seller/components/SellerProductShell.js +123 -13
- package/dist/features/seller/components/SellerReviewsView.js +4 -2
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/seller/schemas/coupon-form.d.ts +26 -0
- package/dist/features/seller/schemas/coupon-form.js +27 -0
- package/dist/features/seller/schemas/product-form.d.ts +3 -0
- package/dist/features/seller/schemas/product-form.js +5 -0
- package/dist/features/shell/FormShell.js +2 -2
- package/dist/features/shell/QuickFormDrawer.js +14 -0
- package/dist/features/shell/SectionForm.d.ts +19 -0
- package/dist/features/shell/SectionForm.js +133 -9
- package/dist/features/shell/SectionForm.style.css +54 -0
- package/dist/features/shipments/components/AdminShipmentsView.js +16 -1
- package/dist/features/shipments/repository/shipment-items.repository.d.ts +9 -0
- package/dist/features/shipments/repository/shipment-items.repository.js +14 -0
- package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
- package/dist/features/sticker-trace/components/StickerCommitPanel.js +1 -1
- package/dist/features/store-extensions/schemas/report-review-form.d.ts +8 -8
- package/dist/features/stores/components/StoreGuideHubView.js +1 -1
- package/dist/features/stores/components/StoreHeader.js +1 -1
- package/dist/features/support/schemas/firestore.d.ts +73 -2
- package/dist/features/support/schemas/firestore.js +59 -0
- package/dist/index.d.ts +16 -24
- package/dist/index.js +38 -47
- package/dist/next/ErrorBoundary.js +1 -1
- package/dist/next/routing/route-map.d.ts +24 -0
- package/dist/next/routing/route-map.js +40 -0
- package/dist/providers/payment-manual/index.d.ts +15 -1
- package/dist/providers/payment-manual/index.js +37 -0
- package/dist/providers/payment-phonepe/index.d.ts +14 -1
- package/dist/providers/payment-phonepe/index.js +36 -0
- package/dist/providers/payment-razorpay/index.d.ts +44 -1
- package/dist/providers/payment-razorpay/index.js +99 -0
- package/dist/providers/shipping-shiprocket/index.d.ts +18 -0
- package/dist/providers/shipping-shiprocket/index.js +18 -0
- package/dist/schemas/registry.d.ts +44 -11
- package/dist/server-entry.d.ts +2 -0
- package/dist/server-entry.js +27 -0
- package/dist/server.d.ts +42 -0
- package/dist/server.js +93 -0
- package/dist/styles.css +418 -149
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/default-dark.js +15 -12
- package/dist/tokens/themes/default-light.js +14 -11
- package/dist/tokens/tokens.css +106 -23
- package/dist/ui/DataTable.style.css +4 -4
- package/dist/ui/columns/cell-renderers.d.ts +20 -0
- package/dist/ui/columns/cell-renderers.js +26 -1
- package/dist/ui/columns/index.d.ts +1 -1
- package/dist/ui/columns/index.js +4 -1
- package/dist/ui/components/Badge.style.css +1 -1
- package/dist/ui/components/Breadcrumb.js +19 -1
- package/dist/ui/components/BulkActionBar.style.css +4 -4
- package/dist/ui/components/Button.style.css +1 -1
- package/dist/ui/components/Checkbox.style.css +1 -1
- package/dist/ui/components/FallbackShell.js +1 -1
- package/dist/ui/components/Form.d.ts +14 -1
- package/dist/ui/components/Form.js +2 -2
- package/dist/ui/components/FormField.js +18 -4
- package/dist/ui/components/FormField.style.css +43 -3
- package/dist/ui/components/GatedPrice.style.css +1 -1
- package/dist/ui/components/IconButton.style.css +1 -1
- package/dist/ui/components/Input.js +22 -1
- package/dist/ui/components/Input.style.css +38 -22
- package/dist/ui/components/ListingFilterDrawer.js +1 -1
- package/dist/ui/components/ListingToolbar.js +1 -1
- package/dist/ui/components/PaginatedSelect.js +0 -0
- package/dist/ui/components/Radio.style.css +1 -1
- package/dist/ui/components/Repeater.d.ts +53 -0
- package/dist/ui/components/Repeater.js +7 -0
- package/dist/ui/components/RichTextEditor.d.ts +20 -1
- package/dist/ui/components/RichTextEditor.js +23 -2
- package/dist/ui/components/Select.style.css +7 -21
- package/dist/ui/components/Semantic.js +27 -3
- package/dist/ui/components/TagInput.style.css +4 -9
- package/dist/ui/components/Textarea.style.css +4 -18
- package/dist/ui/components/Toggle.style.css +1 -1
- package/dist/ui/components/Typography.d.ts +3 -3
- package/dist/ui/components/Typography.js +15 -15
- package/dist/ui/components/ViewToggle.style.css +4 -4
- package/dist/ui/forms/AddAnotherToggle.d.ts +16 -0
- package/dist/ui/forms/AddAnotherToggle.js +56 -0
- package/dist/ui/forms/ColorPickerField.js +15 -2
- package/dist/ui/forms/FieldCheckbox.js +15 -2
- package/dist/ui/forms/FieldInput.d.ts +22 -1
- package/dist/ui/forms/FieldInput.js +19 -5
- package/dist/ui/forms/FieldLabel.d.ts +35 -0
- package/dist/ui/forms/FieldLabel.js +70 -0
- package/dist/ui/forms/FieldSelect.d.ts +4 -1
- package/dist/ui/forms/FieldSelect.js +27 -5
- package/dist/ui/forms/FieldTextarea.d.ts +4 -1
- package/dist/ui/forms/FieldTextarea.js +22 -5
- package/dist/ui/forms/FormShell.d.ts +17 -1
- package/dist/ui/forms/FormShell.js +36 -2
- package/dist/ui/forms/FormShell.style.css +41 -33
- package/dist/ui/forms/index.d.ts +5 -1
- package/dist/ui/forms/index.js +2 -0
- package/dist/ui/icons/icon-registry.d.ts +1 -0
- package/dist/ui/icons/icon-registry.js +17 -1
- package/dist/utils/id-generators.d.ts +80 -0
- package/dist/utils/id-generators.js +93 -0
- package/dist/utils/search-txt-builders.d.ts +12 -0
- package/dist/utils/search-txt-builders.js +17 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +106 -5
- package/dist/features/loyalty/api/admin/loyalty/grant/route.d.ts +0 -12
- package/dist/features/loyalty/api/admin/loyalty/grant/route.js +0 -38
- package/dist/features/loyalty/api/balance/route.d.ts +0 -12
- package/dist/features/loyalty/api/balance/route.js +0 -41
- package/dist/features/loyalty/api/earn/route.d.ts +0 -12
- package/dist/features/loyalty/api/earn/route.js +0 -38
- package/dist/features/loyalty/api/history/route.d.ts +0 -12
- package/dist/features/loyalty/api/history/route.js +0 -58
- package/dist/features/loyalty/api/redeem/route.d.ts +0 -12
- package/dist/features/loyalty/api/redeem/route.js +0 -38
- package/dist/features/loyalty/columns/index.d.ts +0 -17
- package/dist/features/loyalty/columns/index.js +0 -40
- package/dist/features/loyalty/components/CoinsDisplay.d.ts +0 -12
- package/dist/features/loyalty/components/CoinsDisplay.js +0 -10
- package/dist/features/loyalty/components/index.d.ts +0 -1
- package/dist/features/loyalty/components/index.js +0 -1
- package/dist/features/loyalty/hooks/useLoyaltyBalance.d.ts +0 -4
- package/dist/features/loyalty/hooks/useLoyaltyBalance.js +0 -25
- package/dist/features/loyalty/index.d.ts +0 -6
- package/dist/features/loyalty/index.js +0 -6
- package/dist/features/loyalty/manifest.d.ts +0 -2
- package/dist/features/loyalty/manifest.js +0 -16
- package/dist/features/loyalty/messages/en.json +0 -19
- package/dist/features/loyalty/repository/loyalty.repository.d.ts +0 -8
- package/dist/features/loyalty/repository/loyalty.repository.js +0 -22
- package/dist/features/loyalty/schemas/index.d.ts +0 -85
- package/dist/features/loyalty/schemas/index.js +0 -40
- package/dist/features/loyalty/server.d.ts +0 -6
- package/dist/features/loyalty/server.js +0 -6
- package/dist/features/loyalty/types/index.d.ts +0 -31
- package/dist/features/loyalty/types/index.js +0 -1
- package/dist/features/loyalty/types/loyalty-config.d.ts +0 -7
- package/dist/features/loyalty/types/loyalty-config.js +0 -1
- package/dist/features/loyalty/types/loyalty-math.d.ts +0 -9
- package/dist/features/loyalty/types/loyalty-math.js +0 -27
- package/dist/features/products/components/ProductOwnerQuickActions.d.ts +0 -11
- 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;
|