@open-mercato/core 0.6.8-develop.6911.1.f853dd04a9 → 0.6.8-develop.6912.1.36161dc9ac
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/modules/api_keys/backend/api-keys/page.js.map +1 -1
- package/dist/modules/attachments/components/AttachmentLibrary.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +1 -1
- package/dist/modules/audit_logs/components/AccessLogsTable.js.map +1 -1
- package/dist/modules/audit_logs/components/AuditLogsActions.js.map +1 -1
- package/dist/modules/auth/backend/roles/page.js.map +2 -2
- package/dist/modules/auth/backend/users/page.js.map +2 -2
- package/dist/modules/business_rules/backend/logs/page.js.map +1 -1
- package/dist/modules/business_rules/backend/rules/page.js.map +1 -1
- package/dist/modules/business_rules/backend/sets/page.js.map +1 -1
- package/dist/modules/catalog/components/PriceKindSettings.js.map +1 -1
- package/dist/modules/catalog/components/categories/CategoriesDataTable.js.map +1 -1
- package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +1 -1
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +1 -1
- package/dist/modules/currencies/backend/currencies/page.js.map +1 -1
- package/dist/modules/currencies/backend/exchange-rates/page.js.map +1 -1
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +1 -1
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +1 -1
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js.map +1 -1
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/components/CustomerTodosTable.js.map +1 -1
- package/dist/modules/data_sync/backend/data-sync/page.js.map +1 -1
- package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/page.js.map +1 -1
- package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/entities/components/SystemEntitiesTable.js.map +2 -2
- package/dist/modules/entities/components/UserEntitiesTable.js.map +2 -2
- package/dist/modules/feature_toggles/components/FeatureTogglesTable.js.map +2 -2
- package/dist/modules/feature_toggles/components/OverridesTable.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js.map +1 -1
- package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +1 -1
- package/dist/modules/messages/components/MessagesInboxPageClient.js.map +1 -1
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +1 -1
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/page.js.map +1 -1
- package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
- package/dist/modules/sales/components/AdjustmentKindSettings.js.map +1 -1
- package/dist/modules/sales/components/PaymentMethodsSettings.js.map +1 -1
- package/dist/modules/sales/components/ShippingMethodsSettings.js.map +1 -1
- package/dist/modules/sales/components/TaxRatesSettings.js.map +1 -1
- package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
- package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +1 -1
- package/dist/modules/sales/components/documents/PaymentsSection.js.map +1 -1
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesOrderDraftLines.js.map +1 -1
- package/dist/modules/staff/backend/staff/leave-requests/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/my-leave-requests/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-roles/page.js.map +1 -1
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsConfigurationPage.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsLocationDetailPage.js.map +1 -1
- package/dist/modules/wms/components/backend/WmsLotDetailPage.js.map +1 -1
- package/dist/modules/wms/components/backend/WmsLotsListPage.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js.map +1 -1
- package/dist/modules/wms/components/backend/WmsSkuDetailPage.js.map +1 -1
- package/dist/modules/workflows/backend/definitions/page.js.map +1 -1
- package/dist/modules/workflows/backend/events/page.js.map +1 -1
- package/dist/modules/workflows/backend/instances/page.js.map +1 -1
- package/dist/modules/workflows/backend/tasks/page.js.map +1 -1
- package/jest.config.cjs +1 -1
- package/package.json +7 -7
- package/src/modules/api_keys/backend/api-keys/page.tsx +1 -1
- package/src/modules/attachments/components/AttachmentLibrary.tsx +2 -1
- package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +1 -1
- package/src/modules/audit_logs/components/AccessLogsTable.tsx +1 -1
- package/src/modules/audit_logs/components/AuditLogsActions.tsx +1 -1
- package/src/modules/auth/backend/roles/page.tsx +2 -1
- package/src/modules/auth/backend/users/page.tsx +2 -1
- package/src/modules/business_rules/backend/logs/page.tsx +1 -1
- package/src/modules/business_rules/backend/rules/page.tsx +1 -1
- package/src/modules/business_rules/backend/sets/page.tsx +1 -1
- package/src/modules/catalog/components/PriceKindSettings.tsx +1 -1
- package/src/modules/catalog/components/categories/CategoriesDataTable.tsx +1 -1
- package/src/modules/catalog/components/products/ProductsDataTable.tsx +2 -1
- package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +1 -1
- package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +1 -1
- package/src/modules/currencies/backend/currencies/page.tsx +1 -1
- package/src/modules/currencies/backend/exchange-rates/page.tsx +1 -1
- package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +1 -1
- package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +1 -1
- package/src/modules/customers/backend/customers/companies/page.tsx +2 -1
- package/src/modules/customers/backend/customers/deals/page.tsx +1 -1
- package/src/modules/customers/backend/customers/people/page.tsx +2 -1
- package/src/modules/customers/components/CustomerTodosTable.tsx +1 -1
- package/src/modules/data_sync/backend/data-sync/page.tsx +1 -1
- package/src/modules/dictionaries/components/DictionaryTable.tsx +2 -1
- package/src/modules/directory/backend/directory/organizations/page.tsx +1 -1
- package/src/modules/directory/backend/directory/tenants/page.tsx +2 -1
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +2 -1
- package/src/modules/entities/components/SystemEntitiesTable.tsx +2 -1
- package/src/modules/entities/components/UserEntitiesTable.tsx +2 -1
- package/src/modules/feature_toggles/components/FeatureTogglesTable.tsx +2 -1
- package/src/modules/feature_toggles/components/OverridesTable.tsx +2 -1
- package/src/modules/inbox_ops/backend/inbox-ops/log/page.tsx +1 -1
- package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +1 -1
- package/src/modules/messages/components/MessagesInboxPageClient.tsx +1 -1
- package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +1 -1
- package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +2 -1
- package/src/modules/query_index/components/QueryIndexesTable.tsx +2 -1
- package/src/modules/resources/backend/resources/resource-types/page.tsx +2 -1
- package/src/modules/resources/backend/resources/resources/page.tsx +1 -1
- package/src/modules/sales/backend/sales/channels/offers/page.tsx +2 -1
- package/src/modules/sales/backend/sales/channels/page.tsx +2 -1
- package/src/modules/sales/components/AdjustmentKindSettings.tsx +1 -1
- package/src/modules/sales/components/PaymentMethodsSettings.tsx +1 -1
- package/src/modules/sales/components/ShippingMethodsSettings.tsx +1 -1
- package/src/modules/sales/components/TaxRatesSettings.tsx +1 -1
- package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +2 -1
- package/src/modules/sales/components/documents/AdjustmentsSection.tsx +1 -1
- package/src/modules/sales/components/documents/PaymentsSection.tsx +1 -1
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +2 -1
- package/src/modules/sales/components/documents/SalesOrderDraftLines.tsx +1 -1
- package/src/modules/staff/backend/staff/leave-requests/page.tsx +2 -1
- package/src/modules/staff/backend/staff/my-leave-requests/page.tsx +2 -1
- package/src/modules/staff/backend/staff/team-members/page.tsx +3 -2
- package/src/modules/staff/backend/staff/team-roles/page.tsx +1 -1
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +2 -1
- package/src/modules/staff/backend/staff/teams/page.tsx +2 -1
- package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +2 -1
- package/src/modules/wms/components/backend/WmsConfigurationPage.tsx +2 -1
- package/src/modules/wms/components/backend/WmsInventoryConsolePage.tsx +4 -3
- package/src/modules/wms/components/backend/WmsLocationDetailPage.tsx +1 -1
- package/src/modules/wms/components/backend/WmsLotDetailPage.tsx +1 -1
- package/src/modules/wms/components/backend/WmsLotsListPage.tsx +2 -1
- package/src/modules/wms/components/backend/WmsOperationalDashboardPage.tsx +1 -1
- package/src/modules/wms/components/backend/WmsSkuDetailPage.tsx +1 -1
- package/src/modules/workflows/backend/definitions/page.tsx +1 -1
- package/src/modules/workflows/backend/events/page.tsx +1 -1
- package/src/modules/workflows/backend/instances/page.tsx +1 -1
- package/src/modules/workflows/backend/tasks/page.tsx +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/wms/components/backend/WmsLocationDetailPage.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/wms/extension-points'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { z } from 'zod'\nimport { useQuery } from '@tanstack/react-query'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport {\n ArrowLeft,\n ArrowLeftRight,\n ClipboardList,\n Download,\n ExternalLink,\n SlidersHorizontal,\n Warehouse,\n} from 'lucide-react'\nimport { Page, PageBody, PageHeader } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { EmptyState } from '@open-mercato/ui/backend/EmptyState'\nimport { ErrorMessage, LoadingMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { LinkButton } from '@open-mercato/ui/primitives/link-button'\nimport { Progress } from '@open-mercato/ui/primitives/progress'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { StatusBadge, type StatusBadgeVariant } from '@open-mercato/ui/primitives/status-badge'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n inventoryMovementReasonLabel,\n type InventoryDisplayTranslator,\n} from '../../lib/inventoryDisplayUi'\nimport { AdjustInventoryDialog } from './AdjustInventoryDialog'\nimport { CycleCountWizardDialog } from './CycleCountWizardDialog'\nimport { LocationEditDialog } from './LocationEditDialog'\nimport { useWmsInventoryMutationAccess } from './useWmsInventoryMutationAccess'\n\nconst locationIdSchema = z.string().uuid()\n\ntype PagedResponse<T> = {\n items: T[]\n total: number\n totalPages: number\n}\n\ntype LocationRow = {\n id: string\n code?: string | null\n type?: string | null\n parent_id?: string | null\n warehouse_id?: string | null\n warehouse_name?: string | null\n warehouse_code?: string | null\n is_active?: boolean | null\n capacity_units?: string | number | null\n capacity_weight?: string | number | null\n updated_at?: string | null\n}\n\ntype InventoryProfileRow = {\n catalog_variant_id?: string | null\n reorder_point?: string | number | null\n}\n\ntype InventoryBalanceRow = {\n id: string\n warehouse_id?: string | null\n warehouse_name?: string | null\n warehouse_code?: string | null\n location_id?: string | null\n location_code?: string | null\n catalog_variant_id?: string | null\n variant_name?: string | null\n variant_sku?: string | null\n lot_id?: string | null\n quantity_on_hand?: string | number | null\n quantity_reserved?: string | number | null\n quantity_allocated?: string | number | null\n quantity_available?: number | null\n}\n\ntype InventoryLotRow = {\n id: string\n lot_number?: string | null\n expires_at?: string | null\n status?: string | null\n}\n\ntype InventoryMovementRow = {\n id: string\n warehouse_id?: string | null\n warehouse_name?: string | null\n warehouse_code?: string | null\n location_from_id?: string | null\n location_from_code?: string | null\n location_to_id?: string | null\n location_to_code?: string | null\n catalog_variant_id?: string | null\n variant_sku?: string | null\n variant_name?: string | null\n quantity?: string | number | null\n type?: string | null\n reference_type?: string | null\n reference_id?: string | null\n reason?: string | null\n reason_code?: string | null\n performed_at?: string | null\n received_at?: string | null\n}\n\ntype InventoryMutationPreset = {\n warehouseId?: string\n locationId?: string\n catalogVariantId?: string\n lotId?: string\n}\n\ntype ItemFilter = 'all' | 'sellable' | 'picking' | 'nearExpiry'\n\nconst NEAR_EXPIRY_MS = 30 * 24 * 60 * 60 * 1000\n\nfunction escapeCsvCell(value: string | number): string {\n const str = String(value ?? '')\n if (/[\",\\n]/.test(str)) return `\"${str.replace(/\"/g, '\"\"')}\"`\n return str\n}\n\nfunction downloadCsvFile(filename: string, rows: string[][]) {\n const csv = rows.map((row) => row.map(escapeCsvCell).join(',')).join('\\n')\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })\n const url = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = filename\n anchor.click()\n URL.revokeObjectURL(url)\n}\n\nfunction toNumber(value: string | number | null | undefined): number {\n const parsed = Number(value ?? 0)\n return Number.isFinite(parsed) ? parsed : 0\n}\n\nfunction formatWarehouseLabel(row: {\n warehouse_name?: string | null\n warehouse_code?: string | null\n warehouse_id?: string | null\n}): string {\n const code = (row.warehouse_code ?? '').trim()\n const name = (row.warehouse_name ?? '').trim()\n if (code && name) return `${code} \u00B7 ${name}`\n return name || code || row.warehouse_id || '\u2014'\n}\n\nfunction formatSkuLabel(row: InventoryBalanceRow): string {\n const sku = (row.variant_sku ?? '').trim()\n const name = (row.variant_name ?? '').trim()\n if (sku) return sku\n if (name) return name\n return row.catalog_variant_id || '\u2014'\n}\n\nfunction formatVariantName(row: InventoryBalanceRow): string {\n return (row.variant_name ?? '').trim() || '\u2014'\n}\n\nfunction formatLotLabel(\n lot: InventoryLotRow | undefined,\n locale: string,\n): string {\n if (!lot) return '\u2014'\n const number = (lot.lot_number ?? '').trim() || lot.id\n if (!lot.expires_at) return number\n const expires = new Date(lot.expires_at)\n if (Number.isNaN(expires.getTime())) return number\n const expLabel = new Intl.DateTimeFormat(locale, { month: '2-digit', year: '2-digit' }).format(expires)\n return `${number} \u00B7 exp ${expLabel}`\n}\n\nfunction isNearExpiry(expiresAt: string | null | undefined, nowMs: number): boolean {\n if (!expiresAt) return false\n const expires = new Date(expiresAt).getTime()\n if (Number.isNaN(expires)) return false\n return expires > nowMs && expires - nowMs <= NEAR_EXPIRY_MS\n}\n\nfunction isExpired(expiresAt: string | null | undefined, nowMs: number): boolean {\n if (!expiresAt) return false\n const expires = new Date(expiresAt).getTime()\n return !Number.isNaN(expires) && expires <= nowMs\n}\n\nfunction resolveItemStatus(\n row: InventoryBalanceRow,\n lot: InventoryLotRow | undefined,\n reorderPoint: number,\n nowMs: number,\n): { variant: StatusBadgeVariant; labelKey: string; labelFallback: string } {\n if (lot?.status === 'expired' || isExpired(lot?.expires_at, nowMs)) {\n return {\n variant: 'error',\n labelKey: 'wms.backend.location.items.status.expired',\n labelFallback: 'Expired',\n }\n }\n if (isNearExpiry(lot?.expires_at, nowMs)) {\n return {\n variant: 'warning',\n labelKey: 'wms.backend.location.items.status.nearExpiry',\n labelFallback: 'Near expiry',\n }\n }\n const available = row.quantity_available ?? 0\n if (reorderPoint > 0 && available <= reorderPoint) {\n return {\n variant: 'warning',\n labelKey: 'wms.backend.location.items.status.lowStock',\n labelFallback: 'Low stock',\n }\n }\n const reserved = toNumber(row.quantity_reserved)\n const allocated = toNumber(row.quantity_allocated)\n if (reserved > 0 || allocated > 0) {\n return {\n variant: 'info',\n labelKey: 'wms.backend.location.items.status.reserved',\n labelFallback: 'Reserved',\n }\n }\n return {\n variant: 'success',\n labelKey: 'wms.backend.location.items.status.available',\n labelFallback: 'Available',\n }\n}\n\nfunction matchesItemFilter(\n row: InventoryBalanceRow,\n lot: InventoryLotRow | undefined,\n filter: ItemFilter,\n nowMs: number,\n): boolean {\n switch (filter) {\n case 'all':\n return true\n case 'sellable':\n return (row.quantity_available ?? 0) > 0 && !isExpired(lot?.expires_at, nowMs) && lot?.status !== 'expired'\n case 'picking':\n return toNumber(row.quantity_reserved) > 0 || toNumber(row.quantity_allocated) > 0\n case 'nearExpiry':\n return isNearExpiry(lot?.expires_at, nowMs) || lot?.status === 'expired'\n default:\n return true\n }\n}\n\nfunction movementTypeLabel(type: string, t: ReturnType<typeof useT>): string {\n const key = `wms.backend.location.activity.types.${type}`\n const fallbacks: Record<string, string> = {\n receipt: 'Receive',\n return_receive: 'Receive',\n adjust: 'Adjust',\n transfer: 'Move',\n pick: 'Allocate',\n pack: 'Allocate',\n cycle_count: 'Reconcile',\n putaway: 'Putaway',\n ship: 'Ship',\n }\n return t(key, fallbacks[type] ?? type)\n}\n\nconst movementStatusMap: Record<string, StatusBadgeVariant> = {\n receipt: 'success',\n return_receive: 'success',\n adjust: 'warning',\n transfer: 'info',\n pick: 'info',\n pack: 'info',\n cycle_count: 'neutral',\n putaway: 'info',\n ship: 'success',\n}\n\nfunction formatMovementTitle(\n row: InventoryMovementRow,\n skuLabel: string,\n t: ReturnType<typeof useT>,\n): string {\n const quantity = Math.abs(toNumber(row.quantity))\n const signedQuantity = toNumber(row.quantity)\n switch (row.type) {\n case 'receipt':\n case 'return_receive':\n return t('wms.backend.dashboard.activity.titles.received', 'Received {quantity}\u00D7 {sku}', {\n quantity,\n sku: skuLabel,\n })\n case 'adjust':\n return t('wms.backend.dashboard.activity.titles.adjusted', 'Adjusted {quantity}\u00D7 {sku}', {\n quantity: `${signedQuantity >= 0 ? '+' : ''}${signedQuantity}`,\n sku: skuLabel,\n })\n case 'transfer':\n return t('wms.backend.dashboard.activity.titles.moved', 'Moved {quantity}\u00D7 {sku}', {\n quantity,\n sku: skuLabel,\n })\n case 'pick':\n case 'pack':\n return t('wms.backend.dashboard.activity.titles.allocated', 'Allocated {quantity}\u00D7 {sku}', {\n quantity,\n sku: skuLabel,\n })\n case 'cycle_count':\n return t('wms.backend.dashboard.activity.titles.reconciled', 'Inventory reconciled \u2014 {sku}', {\n sku: skuLabel,\n })\n default:\n return t('wms.backend.dashboard.activity.titles.generic', '{type} {quantity}\u00D7 {sku}', {\n type: row.type ?? 'movement',\n quantity,\n sku: skuLabel,\n })\n }\n}\n\nfunction formatMovementSubtitle(\n row: InventoryMovementRow,\n t: InventoryDisplayTranslator,\n): string | null {\n const reasonLabel = inventoryMovementReasonLabel(\n {\n reasonCode: row.reason_code,\n reason: row.reason,\n movementType: row.type,\n },\n t,\n )\n if (reasonLabel) return reasonLabel\n if (row.reference_type && row.reference_id) return `${row.reference_type} \u00B7 ${row.reference_id}`\n return null\n}\n\nfunction formatMovementLocation(\n row: InventoryMovementRow,\n locationId: string,\n locationCode: string,\n): string {\n const fromId = row.location_from_id?.trim()\n const toId = row.location_to_id?.trim()\n const from = (row.location_from_code ?? '').trim() || fromId || '\u2014'\n const to = (row.location_to_code ?? '').trim() || toId || '\u2014'\n if (row.type === 'transfer' && from !== '\u2014' && to !== '\u2014') return `${from} \u2192 ${to}`\n if (fromId === locationId || toId === locationId) return locationCode || locationId\n return from !== '\u2014' ? from : to\n}\n\nfunction formatRelativeDays(valueMs: number, t: ReturnType<typeof useT>): string {\n const days = Math.max(0, Math.floor((Date.now() - valueMs) / (24 * 60 * 60 * 1000)))\n if (days === 0) return t('wms.backend.location.kpis.lastCounted.today', 'Today')\n return t('wms.backend.location.kpis.lastCounted.daysAgo', '{days}d ago', { days })\n}\n\ntype LocationKpiCardProps = {\n title: string\n caption: string\n value: string\n badgeLabel: string | null\n badgeVariant: StatusBadgeVariant\n ctaLabel: string\n ctaHref?: string\n onCtaClick?: () => void\n progress?: number | null\n}\n\nfunction LocationKpiCard({\n title,\n caption,\n value,\n badgeLabel,\n badgeVariant,\n ctaLabel,\n ctaHref,\n onCtaClick,\n progress,\n}: LocationKpiCardProps) {\n return (\n <section className=\"flex min-h-52 flex-col rounded-lg border bg-card p-5 text-card-foreground shadow-sm\">\n <p className=\"text-sm font-medium\">{title}</p>\n <p className=\"mt-3 text-xs text-muted-foreground\">{caption}</p>\n <div className=\"mt-2 flex items-end gap-3\">\n <p className=\"text-3xl font-semibold tracking-tight\">{value}</p>\n {badgeLabel ? (\n <StatusBadge variant={badgeVariant} dot>\n {badgeLabel}\n </StatusBadge>\n ) : null}\n </div>\n {progress != null ? (\n <Progress\n value={progress}\n max={100}\n tone={progress >= 80 ? 'warning' : 'accent'}\n size=\"sm\"\n className=\"mt-3\"\n />\n ) : null}\n {onCtaClick ? (\n <LinkButton variant=\"primary\" size=\"sm\" className=\"mt-auto pt-4 w-fit\" onClick={onCtaClick}>\n {ctaLabel}\n </LinkButton>\n ) : ctaHref ? (\n <LinkButton asChild variant=\"primary\" size=\"sm\" className=\"mt-auto pt-4 w-fit\">\n <Link href={ctaHref}>{ctaLabel}</Link>\n </LinkButton>\n ) : null}\n </section>\n )\n}\n\ntype WmsLocationDetailPageProps = {\n locationId: string\n}\n\nexport default function WmsLocationDetailPage({ locationId }: WmsLocationDetailPageProps) {\n const t = useT()\n const locale = useLocale()\n const router = useRouter()\n const access = useWmsInventoryMutationAccess()\n const parsedLocationId = locationIdSchema.safeParse(locationId.trim())\n const scopedLocationId = parsedLocationId.success ? parsedLocationId.data : null\n\n const [itemFilter, setItemFilter] = React.useState<ItemFilter>('all')\n const [itemsPage, setItemsPage] = React.useState(1)\n const [selectedBalanceIds, setSelectedBalanceIds] = React.useState<Set<string>>(() => new Set())\n const [adjustOpen, setAdjustOpen] = React.useState(false)\n const [adjustPreset, setAdjustPreset] = React.useState<InventoryMutationPreset>({})\n const [cycleOpen, setCycleOpen] = React.useState(false)\n const [cyclePreset, setCyclePreset] = React.useState<Pick<InventoryMutationPreset, 'warehouseId' | 'locationId'>>({})\n const [editOpen, setEditOpen] = React.useState(false)\n\n const itemsPageSize = 20\n\n const activityTimeFormatter = React.useMemo(\n () =>\n new Intl.DateTimeFormat(locale, {\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n }),\n [locale],\n )\n\n const locationQuery = useQuery({\n queryKey: ['wms-location-detail', 'location', scopedLocationId],\n enabled: Boolean(scopedLocationId),\n queryFn: async () => {\n const params = new URLSearchParams({\n ids: scopedLocationId!,\n page: '1',\n pageSize: '1',\n })\n const call = await apiCall<PagedResponse<LocationRow>>(`/api/wms/locations?${params.toString()}`)\n if (!call.ok) {\n await raiseCrudError(call.response, t('wms.backend.location.errors.location', 'Failed to load location.'))\n }\n return call.result?.items?.[0] ?? null\n },\n })\n\n const parentLocationQuery = useQuery({\n queryKey: ['wms-location-detail', 'parent', locationQuery.data?.parent_id],\n enabled: Boolean(locationQuery.data?.parent_id),\n queryFn: async () => {\n const parentId = locationQuery.data?.parent_id?.trim()\n if (!parentId) return null\n const params = new URLSearchParams({ ids: parentId, page: '1', pageSize: '1' })\n const call = await apiCall<PagedResponse<LocationRow>>(`/api/wms/locations?${params.toString()}`)\n if (!call.ok) return null\n return call.result?.items?.[0] ?? null\n },\n })\n\n const balancesQuery = useQuery({\n queryKey: ['wms-location-detail', 'balances', scopedLocationId],\n enabled: Boolean(scopedLocationId),\n queryFn: async () => {\n const params = new URLSearchParams({\n locationId: scopedLocationId!,\n page: '1',\n pageSize: '100',\n sortField: 'updatedAt',\n sortDir: 'desc',\n })\n const call = await apiCall<PagedResponse<InventoryBalanceRow>>(\n `/api/wms/inventory/balances?${params.toString()}`,\n )\n if (!call.ok) {\n await raiseCrudError(call.response, t('wms.backend.location.errors.balances', 'Failed to load items in location.'))\n }\n return call.result ?? { items: [], total: 0, totalPages: 1 }\n },\n })\n\n const variantIds = React.useMemo(() => {\n const ids = new Set<string>()\n for (const row of balancesQuery.data?.items ?? []) {\n const variantId = row.catalog_variant_id?.trim()\n if (variantId) ids.add(variantId)\n }\n return Array.from(ids)\n }, [balancesQuery.data?.items])\n\n const profilesQuery = useQuery({\n queryKey: ['wms-location-detail', 'profiles', variantIds],\n enabled: variantIds.length > 0,\n queryFn: async () => {\n const params = new URLSearchParams({ page: '1', pageSize: '100' })\n const call = await apiCall<PagedResponse<InventoryProfileRow>>(\n `/api/wms/inventory-profiles?${params.toString()}`,\n )\n if (!call.ok) return []\n return call.result?.items ?? []\n },\n })\n\n const lotsQuery = useQuery({\n queryKey: ['wms-location-detail', 'lots', variantIds],\n enabled: variantIds.length > 0,\n queryFn: async () => {\n const params = new URLSearchParams({ page: '1', pageSize: '100' })\n const call = await apiCall<PagedResponse<InventoryLotRow>>(`/api/wms/lots?${params.toString()}`)\n if (!call.ok) {\n await raiseCrudError(call.response, t('wms.backend.location.errors.lots', 'Failed to load lots.'))\n }\n return call.result?.items ?? []\n },\n })\n\n const warehouseId = locationQuery.data?.warehouse_id?.trim() ?? ''\n\n const movementsQuery = useQuery({\n queryKey: ['wms-location-detail', 'movements', scopedLocationId, warehouseId],\n enabled: Boolean(scopedLocationId && warehouseId),\n queryFn: async () => {\n const params = new URLSearchParams({\n warehouseId,\n locationId: scopedLocationId!,\n page: '1',\n pageSize: '50',\n sortField: 'performedAt',\n sortDir: 'desc',\n })\n const call = await apiCall<PagedResponse<InventoryMovementRow>>(\n `/api/wms/inventory/movements?${params.toString()}`,\n )\n if (!call.ok) {\n await raiseCrudError(call.response, t('wms.backend.location.errors.movements', 'Failed to load recent activity.'))\n }\n return call.result?.items ?? []\n },\n })\n\n const lotById = React.useMemo(() => {\n const map = new Map<string, InventoryLotRow>()\n for (const lot of lotsQuery.data ?? []) {\n if (lot.id) map.set(lot.id, lot)\n }\n return map\n }, [lotsQuery.data])\n\n const reorderPointByVariant = React.useMemo(() => {\n const map = new Map<string, number>()\n for (const profile of profilesQuery.data ?? []) {\n const variantId = profile.catalog_variant_id?.trim()\n if (!variantId) continue\n map.set(variantId, toNumber(profile.reorder_point))\n }\n return map\n }, [profilesQuery.data])\n\n const locationCode = (locationQuery.data?.code ?? '').trim() || scopedLocationId || '\u2014'\n const pageTitle = locationCode\n\n const locationTypeLabel = React.useMemo(() => {\n const type = (locationQuery.data?.type ?? '').trim()\n if (!type) return null\n return t(`wms.backend.location.types.${type}`, type)\n }, [locationQuery.data?.type, t])\n\n const nowMs = React.useMemo(() => Date.now(), [])\n\n const totals = React.useMemo(() => {\n const items = balancesQuery.data?.items ?? []\n let onHand = 0\n let reserved = 0\n let allocated = 0\n const skuIds = new Set<string>()\n const lotIds = new Set<string>()\n for (const row of items) {\n onHand += toNumber(row.quantity_on_hand)\n reserved += toNumber(row.quantity_reserved)\n allocated += toNumber(row.quantity_allocated)\n const variantId = row.catalog_variant_id?.trim()\n if (variantId && toNumber(row.quantity_on_hand) > 0) skuIds.add(variantId)\n const lotId = row.lot_id?.trim()\n if (lotId && toNumber(row.quantity_on_hand) > 0) lotIds.add(lotId)\n }\n return {\n onHand,\n reserved,\n allocated,\n pendingPicks: reserved + allocated,\n skuCount: skuIds.size,\n lotCount: lotIds.size,\n }\n }, [balancesQuery.data?.items])\n\n const capacityUsedPercent = React.useMemo(() => {\n const capacity = toNumber(locationQuery.data?.capacity_units)\n if (capacity <= 0) return null\n return Math.min(100, Math.round((totals.onHand / capacity) * 100))\n }, [locationQuery.data?.capacity_units, totals.onHand])\n\n const lastCounted = React.useMemo(() => {\n const cycleMovements = (movementsQuery.data ?? []).filter((row) => row.type === 'cycle_count')\n if (cycleMovements.length === 0) return null\n const latest = cycleMovements[0]\n const raw = latest.performed_at ?? latest.received_at\n if (!raw) return null\n const ts = new Date(raw).getTime()\n if (Number.isNaN(ts)) return null\n return { ts, label: formatRelativeDays(ts, t) }\n }, [movementsQuery.data, t])\n\n const filteredBalances = React.useMemo(() => {\n const items = balancesQuery.data?.items ?? []\n return items.filter((row) =>\n matchesItemFilter(row, row.lot_id ? lotById.get(row.lot_id) : undefined, itemFilter, nowMs),\n )\n }, [balancesQuery.data?.items, itemFilter, lotById, nowMs])\n\n const itemsTotal = filteredBalances.length\n const itemsTotalPages = Math.max(1, Math.ceil(itemsTotal / itemsPageSize))\n const pagedBalances = React.useMemo(() => {\n const start = (itemsPage - 1) * itemsPageSize\n return filteredBalances.slice(start, start + itemsPageSize)\n }, [filteredBalances, itemsPage, itemsPageSize])\n\n React.useEffect(() => {\n setItemsPage(1)\n setSelectedBalanceIds(new Set())\n }, [itemFilter])\n\n const selectedBalances = React.useMemo(\n () => filteredBalances.filter((row) => selectedBalanceIds.has(row.id)),\n [filteredBalances, selectedBalanceIds],\n )\n\n const movementsHref = React.useMemo(() => {\n const params = new URLSearchParams()\n if (warehouseId) params.set('warehouseId', warehouseId)\n const query = params.toString()\n return query ? `/backend/wms/movements?${query}` : '/backend/wms/movements'\n }, [warehouseId])\n\n const inventoryConsoleHref = React.useMemo(() => {\n if (!scopedLocationId) return '/backend/wms/inventory'\n return `/backend/wms/inventory?locationId=${encodeURIComponent(scopedLocationId)}`\n }, [scopedLocationId])\n\n const locationsHref = '/backend/wms/locations'\n\n const openAdjustDialog = React.useCallback((preset: InventoryMutationPreset = {}) => {\n setAdjustPreset(preset)\n setAdjustOpen(true)\n }, [])\n\n const openCycleCountDialog = React.useCallback(\n (preset: Pick<InventoryMutationPreset, 'warehouseId' | 'locationId'> = {}) => {\n setCyclePreset(preset)\n setCycleOpen(true)\n },\n [],\n )\n\n const resolveMutationContext = React.useCallback((): InventoryMutationPreset => {\n const firstSelected = selectedBalances[0]\n const lotId =\n selectedBalances.length === 1 ? firstSelected?.lot_id?.trim() || undefined : undefined\n const catalogVariantId =\n selectedBalances.length === 1 ? firstSelected?.catalog_variant_id?.trim() || undefined : undefined\n return {\n warehouseId: warehouseId || undefined,\n locationId: scopedLocationId || undefined,\n catalogVariantId,\n lotId,\n }\n }, [scopedLocationId, selectedBalances, warehouseId])\n\n const toggleBalanceSelection = React.useCallback((balanceId: string, selected: boolean) => {\n setSelectedBalanceIds((current) => {\n const next = new Set(current)\n if (selected) next.add(balanceId)\n else next.delete(balanceId)\n return next\n })\n }, [])\n\n const togglePageSelection = React.useCallback((selected: boolean) => {\n setSelectedBalanceIds((current) => {\n const next = new Set(current)\n for (const row of pagedBalances) {\n if (selected) next.add(row.id)\n else next.delete(row.id)\n }\n return next\n })\n }, [pagedBalances])\n\n const pageSelectionState = React.useMemo(() => {\n if (pagedBalances.length === 0) {\n return { checked: false, indeterminate: false }\n }\n const selectedOnPage = pagedBalances.filter((row) => selectedBalanceIds.has(row.id)).length\n return {\n checked: selectedOnPage === pagedBalances.length,\n indeterminate: selectedOnPage > 0 && selectedOnPage < pagedBalances.length,\n }\n }, [pagedBalances, selectedBalanceIds])\n\n const handleExportItemsCsv = React.useCallback(() => {\n const headers = [\n t('wms.backend.location.items.columns.sku', 'SKU'),\n t('wms.backend.location.items.columns.variant', 'Variant name'),\n t('wms.backend.location.items.columns.lot', 'Lot'),\n t('wms.backend.location.items.columns.onHand', 'On hand'),\n t('wms.backend.location.items.columns.reserved', 'Reserved'),\n t('wms.backend.location.items.columns.status', 'Status'),\n ]\n const rows = filteredBalances.map((row) => {\n const lot = row.lot_id ? lotById.get(row.lot_id) : undefined\n const reorderPoint = reorderPointByVariant.get(row.catalog_variant_id?.trim() ?? '') ?? 0\n const status = resolveItemStatus(row, lot, reorderPoint, nowMs)\n return [\n formatSkuLabel(row),\n formatVariantName(row),\n formatLotLabel(lot, locale),\n String(toNumber(row.quantity_on_hand)),\n String(toNumber(row.quantity_reserved)),\n t(status.labelKey, status.labelFallback),\n ]\n })\n const safeCode = pageTitle.replace(/[^\\w.-]+/g, '-').replace(/^-+|-+$/g, '') || 'location'\n downloadCsvFile(`${safeCode}-items.csv`, [headers, ...rows])\n }, [filteredBalances, locale, lotById, nowMs, pageTitle, reorderPointByVariant, t])\n\n const warehouseLabel = formatWarehouseLabel(locationQuery.data ?? {})\n\n const subtitleParts = [\n locationTypeLabel ? `${locationTypeLabel} ${locationCode}` : null,\n parentLocationQuery.data?.code?.trim()\n ? t('wms.backend.location.header.zone', 'Zone {code}', {\n code: parentLocationQuery.data.code.trim(),\n })\n : null,\n warehouseLabel !== '\u2014' ? warehouseLabel : null,\n locationQuery.data?.is_active === false\n ? t('wms.backend.location.header.inactive', 'Inactive')\n : null,\n ].filter(Boolean)\n\n const itemColumns = React.useMemo<ColumnDef<InventoryBalanceRow>[]>(\n () => [\n {\n id: 'select',\n header: () => (\n <Checkbox\n aria-label={t('wms.backend.location.items.columns.select', 'Select')}\n checked={pageSelectionState.indeterminate ? 'indeterminate' : pageSelectionState.checked}\n onCheckedChange={(checked) => togglePageSelection(checked === true)}\n />\n ),\n cell: ({ row }) => (\n <Checkbox\n aria-label={t('wms.backend.location.items.columns.select', 'Select')}\n checked={selectedBalanceIds.has(row.original.id)}\n onCheckedChange={(checked) => toggleBalanceSelection(row.original.id, checked === true)}\n />\n ),\n meta: { maxWidth: '2.75rem' },\n },\n {\n accessorKey: 'variant_sku',\n header: t('wms.backend.location.items.columns.sku', 'SKU'),\n cell: ({ row }) => {\n const variantId = row.original.catalog_variant_id?.trim()\n const label = formatSkuLabel(row.original)\n if (!variantId) return label\n return (\n <Link\n href={`/backend/wms/sku/${encodeURIComponent(variantId)}`}\n className=\"font-medium text-primary hover:underline\"\n >\n {label}\n </Link>\n )\n },\n },\n {\n accessorKey: 'variant_name',\n header: t('wms.backend.location.items.columns.variant', 'Variant name'),\n cell: ({ row }) => formatVariantName(row.original),\n },\n {\n accessorKey: 'lot_id',\n header: t('wms.backend.location.items.columns.lot', 'Lot'),\n cell: ({ row }) => {\n const lotId = row.original.lot_id?.trim()\n const lot = lotId ? lotById.get(lotId) : undefined\n const label = formatLotLabel(lot, locale)\n if (!lotId || label === '\u2014') return label\n return (\n <Link\n href={`/backend/wms/lot/${encodeURIComponent(lotId)}`}\n className=\"font-medium text-primary hover:underline\"\n >\n {label}\n </Link>\n )\n },\n },\n {\n accessorKey: 'quantity_on_hand',\n header: t('wms.backend.location.items.columns.onHand', 'On hand'),\n cell: ({ row }) => String(toNumber(row.original.quantity_on_hand)),\n },\n {\n accessorKey: 'quantity_reserved',\n header: t('wms.backend.location.items.columns.reserved', 'Reserved'),\n cell: ({ row }) => String(toNumber(row.original.quantity_reserved)),\n },\n {\n id: 'status',\n header: t('wms.backend.location.items.columns.status', 'Status'),\n cell: ({ row }) => {\n const lot = row.original.lot_id ? lotById.get(row.original.lot_id) : undefined\n const reorderPoint = reorderPointByVariant.get(row.original.catalog_variant_id?.trim() ?? '') ?? 0\n const status = resolveItemStatus(row.original, lot, reorderPoint, nowMs)\n return (\n <StatusBadge variant={status.variant} dot>\n {t(status.labelKey, status.labelFallback)}\n </StatusBadge>\n )\n },\n },\n ],\n [\n locale,\n lotById,\n nowMs,\n pageSelectionState.checked,\n pageSelectionState.indeterminate,\n reorderPointByVariant,\n selectedBalanceIds,\n t,\n toggleBalanceSelection,\n togglePageSelection,\n ],\n )\n\n const activityColumns = React.useMemo<ColumnDef<InventoryMovementRow>[]>(\n () => [\n {\n accessorKey: 'type',\n header: t('wms.backend.location.activity.columns.event', 'Event'),\n cell: ({ row }) => {\n const type = row.original.type ?? 'movement'\n return (\n <StatusBadge variant={movementStatusMap[type] ?? 'neutral'}>\n {movementTypeLabel(type, t)}\n </StatusBadge>\n )\n },\n },\n {\n id: 'details',\n header: t('wms.backend.location.activity.columns.details', 'Details'),\n cell: ({ row }) => {\n const skuLabel =\n (row.original.variant_sku ?? '').trim() ||\n (row.original.variant_name ?? '').trim() ||\n row.original.catalog_variant_id ||\n '\u2014'\n const subtitle = formatMovementSubtitle(row.original, t)\n return (\n <div className=\"space-y-0.5\">\n <p className=\"text-sm font-medium\">{formatMovementTitle(row.original, skuLabel, t)}</p>\n {subtitle ? <p className=\"text-xs text-muted-foreground\">{subtitle}</p> : null}\n </div>\n )\n },\n },\n {\n id: 'location',\n header: t('wms.backend.location.activity.columns.location', 'Location'),\n cell: ({ row }) => (\n <span className=\"text-sm text-muted-foreground\">\n {formatMovementLocation(row.original, scopedLocationId ?? '', locationCode)}\n </span>\n ),\n },\n {\n id: 'time',\n header: t('wms.backend.location.activity.columns.time', 'Time'),\n cell: ({ row }) => {\n const raw = row.original.performed_at ?? row.original.received_at\n if (!raw) return '\u2014'\n const date = new Date(raw)\n if (Number.isNaN(date.getTime())) return raw\n return activityTimeFormatter.format(date)\n },\n meta: { maxWidth: '6rem' },\n },\n ],\n [activityTimeFormatter, locationCode, scopedLocationId, t],\n )\n\n const filterChips: Array<{ id: ItemFilter; label: string }> = [\n { id: 'all', label: t('wms.backend.location.items.filters.all', 'All items') },\n { id: 'sellable', label: t('wms.backend.location.items.filters.sellable', 'Sellable') },\n { id: 'picking', label: t('wms.backend.location.items.filters.picking', 'Picking') },\n { id: 'nearExpiry', label: t('wms.backend.location.items.filters.nearExpiry', 'Near expiry') },\n ]\n\n const isLoading =\n locationQuery.isLoading ||\n balancesQuery.isLoading ||\n lotsQuery.isLoading ||\n movementsQuery.isLoading ||\n parentLocationQuery.isLoading\n\n const hasError =\n locationQuery.isError ||\n balancesQuery.isError ||\n lotsQuery.isError ||\n movementsQuery.isError\n\n if (!scopedLocationId) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={t('wms.backend.location.errors.invalidId', 'Invalid location identifier.')}\n action={(\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => router.push(locationsHref)}>\n {t('wms.backend.location.actions.backToLocations', 'Back to locations')}\n </Button>\n )}\n />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <LinkButton asChild variant=\"gray\" size=\"sm\" className=\"w-fit px-0 text-muted-foreground hover:text-foreground\">\n <Link href={locationsHref}>\n <ArrowLeft className=\"size-4\" />\n {t('wms.backend.location.actions.backToLocations', 'Back to locations')}\n </Link>\n </LinkButton>\n\n <PageHeader\n title={pageTitle}\n description={subtitleParts.join(' \u00B7 ')}\n actions={(\n <>\n {warehouseId ? (\n <Select value={warehouseId} disabled>\n <SelectTrigger className=\"w-full max-w-xs sm:w-56\">\n <Warehouse className=\"mr-2 size-4 shrink-0 text-muted-foreground\" />\n <SelectValue placeholder={warehouseLabel} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={warehouseId}>{warehouseLabel}</SelectItem>\n </SelectContent>\n </Select>\n ) : null}\n {access.canAdjust ? (\n <Button type=\"button\" variant=\"outline\" onClick={() => openAdjustDialog(resolveMutationContext())}>\n <SlidersHorizontal className=\"size-4\" />\n {t('wms.backend.location.actions.adjust', 'Adjust stock')}\n </Button>\n ) : null}\n </>\n )}\n />\n\n {isLoading ? (\n <LoadingMessage label={t('wms.backend.location.loading', 'Loading location view\u2026')} />\n ) : null}\n\n {hasError ? (\n <ErrorMessage label={t('wms.backend.location.errors.load', 'Failed to load location view.')} />\n ) : null}\n\n {!isLoading && !hasError && locationQuery.data ? (\n <>\n <section className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5\">\n <LocationKpiCard\n title={t('wms.backend.location.kpis.skusOccupied.title', 'SKUs occupied')}\n caption={t('wms.backend.location.kpis.skusOccupied.caption', 'Distinct variants with stock')}\n value={String(totals.skuCount)}\n badgeLabel={\n totals.skuCount > 0\n ? t('wms.backend.location.kpis.skusOccupied.badge', '{count} active', { count: totals.skuCount })\n : null\n }\n badgeVariant=\"info\"\n ctaLabel={t('wms.backend.location.kpis.skusOccupied.cta', 'View items')}\n ctaHref=\"#location-items\"\n />\n <LocationKpiCard\n title={t('wms.backend.location.kpis.capacityUsed.title', 'Capacity used')}\n caption={t('wms.backend.location.kpis.capacityUsed.caption', 'On hand vs location capacity')}\n value={capacityUsedPercent === null ? '\u2014' : `${capacityUsedPercent}%`}\n badgeLabel={\n capacityUsedPercent !== null && capacityUsedPercent >= 80\n ? t('wms.backend.location.kpis.capacityUsed.badgeHigh', 'Near limit')\n : capacityUsedPercent !== null\n ? t('wms.backend.location.kpis.capacityUsed.badgeOk', 'Within capacity')\n : null\n }\n badgeVariant={capacityUsedPercent !== null && capacityUsedPercent >= 80 ? 'warning' : 'success'}\n ctaLabel={t('wms.backend.location.kpis.capacityUsed.cta', 'Edit location')}\n onCtaClick={access.canManageLocations ? () => setEditOpen(true) : undefined}\n progress={capacityUsedPercent}\n />\n <LocationKpiCard\n title={t('wms.backend.location.kpis.activeLots.title', 'Active lots')}\n caption={t('wms.backend.location.kpis.activeLots.caption', 'Lots with on-hand quantity')}\n value={String(totals.lotCount)}\n badgeLabel={\n totals.lotCount > 0\n ? t('wms.backend.location.kpis.activeLots.badge', '{count} lots', { count: totals.lotCount })\n : null\n }\n badgeVariant=\"neutral\"\n ctaLabel={t('wms.backend.location.kpis.activeLots.cta', 'View items')}\n ctaHref=\"#location-items\"\n />\n <LocationKpiCard\n title={t('wms.backend.location.kpis.pendingPicks.title', 'Pending picks')}\n caption={t('wms.backend.location.kpis.pendingPicks.caption', 'Reserved and allocated units')}\n value={String(totals.pendingPicks)}\n badgeLabel={\n totals.pendingPicks > 0\n ? t('wms.backend.location.kpis.pendingPicks.badge', '{count} units', { count: totals.pendingPicks })\n : null\n }\n badgeVariant=\"info\"\n ctaLabel={t('wms.backend.location.kpis.pendingPicks.cta', 'View reservations')}\n ctaHref=\"/backend/wms/reservations\"\n />\n <LocationKpiCard\n title={t('wms.backend.location.kpis.lastCounted.title', 'Last counted')}\n caption={t('wms.backend.location.kpis.lastCounted.caption', 'Most recent cycle count')}\n value={lastCounted?.label ?? '\u2014'}\n badgeLabel={\n lastCounted && nowMs - lastCounted.ts > 14 * 24 * 60 * 60 * 1000\n ? t('wms.backend.location.kpis.lastCounted.badgeStale', 'Due soon')\n : lastCounted\n ? t('wms.backend.location.kpis.lastCounted.badgeRecent', 'Recent')\n : t('wms.backend.location.kpis.lastCounted.badgeNever', 'Never')\n }\n badgeVariant={\n !lastCounted || nowMs - (lastCounted?.ts ?? 0) > 14 * 24 * 60 * 60 * 1000\n ? 'warning'\n : 'success'\n }\n ctaLabel={t('wms.backend.location.kpis.lastCounted.cta', 'Start cycle count')}\n ctaHref=\"#location-items\"\n />\n </section>\n\n <section\n id=\"location-items\"\n className=\"rounded-lg border bg-card text-card-foreground shadow-sm\"\n >\n <div className=\"border-b px-5 py-4\">\n <h2 className=\"text-base font-semibold\">\n {t('wms.backend.location.items.title', 'Items in this location')}\n </h2>\n </div>\n <div className=\"flex flex-col gap-4 border-b px-5 py-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap gap-2\">\n {filterChips.map((chip) => (\n <Button\n key={chip.id}\n type=\"button\"\n size=\"sm\"\n variant={itemFilter === chip.id ? 'default' : 'outline'}\n className={cn('rounded-full', itemFilter === chip.id && 'shadow-sm')}\n onClick={() => setItemFilter(chip.id)}\n >\n {chip.label}\n </Button>\n ))}\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={handleExportItemsCsv}>\n <Download className=\"size-4\" />\n {t('wms.backend.location.items.actions.exportCsv', 'Export CSV')}\n </Button>\n {access.canCycleCount ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() =>\n openCycleCountDialog({\n warehouseId: warehouseId || undefined,\n locationId: scopedLocationId,\n })\n }\n >\n {t('wms.backend.location.items.actions.cycleCountZone', 'Cycle count zone')}\n </Button>\n ) : null}\n {access.canAdjust && selectedBalances.length > 0 ? (\n <Button\n type=\"button\"\n variant=\"default\"\n onClick={() => openAdjustDialog(resolveMutationContext())}\n >\n {t('wms.backend.location.items.actions.adjustSelected', 'Adjust selected ({count})', { count: selectedBalances.length })}\n </Button>\n ) : null}\n </div>\n </div>\n <DataTable<InventoryBalanceRow>\n embedded\n columns={itemColumns}\n data={pagedBalances}\n disableRowClick\n entityId={E.wms.inventory_balance}\n perspective={{ tableId: extensionPoints.hosts.locationItemsTable.tableId }}\n pagination={{\n page: itemsPage,\n pageSize: itemsPageSize,\n total: itemsTotal,\n totalPages: itemsTotalPages,\n onPageChange: setItemsPage,\n }}\n emptyState={(\n <EmptyState\n title={t('wms.backend.location.items.empty.title', 'No items in this view')}\n description={t(\n 'wms.backend.location.items.empty.description',\n 'Try another filter or post inventory through receipts or adjustments.',\n )}\n />\n )}\n />\n {itemsTotal > 0 ? (\n <div className=\"flex flex-col gap-2 border-t px-5 py-3 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between\">\n <span>\n {t('wms.backend.location.items.footer.showing', 'Showing {shown} of {total} items', {\n shown: pagedBalances.length,\n total: itemsTotal,\n })}\n </span>\n {balancesQuery.data && balancesQuery.data.total > itemsTotal ? (\n <LinkButton asChild variant=\"gray\" size=\"sm\" className=\"h-auto px-0\">\n <Link href={inventoryConsoleHref}>\n {t('wms.backend.location.items.footer.viewAll', 'View all items \u2192')}\n </Link>\n </LinkButton>\n ) : null}\n </div>\n ) : null}\n </section>\n\n <DataTable<InventoryMovementRow>\n title={t('wms.backend.location.activity.title', 'Recent activity')}\n columns={activityColumns}\n data={movementsQuery.data ?? []}\n disableRowClick\n entityId={E.wms.inventory_movement}\n perspective={{ tableId: extensionPoints.hosts.locationActivityTable.tableId }}\n emptyState={t('wms.backend.location.activity.empty', 'No recent movements for this location.')}\n actions={(\n <Button asChild type=\"button\" variant=\"ghost\" size=\"sm\">\n <Link href={movementsHref}>\n {t('wms.backend.location.activity.viewAll', 'View all movements \u2192')}\n </Link>\n </Button>\n )}\n />\n\n <section className=\"flex flex-col gap-4 rounded-lg border bg-card px-5 py-4 shadow-sm lg:flex-row lg:items-center lg:justify-between\">\n <div>\n <h2 className=\"text-base font-semibold\">\n {t('wms.backend.location.quickActions.title', 'Quick actions')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'wms.backend.location.quickActions.description',\n 'Run common inventory actions without leaving this location view',\n )}\n </p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {access.canAdjust ? (\n <Button type=\"button\" variant=\"default\" onClick={() => openAdjustDialog(resolveMutationContext())}>\n <SlidersHorizontal className=\"size-4\" />\n {t('wms.backend.location.quickActions.adjust', 'Adjust inventory')}\n </Button>\n ) : null}\n {access.canAdjust ? (\n <Button asChild type=\"button\" variant=\"outline\">\n <Link href={inventoryConsoleHref}>\n <ArrowLeftRight className=\"size-4\" />\n {t('wms.backend.location.quickActions.move', 'Move stock')}\n </Link>\n </Button>\n ) : null}\n {access.canCycleCount ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() =>\n openCycleCountDialog({\n warehouseId: warehouseId || undefined,\n locationId: scopedLocationId,\n })\n }\n >\n <ClipboardList className=\"size-4\" />\n {t('wms.backend.location.quickActions.cycleCount', 'Cycle count')}\n </Button>\n ) : null}\n <Button asChild type=\"button\" variant=\"outline\">\n <Link href={movementsHref}>\n {t('wms.backend.location.quickActions.openLedger', 'Open ledger')}\n <ExternalLink className=\"size-4\" />\n </Link>\n </Button>\n </div>\n </section>\n </>\n ) : null}\n\n {!isLoading && !hasError && !locationQuery.data ? (\n <RecordNotFoundState\n label={t('wms.backend.location.errors.notFound', 'Location not found.')}\n backHref={locationsHref}\n backLabel={t('wms.backend.location.actions.backToLocations', 'Back to locations')}\n />\n ) : null}\n </PageBody>\n\n {access.canAdjust && scopedLocationId ? (\n <AdjustInventoryDialog\n open={adjustOpen}\n onOpenChange={setAdjustOpen}\n access={access}\n initialWarehouseId={adjustPreset.warehouseId ?? warehouseId}\n initialLocationId={adjustPreset.locationId ?? scopedLocationId}\n initialCatalogVariantId={adjustPreset.catalogVariantId}\n initialLotId={adjustPreset.lotId}\n />\n ) : null}\n {access.canCycleCount ? (\n <CycleCountWizardDialog\n open={cycleOpen}\n onOpenChange={setCycleOpen}\n access={access}\n initialWarehouseId={cyclePreset.warehouseId ?? warehouseId}\n initialLocationId={cyclePreset.locationId ?? scopedLocationId ?? undefined}\n />\n ) : null}\n {access.canManageLocations && locationQuery.data ? (\n <LocationEditDialog\n open={editOpen}\n onOpenChange={setEditOpen}\n mode=\"edit\"\n row={locationQuery.data}\n onSaved={async () => {\n await locationQuery.refetch()\n }}\n />\n ) : null}\n </Page>\n )\n}\n"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/wms/extension-points'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { z } from 'zod'\nimport { useQuery } from '@tanstack/react-query'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport {\n ArrowLeft,\n ArrowLeftRight,\n ClipboardList,\n Download,\n ExternalLink,\n SlidersHorizontal,\n Warehouse,\n} from 'lucide-react'\nimport { Page, PageBody, PageHeader } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { EmptyState } from '@open-mercato/ui/backend/EmptyState'\nimport { ErrorMessage, LoadingMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { LinkButton } from '@open-mercato/ui/primitives/link-button'\nimport { Progress } from '@open-mercato/ui/primitives/progress'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { StatusBadge, type StatusBadgeVariant } from '@open-mercato/ui/primitives/status-badge'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n inventoryMovementReasonLabel,\n type InventoryDisplayTranslator,\n} from '../../lib/inventoryDisplayUi'\nimport { AdjustInventoryDialog } from './AdjustInventoryDialog'\nimport { CycleCountWizardDialog } from './CycleCountWizardDialog'\nimport { LocationEditDialog } from './LocationEditDialog'\nimport { useWmsInventoryMutationAccess } from './useWmsInventoryMutationAccess'\n\nconst locationIdSchema = z.string().uuid()\n\ntype PagedResponse<T> = {\n items: T[]\n total: number\n totalPages: number\n}\n\ntype LocationRow = {\n id: string\n code?: string | null\n type?: string | null\n parent_id?: string | null\n warehouse_id?: string | null\n warehouse_name?: string | null\n warehouse_code?: string | null\n is_active?: boolean | null\n capacity_units?: string | number | null\n capacity_weight?: string | number | null\n updated_at?: string | null\n}\n\ntype InventoryProfileRow = {\n catalog_variant_id?: string | null\n reorder_point?: string | number | null\n}\n\ntype InventoryBalanceRow = {\n id: string\n warehouse_id?: string | null\n warehouse_name?: string | null\n warehouse_code?: string | null\n location_id?: string | null\n location_code?: string | null\n catalog_variant_id?: string | null\n variant_name?: string | null\n variant_sku?: string | null\n lot_id?: string | null\n quantity_on_hand?: string | number | null\n quantity_reserved?: string | number | null\n quantity_allocated?: string | number | null\n quantity_available?: number | null\n}\n\ntype InventoryLotRow = {\n id: string\n lot_number?: string | null\n expires_at?: string | null\n status?: string | null\n}\n\ntype InventoryMovementRow = {\n id: string\n warehouse_id?: string | null\n warehouse_name?: string | null\n warehouse_code?: string | null\n location_from_id?: string | null\n location_from_code?: string | null\n location_to_id?: string | null\n location_to_code?: string | null\n catalog_variant_id?: string | null\n variant_sku?: string | null\n variant_name?: string | null\n quantity?: string | number | null\n type?: string | null\n reference_type?: string | null\n reference_id?: string | null\n reason?: string | null\n reason_code?: string | null\n performed_at?: string | null\n received_at?: string | null\n}\n\ntype InventoryMutationPreset = {\n warehouseId?: string\n locationId?: string\n catalogVariantId?: string\n lotId?: string\n}\n\ntype ItemFilter = 'all' | 'sellable' | 'picking' | 'nearExpiry'\n\nconst NEAR_EXPIRY_MS = 30 * 24 * 60 * 60 * 1000\n\nfunction escapeCsvCell(value: string | number): string {\n const str = String(value ?? '')\n if (/[\",\\n]/.test(str)) return `\"${str.replace(/\"/g, '\"\"')}\"`\n return str\n}\n\nfunction downloadCsvFile(filename: string, rows: string[][]) {\n const csv = rows.map((row) => row.map(escapeCsvCell).join(',')).join('\\n')\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })\n const url = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = filename\n anchor.click()\n URL.revokeObjectURL(url)\n}\n\nfunction toNumber(value: string | number | null | undefined): number {\n const parsed = Number(value ?? 0)\n return Number.isFinite(parsed) ? parsed : 0\n}\n\nfunction formatWarehouseLabel(row: {\n warehouse_name?: string | null\n warehouse_code?: string | null\n warehouse_id?: string | null\n}): string {\n const code = (row.warehouse_code ?? '').trim()\n const name = (row.warehouse_name ?? '').trim()\n if (code && name) return `${code} \u00B7 ${name}`\n return name || code || row.warehouse_id || '\u2014'\n}\n\nfunction formatSkuLabel(row: InventoryBalanceRow): string {\n const sku = (row.variant_sku ?? '').trim()\n const name = (row.variant_name ?? '').trim()\n if (sku) return sku\n if (name) return name\n return row.catalog_variant_id || '\u2014'\n}\n\nfunction formatVariantName(row: InventoryBalanceRow): string {\n return (row.variant_name ?? '').trim() || '\u2014'\n}\n\nfunction formatLotLabel(\n lot: InventoryLotRow | undefined,\n locale: string,\n): string {\n if (!lot) return '\u2014'\n const number = (lot.lot_number ?? '').trim() || lot.id\n if (!lot.expires_at) return number\n const expires = new Date(lot.expires_at)\n if (Number.isNaN(expires.getTime())) return number\n const expLabel = new Intl.DateTimeFormat(locale, { month: '2-digit', year: '2-digit' }).format(expires)\n return `${number} \u00B7 exp ${expLabel}`\n}\n\nfunction isNearExpiry(expiresAt: string | null | undefined, nowMs: number): boolean {\n if (!expiresAt) return false\n const expires = new Date(expiresAt).getTime()\n if (Number.isNaN(expires)) return false\n return expires > nowMs && expires - nowMs <= NEAR_EXPIRY_MS\n}\n\nfunction isExpired(expiresAt: string | null | undefined, nowMs: number): boolean {\n if (!expiresAt) return false\n const expires = new Date(expiresAt).getTime()\n return !Number.isNaN(expires) && expires <= nowMs\n}\n\nfunction resolveItemStatus(\n row: InventoryBalanceRow,\n lot: InventoryLotRow | undefined,\n reorderPoint: number,\n nowMs: number,\n): { variant: StatusBadgeVariant; labelKey: string; labelFallback: string } {\n if (lot?.status === 'expired' || isExpired(lot?.expires_at, nowMs)) {\n return {\n variant: 'error',\n labelKey: 'wms.backend.location.items.status.expired',\n labelFallback: 'Expired',\n }\n }\n if (isNearExpiry(lot?.expires_at, nowMs)) {\n return {\n variant: 'warning',\n labelKey: 'wms.backend.location.items.status.nearExpiry',\n labelFallback: 'Near expiry',\n }\n }\n const available = row.quantity_available ?? 0\n if (reorderPoint > 0 && available <= reorderPoint) {\n return {\n variant: 'warning',\n labelKey: 'wms.backend.location.items.status.lowStock',\n labelFallback: 'Low stock',\n }\n }\n const reserved = toNumber(row.quantity_reserved)\n const allocated = toNumber(row.quantity_allocated)\n if (reserved > 0 || allocated > 0) {\n return {\n variant: 'info',\n labelKey: 'wms.backend.location.items.status.reserved',\n labelFallback: 'Reserved',\n }\n }\n return {\n variant: 'success',\n labelKey: 'wms.backend.location.items.status.available',\n labelFallback: 'Available',\n }\n}\n\nfunction matchesItemFilter(\n row: InventoryBalanceRow,\n lot: InventoryLotRow | undefined,\n filter: ItemFilter,\n nowMs: number,\n): boolean {\n switch (filter) {\n case 'all':\n return true\n case 'sellable':\n return (row.quantity_available ?? 0) > 0 && !isExpired(lot?.expires_at, nowMs) && lot?.status !== 'expired'\n case 'picking':\n return toNumber(row.quantity_reserved) > 0 || toNumber(row.quantity_allocated) > 0\n case 'nearExpiry':\n return isNearExpiry(lot?.expires_at, nowMs) || lot?.status === 'expired'\n default:\n return true\n }\n}\n\nfunction movementTypeLabel(type: string, t: ReturnType<typeof useT>): string {\n const key = `wms.backend.location.activity.types.${type}`\n const fallbacks: Record<string, string> = {\n receipt: 'Receive',\n return_receive: 'Receive',\n adjust: 'Adjust',\n transfer: 'Move',\n pick: 'Allocate',\n pack: 'Allocate',\n cycle_count: 'Reconcile',\n putaway: 'Putaway',\n ship: 'Ship',\n }\n return t(key, fallbacks[type] ?? type)\n}\n\nconst movementStatusMap: Record<string, StatusBadgeVariant> = {\n receipt: 'success',\n return_receive: 'success',\n adjust: 'warning',\n transfer: 'info',\n pick: 'info',\n pack: 'info',\n cycle_count: 'neutral',\n putaway: 'info',\n ship: 'success',\n}\n\nfunction formatMovementTitle(\n row: InventoryMovementRow,\n skuLabel: string,\n t: ReturnType<typeof useT>,\n): string {\n const quantity = Math.abs(toNumber(row.quantity))\n const signedQuantity = toNumber(row.quantity)\n switch (row.type) {\n case 'receipt':\n case 'return_receive':\n return t('wms.backend.dashboard.activity.titles.received', 'Received {quantity}\u00D7 {sku}', {\n quantity,\n sku: skuLabel,\n })\n case 'adjust':\n return t('wms.backend.dashboard.activity.titles.adjusted', 'Adjusted {quantity}\u00D7 {sku}', {\n quantity: `${signedQuantity >= 0 ? '+' : ''}${signedQuantity}`,\n sku: skuLabel,\n })\n case 'transfer':\n return t('wms.backend.dashboard.activity.titles.moved', 'Moved {quantity}\u00D7 {sku}', {\n quantity,\n sku: skuLabel,\n })\n case 'pick':\n case 'pack':\n return t('wms.backend.dashboard.activity.titles.allocated', 'Allocated {quantity}\u00D7 {sku}', {\n quantity,\n sku: skuLabel,\n })\n case 'cycle_count':\n return t('wms.backend.dashboard.activity.titles.reconciled', 'Inventory reconciled \u2014 {sku}', {\n sku: skuLabel,\n })\n default:\n return t('wms.backend.dashboard.activity.titles.generic', '{type} {quantity}\u00D7 {sku}', {\n type: row.type ?? 'movement',\n quantity,\n sku: skuLabel,\n })\n }\n}\n\nfunction formatMovementSubtitle(\n row: InventoryMovementRow,\n t: InventoryDisplayTranslator,\n): string | null {\n const reasonLabel = inventoryMovementReasonLabel(\n {\n reasonCode: row.reason_code,\n reason: row.reason,\n movementType: row.type,\n },\n t,\n )\n if (reasonLabel) return reasonLabel\n if (row.reference_type && row.reference_id) return `${row.reference_type} \u00B7 ${row.reference_id}`\n return null\n}\n\nfunction formatMovementLocation(\n row: InventoryMovementRow,\n locationId: string,\n locationCode: string,\n): string {\n const fromId = row.location_from_id?.trim()\n const toId = row.location_to_id?.trim()\n const from = (row.location_from_code ?? '').trim() || fromId || '\u2014'\n const to = (row.location_to_code ?? '').trim() || toId || '\u2014'\n if (row.type === 'transfer' && from !== '\u2014' && to !== '\u2014') return `${from} \u2192 ${to}`\n if (fromId === locationId || toId === locationId) return locationCode || locationId\n return from !== '\u2014' ? from : to\n}\n\nfunction formatRelativeDays(valueMs: number, t: ReturnType<typeof useT>): string {\n const days = Math.max(0, Math.floor((Date.now() - valueMs) / (24 * 60 * 60 * 1000)))\n if (days === 0) return t('wms.backend.location.kpis.lastCounted.today', 'Today')\n return t('wms.backend.location.kpis.lastCounted.daysAgo', '{days}d ago', { days })\n}\n\ntype LocationKpiCardProps = {\n title: string\n caption: string\n value: string\n badgeLabel: string | null\n badgeVariant: StatusBadgeVariant\n ctaLabel: string\n ctaHref?: string\n onCtaClick?: () => void\n progress?: number | null\n}\n\nfunction LocationKpiCard({\n title,\n caption,\n value,\n badgeLabel,\n badgeVariant,\n ctaLabel,\n ctaHref,\n onCtaClick,\n progress,\n}: LocationKpiCardProps) {\n return (\n <section className=\"flex min-h-52 flex-col rounded-lg border bg-card p-5 text-card-foreground shadow-sm\">\n <p className=\"text-sm font-medium\">{title}</p>\n <p className=\"mt-3 text-xs text-muted-foreground\">{caption}</p>\n <div className=\"mt-2 flex items-end gap-3\">\n <p className=\"text-3xl font-semibold tracking-tight\">{value}</p>\n {badgeLabel ? (\n <StatusBadge variant={badgeVariant} dot>\n {badgeLabel}\n </StatusBadge>\n ) : null}\n </div>\n {progress != null ? (\n <Progress\n value={progress}\n max={100}\n tone={progress >= 80 ? 'warning' : 'accent'}\n size=\"sm\"\n className=\"mt-3\"\n />\n ) : null}\n {onCtaClick ? (\n <LinkButton variant=\"primary\" size=\"sm\" className=\"mt-auto pt-4 w-fit\" onClick={onCtaClick}>\n {ctaLabel}\n </LinkButton>\n ) : ctaHref ? (\n <LinkButton asChild variant=\"primary\" size=\"sm\" className=\"mt-auto pt-4 w-fit\">\n <Link href={ctaHref}>{ctaLabel}</Link>\n </LinkButton>\n ) : null}\n </section>\n )\n}\n\ntype WmsLocationDetailPageProps = {\n locationId: string\n}\n\nexport default function WmsLocationDetailPage({ locationId }: WmsLocationDetailPageProps) {\n const t = useT()\n const locale = useLocale()\n const router = useRouter()\n const access = useWmsInventoryMutationAccess()\n const parsedLocationId = locationIdSchema.safeParse(locationId.trim())\n const scopedLocationId = parsedLocationId.success ? parsedLocationId.data : null\n\n const [itemFilter, setItemFilter] = React.useState<ItemFilter>('all')\n const [itemsPage, setItemsPage] = React.useState(1)\n const [selectedBalanceIds, setSelectedBalanceIds] = React.useState<Set<string>>(() => new Set())\n const [adjustOpen, setAdjustOpen] = React.useState(false)\n const [adjustPreset, setAdjustPreset] = React.useState<InventoryMutationPreset>({})\n const [cycleOpen, setCycleOpen] = React.useState(false)\n const [cyclePreset, setCyclePreset] = React.useState<Pick<InventoryMutationPreset, 'warehouseId' | 'locationId'>>({})\n const [editOpen, setEditOpen] = React.useState(false)\n\n const itemsPageSize = 20\n\n const activityTimeFormatter = React.useMemo(\n () =>\n new Intl.DateTimeFormat(locale, {\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n }),\n [locale],\n )\n\n const locationQuery = useQuery({\n queryKey: ['wms-location-detail', 'location', scopedLocationId],\n enabled: Boolean(scopedLocationId),\n queryFn: async () => {\n const params = new URLSearchParams({\n ids: scopedLocationId!,\n page: '1',\n pageSize: '1',\n })\n const call = await apiCall<PagedResponse<LocationRow>>(`/api/wms/locations?${params.toString()}`)\n if (!call.ok) {\n await raiseCrudError(call.response, t('wms.backend.location.errors.location', 'Failed to load location.'))\n }\n return call.result?.items?.[0] ?? null\n },\n })\n\n const parentLocationQuery = useQuery({\n queryKey: ['wms-location-detail', 'parent', locationQuery.data?.parent_id],\n enabled: Boolean(locationQuery.data?.parent_id),\n queryFn: async () => {\n const parentId = locationQuery.data?.parent_id?.trim()\n if (!parentId) return null\n const params = new URLSearchParams({ ids: parentId, page: '1', pageSize: '1' })\n const call = await apiCall<PagedResponse<LocationRow>>(`/api/wms/locations?${params.toString()}`)\n if (!call.ok) return null\n return call.result?.items?.[0] ?? null\n },\n })\n\n const balancesQuery = useQuery({\n queryKey: ['wms-location-detail', 'balances', scopedLocationId],\n enabled: Boolean(scopedLocationId),\n queryFn: async () => {\n const params = new URLSearchParams({\n locationId: scopedLocationId!,\n page: '1',\n pageSize: '100',\n sortField: 'updatedAt',\n sortDir: 'desc',\n })\n const call = await apiCall<PagedResponse<InventoryBalanceRow>>(\n `/api/wms/inventory/balances?${params.toString()}`,\n )\n if (!call.ok) {\n await raiseCrudError(call.response, t('wms.backend.location.errors.balances', 'Failed to load items in location.'))\n }\n return call.result ?? { items: [], total: 0, totalPages: 1 }\n },\n })\n\n const variantIds = React.useMemo(() => {\n const ids = new Set<string>()\n for (const row of balancesQuery.data?.items ?? []) {\n const variantId = row.catalog_variant_id?.trim()\n if (variantId) ids.add(variantId)\n }\n return Array.from(ids)\n }, [balancesQuery.data?.items])\n\n const profilesQuery = useQuery({\n queryKey: ['wms-location-detail', 'profiles', variantIds],\n enabled: variantIds.length > 0,\n queryFn: async () => {\n const params = new URLSearchParams({ page: '1', pageSize: '100' })\n const call = await apiCall<PagedResponse<InventoryProfileRow>>(\n `/api/wms/inventory-profiles?${params.toString()}`,\n )\n if (!call.ok) return []\n return call.result?.items ?? []\n },\n })\n\n const lotsQuery = useQuery({\n queryKey: ['wms-location-detail', 'lots', variantIds],\n enabled: variantIds.length > 0,\n queryFn: async () => {\n const params = new URLSearchParams({ page: '1', pageSize: '100' })\n const call = await apiCall<PagedResponse<InventoryLotRow>>(`/api/wms/lots?${params.toString()}`)\n if (!call.ok) {\n await raiseCrudError(call.response, t('wms.backend.location.errors.lots', 'Failed to load lots.'))\n }\n return call.result?.items ?? []\n },\n })\n\n const warehouseId = locationQuery.data?.warehouse_id?.trim() ?? ''\n\n const movementsQuery = useQuery({\n queryKey: ['wms-location-detail', 'movements', scopedLocationId, warehouseId],\n enabled: Boolean(scopedLocationId && warehouseId),\n queryFn: async () => {\n const params = new URLSearchParams({\n warehouseId,\n locationId: scopedLocationId!,\n page: '1',\n pageSize: '50',\n sortField: 'performedAt',\n sortDir: 'desc',\n })\n const call = await apiCall<PagedResponse<InventoryMovementRow>>(\n `/api/wms/inventory/movements?${params.toString()}`,\n )\n if (!call.ok) {\n await raiseCrudError(call.response, t('wms.backend.location.errors.movements', 'Failed to load recent activity.'))\n }\n return call.result?.items ?? []\n },\n })\n\n const lotById = React.useMemo(() => {\n const map = new Map<string, InventoryLotRow>()\n for (const lot of lotsQuery.data ?? []) {\n if (lot.id) map.set(lot.id, lot)\n }\n return map\n }, [lotsQuery.data])\n\n const reorderPointByVariant = React.useMemo(() => {\n const map = new Map<string, number>()\n for (const profile of profilesQuery.data ?? []) {\n const variantId = profile.catalog_variant_id?.trim()\n if (!variantId) continue\n map.set(variantId, toNumber(profile.reorder_point))\n }\n return map\n }, [profilesQuery.data])\n\n const locationCode = (locationQuery.data?.code ?? '').trim() || scopedLocationId || '\u2014'\n const pageTitle = locationCode\n\n const locationTypeLabel = React.useMemo(() => {\n const type = (locationQuery.data?.type ?? '').trim()\n if (!type) return null\n return t(`wms.backend.location.types.${type}`, type)\n }, [locationQuery.data?.type, t])\n\n const nowMs = React.useMemo(() => Date.now(), [])\n\n const totals = React.useMemo(() => {\n const items = balancesQuery.data?.items ?? []\n let onHand = 0\n let reserved = 0\n let allocated = 0\n const skuIds = new Set<string>()\n const lotIds = new Set<string>()\n for (const row of items) {\n onHand += toNumber(row.quantity_on_hand)\n reserved += toNumber(row.quantity_reserved)\n allocated += toNumber(row.quantity_allocated)\n const variantId = row.catalog_variant_id?.trim()\n if (variantId && toNumber(row.quantity_on_hand) > 0) skuIds.add(variantId)\n const lotId = row.lot_id?.trim()\n if (lotId && toNumber(row.quantity_on_hand) > 0) lotIds.add(lotId)\n }\n return {\n onHand,\n reserved,\n allocated,\n pendingPicks: reserved + allocated,\n skuCount: skuIds.size,\n lotCount: lotIds.size,\n }\n }, [balancesQuery.data?.items])\n\n const capacityUsedPercent = React.useMemo(() => {\n const capacity = toNumber(locationQuery.data?.capacity_units)\n if (capacity <= 0) return null\n return Math.min(100, Math.round((totals.onHand / capacity) * 100))\n }, [locationQuery.data?.capacity_units, totals.onHand])\n\n const lastCounted = React.useMemo(() => {\n const cycleMovements = (movementsQuery.data ?? []).filter((row) => row.type === 'cycle_count')\n if (cycleMovements.length === 0) return null\n const latest = cycleMovements[0]\n const raw = latest.performed_at ?? latest.received_at\n if (!raw) return null\n const ts = new Date(raw).getTime()\n if (Number.isNaN(ts)) return null\n return { ts, label: formatRelativeDays(ts, t) }\n }, [movementsQuery.data, t])\n\n const filteredBalances = React.useMemo(() => {\n const items = balancesQuery.data?.items ?? []\n return items.filter((row) =>\n matchesItemFilter(row, row.lot_id ? lotById.get(row.lot_id) : undefined, itemFilter, nowMs),\n )\n }, [balancesQuery.data?.items, itemFilter, lotById, nowMs])\n\n const itemsTotal = filteredBalances.length\n const itemsTotalPages = Math.max(1, Math.ceil(itemsTotal / itemsPageSize))\n const pagedBalances = React.useMemo(() => {\n const start = (itemsPage - 1) * itemsPageSize\n return filteredBalances.slice(start, start + itemsPageSize)\n }, [filteredBalances, itemsPage, itemsPageSize])\n\n React.useEffect(() => {\n setItemsPage(1)\n setSelectedBalanceIds(new Set())\n }, [itemFilter])\n\n const selectedBalances = React.useMemo(\n () => filteredBalances.filter((row) => selectedBalanceIds.has(row.id)),\n [filteredBalances, selectedBalanceIds],\n )\n\n const movementsHref = React.useMemo(() => {\n const params = new URLSearchParams()\n if (warehouseId) params.set('warehouseId', warehouseId)\n const query = params.toString()\n return query ? `/backend/wms/movements?${query}` : '/backend/wms/movements'\n }, [warehouseId])\n\n const inventoryConsoleHref = React.useMemo(() => {\n if (!scopedLocationId) return '/backend/wms/inventory'\n return `/backend/wms/inventory?locationId=${encodeURIComponent(scopedLocationId)}`\n }, [scopedLocationId])\n\n const locationsHref = '/backend/wms/locations'\n\n const openAdjustDialog = React.useCallback((preset: InventoryMutationPreset = {}) => {\n setAdjustPreset(preset)\n setAdjustOpen(true)\n }, [])\n\n const openCycleCountDialog = React.useCallback(\n (preset: Pick<InventoryMutationPreset, 'warehouseId' | 'locationId'> = {}) => {\n setCyclePreset(preset)\n setCycleOpen(true)\n },\n [],\n )\n\n const resolveMutationContext = React.useCallback((): InventoryMutationPreset => {\n const firstSelected = selectedBalances[0]\n const lotId =\n selectedBalances.length === 1 ? firstSelected?.lot_id?.trim() || undefined : undefined\n const catalogVariantId =\n selectedBalances.length === 1 ? firstSelected?.catalog_variant_id?.trim() || undefined : undefined\n return {\n warehouseId: warehouseId || undefined,\n locationId: scopedLocationId || undefined,\n catalogVariantId,\n lotId,\n }\n }, [scopedLocationId, selectedBalances, warehouseId])\n\n const toggleBalanceSelection = React.useCallback((balanceId: string, selected: boolean) => {\n setSelectedBalanceIds((current) => {\n const next = new Set(current)\n if (selected) next.add(balanceId)\n else next.delete(balanceId)\n return next\n })\n }, [])\n\n const togglePageSelection = React.useCallback((selected: boolean) => {\n setSelectedBalanceIds((current) => {\n const next = new Set(current)\n for (const row of pagedBalances) {\n if (selected) next.add(row.id)\n else next.delete(row.id)\n }\n return next\n })\n }, [pagedBalances])\n\n const pageSelectionState = React.useMemo(() => {\n if (pagedBalances.length === 0) {\n return { checked: false, indeterminate: false }\n }\n const selectedOnPage = pagedBalances.filter((row) => selectedBalanceIds.has(row.id)).length\n return {\n checked: selectedOnPage === pagedBalances.length,\n indeterminate: selectedOnPage > 0 && selectedOnPage < pagedBalances.length,\n }\n }, [pagedBalances, selectedBalanceIds])\n\n const handleExportItemsCsv = React.useCallback(() => {\n const headers = [\n t('wms.backend.location.items.columns.sku', 'SKU'),\n t('wms.backend.location.items.columns.variant', 'Variant name'),\n t('wms.backend.location.items.columns.lot', 'Lot'),\n t('wms.backend.location.items.columns.onHand', 'On hand'),\n t('wms.backend.location.items.columns.reserved', 'Reserved'),\n t('wms.backend.location.items.columns.status', 'Status'),\n ]\n const rows = filteredBalances.map((row) => {\n const lot = row.lot_id ? lotById.get(row.lot_id) : undefined\n const reorderPoint = reorderPointByVariant.get(row.catalog_variant_id?.trim() ?? '') ?? 0\n const status = resolveItemStatus(row, lot, reorderPoint, nowMs)\n return [\n formatSkuLabel(row),\n formatVariantName(row),\n formatLotLabel(lot, locale),\n String(toNumber(row.quantity_on_hand)),\n String(toNumber(row.quantity_reserved)),\n t(status.labelKey, status.labelFallback),\n ]\n })\n const safeCode = pageTitle.replace(/[^\\w.-]+/g, '-').replace(/^-+|-+$/g, '') || 'location'\n downloadCsvFile(`${safeCode}-items.csv`, [headers, ...rows])\n }, [filteredBalances, locale, lotById, nowMs, pageTitle, reorderPointByVariant, t])\n\n const warehouseLabel = formatWarehouseLabel(locationQuery.data ?? {})\n\n const subtitleParts = [\n locationTypeLabel ? `${locationTypeLabel} ${locationCode}` : null,\n parentLocationQuery.data?.code?.trim()\n ? t('wms.backend.location.header.zone', 'Zone {code}', {\n code: parentLocationQuery.data.code.trim(),\n })\n : null,\n warehouseLabel !== '\u2014' ? warehouseLabel : null,\n locationQuery.data?.is_active === false\n ? t('wms.backend.location.header.inactive', 'Inactive')\n : null,\n ].filter(Boolean)\n\n const itemColumns = React.useMemo<ColumnDef<InventoryBalanceRow>[]>(\n () => [\n {\n id: 'select',\n header: () => (\n <Checkbox\n aria-label={t('wms.backend.location.items.columns.select', 'Select')}\n checked={pageSelectionState.indeterminate ? 'indeterminate' : pageSelectionState.checked}\n onCheckedChange={(checked) => togglePageSelection(checked === true)}\n />\n ),\n cell: ({ row }) => (\n <Checkbox\n aria-label={t('wms.backend.location.items.columns.select', 'Select')}\n checked={selectedBalanceIds.has(row.original.id)}\n onCheckedChange={(checked) => toggleBalanceSelection(row.original.id, checked === true)}\n />\n ),\n meta: { maxWidth: '2.75rem' },\n },\n {\n accessorKey: 'variant_sku',\n header: t('wms.backend.location.items.columns.sku', 'SKU'),\n cell: ({ row }) => {\n const variantId = row.original.catalog_variant_id?.trim()\n const label = formatSkuLabel(row.original)\n if (!variantId) return label\n return (\n <Link\n href={`/backend/wms/sku/${encodeURIComponent(variantId)}`}\n className=\"font-medium text-primary hover:underline\"\n >\n {label}\n </Link>\n )\n },\n },\n {\n accessorKey: 'variant_name',\n header: t('wms.backend.location.items.columns.variant', 'Variant name'),\n cell: ({ row }) => formatVariantName(row.original),\n },\n {\n accessorKey: 'lot_id',\n header: t('wms.backend.location.items.columns.lot', 'Lot'),\n cell: ({ row }) => {\n const lotId = row.original.lot_id?.trim()\n const lot = lotId ? lotById.get(lotId) : undefined\n const label = formatLotLabel(lot, locale)\n if (!lotId || label === '\u2014') return label\n return (\n <Link\n href={`/backend/wms/lot/${encodeURIComponent(lotId)}`}\n className=\"font-medium text-primary hover:underline\"\n >\n {label}\n </Link>\n )\n },\n },\n {\n accessorKey: 'quantity_on_hand',\n header: t('wms.backend.location.items.columns.onHand', 'On hand'),\n cell: ({ row }) => String(toNumber(row.original.quantity_on_hand)),\n },\n {\n accessorKey: 'quantity_reserved',\n header: t('wms.backend.location.items.columns.reserved', 'Reserved'),\n cell: ({ row }) => String(toNumber(row.original.quantity_reserved)),\n },\n {\n id: 'status',\n header: t('wms.backend.location.items.columns.status', 'Status'),\n cell: ({ row }) => {\n const lot = row.original.lot_id ? lotById.get(row.original.lot_id) : undefined\n const reorderPoint = reorderPointByVariant.get(row.original.catalog_variant_id?.trim() ?? '') ?? 0\n const status = resolveItemStatus(row.original, lot, reorderPoint, nowMs)\n return (\n <StatusBadge variant={status.variant} dot>\n {t(status.labelKey, status.labelFallback)}\n </StatusBadge>\n )\n },\n },\n ],\n [\n locale,\n lotById,\n nowMs,\n pageSelectionState.checked,\n pageSelectionState.indeterminate,\n reorderPointByVariant,\n selectedBalanceIds,\n t,\n toggleBalanceSelection,\n togglePageSelection,\n ],\n )\n\n const activityColumns = React.useMemo<ColumnDef<InventoryMovementRow>[]>(\n () => [\n {\n accessorKey: 'type',\n header: t('wms.backend.location.activity.columns.event', 'Event'),\n cell: ({ row }) => {\n const type = row.original.type ?? 'movement'\n return (\n <StatusBadge variant={movementStatusMap[type] ?? 'neutral'}>\n {movementTypeLabel(type, t)}\n </StatusBadge>\n )\n },\n },\n {\n id: 'details',\n header: t('wms.backend.location.activity.columns.details', 'Details'),\n cell: ({ row }) => {\n const skuLabel =\n (row.original.variant_sku ?? '').trim() ||\n (row.original.variant_name ?? '').trim() ||\n row.original.catalog_variant_id ||\n '\u2014'\n const subtitle = formatMovementSubtitle(row.original, t)\n return (\n <div className=\"space-y-0.5\">\n <p className=\"text-sm font-medium\">{formatMovementTitle(row.original, skuLabel, t)}</p>\n {subtitle ? <p className=\"text-xs text-muted-foreground\">{subtitle}</p> : null}\n </div>\n )\n },\n },\n {\n id: 'location',\n header: t('wms.backend.location.activity.columns.location', 'Location'),\n cell: ({ row }) => (\n <span className=\"text-sm text-muted-foreground\">\n {formatMovementLocation(row.original, scopedLocationId ?? '', locationCode)}\n </span>\n ),\n },\n {\n id: 'time',\n header: t('wms.backend.location.activity.columns.time', 'Time'),\n cell: ({ row }) => {\n const raw = row.original.performed_at ?? row.original.received_at\n if (!raw) return '\u2014'\n const date = new Date(raw)\n if (Number.isNaN(date.getTime())) return raw\n return activityTimeFormatter.format(date)\n },\n meta: { maxWidth: '6rem' },\n },\n ],\n [activityTimeFormatter, locationCode, scopedLocationId, t],\n )\n\n const filterChips: Array<{ id: ItemFilter; label: string }> = [\n { id: 'all', label: t('wms.backend.location.items.filters.all', 'All items') },\n { id: 'sellable', label: t('wms.backend.location.items.filters.sellable', 'Sellable') },\n { id: 'picking', label: t('wms.backend.location.items.filters.picking', 'Picking') },\n { id: 'nearExpiry', label: t('wms.backend.location.items.filters.nearExpiry', 'Near expiry') },\n ]\n\n const isLoading =\n locationQuery.isLoading ||\n balancesQuery.isLoading ||\n lotsQuery.isLoading ||\n movementsQuery.isLoading ||\n parentLocationQuery.isLoading\n\n const hasError =\n locationQuery.isError ||\n balancesQuery.isError ||\n lotsQuery.isError ||\n movementsQuery.isError\n\n if (!scopedLocationId) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={t('wms.backend.location.errors.invalidId', 'Invalid location identifier.')}\n action={(\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => router.push(locationsHref)}>\n {t('wms.backend.location.actions.backToLocations', 'Back to locations')}\n </Button>\n )}\n />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <LinkButton asChild variant=\"gray\" size=\"sm\" className=\"w-fit px-0 text-muted-foreground hover:text-foreground\">\n <Link href={locationsHref}>\n <ArrowLeft className=\"size-4\" />\n {t('wms.backend.location.actions.backToLocations', 'Back to locations')}\n </Link>\n </LinkButton>\n\n <PageHeader\n title={pageTitle}\n description={subtitleParts.join(' \u00B7 ')}\n actions={(\n <>\n {warehouseId ? (\n <Select value={warehouseId} disabled>\n <SelectTrigger className=\"w-full max-w-xs sm:w-56\">\n <Warehouse className=\"mr-2 size-4 shrink-0 text-muted-foreground\" />\n <SelectValue placeholder={warehouseLabel} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={warehouseId}>{warehouseLabel}</SelectItem>\n </SelectContent>\n </Select>\n ) : null}\n {access.canAdjust ? (\n <Button type=\"button\" variant=\"outline\" onClick={() => openAdjustDialog(resolveMutationContext())}>\n <SlidersHorizontal className=\"size-4\" />\n {t('wms.backend.location.actions.adjust', 'Adjust stock')}\n </Button>\n ) : null}\n </>\n )}\n />\n\n {isLoading ? (\n <LoadingMessage label={t('wms.backend.location.loading', 'Loading location view\u2026')} />\n ) : null}\n\n {hasError ? (\n <ErrorMessage label={t('wms.backend.location.errors.load', 'Failed to load location view.')} />\n ) : null}\n\n {!isLoading && !hasError && locationQuery.data ? (\n <>\n <section className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5\">\n <LocationKpiCard\n title={t('wms.backend.location.kpis.skusOccupied.title', 'SKUs occupied')}\n caption={t('wms.backend.location.kpis.skusOccupied.caption', 'Distinct variants with stock')}\n value={String(totals.skuCount)}\n badgeLabel={\n totals.skuCount > 0\n ? t('wms.backend.location.kpis.skusOccupied.badge', '{count} active', { count: totals.skuCount })\n : null\n }\n badgeVariant=\"info\"\n ctaLabel={t('wms.backend.location.kpis.skusOccupied.cta', 'View items')}\n ctaHref=\"#location-items\"\n />\n <LocationKpiCard\n title={t('wms.backend.location.kpis.capacityUsed.title', 'Capacity used')}\n caption={t('wms.backend.location.kpis.capacityUsed.caption', 'On hand vs location capacity')}\n value={capacityUsedPercent === null ? '\u2014' : `${capacityUsedPercent}%`}\n badgeLabel={\n capacityUsedPercent !== null && capacityUsedPercent >= 80\n ? t('wms.backend.location.kpis.capacityUsed.badgeHigh', 'Near limit')\n : capacityUsedPercent !== null\n ? t('wms.backend.location.kpis.capacityUsed.badgeOk', 'Within capacity')\n : null\n }\n badgeVariant={capacityUsedPercent !== null && capacityUsedPercent >= 80 ? 'warning' : 'success'}\n ctaLabel={t('wms.backend.location.kpis.capacityUsed.cta', 'Edit location')}\n onCtaClick={access.canManageLocations ? () => setEditOpen(true) : undefined}\n progress={capacityUsedPercent}\n />\n <LocationKpiCard\n title={t('wms.backend.location.kpis.activeLots.title', 'Active lots')}\n caption={t('wms.backend.location.kpis.activeLots.caption', 'Lots with on-hand quantity')}\n value={String(totals.lotCount)}\n badgeLabel={\n totals.lotCount > 0\n ? t('wms.backend.location.kpis.activeLots.badge', '{count} lots', { count: totals.lotCount })\n : null\n }\n badgeVariant=\"neutral\"\n ctaLabel={t('wms.backend.location.kpis.activeLots.cta', 'View items')}\n ctaHref=\"#location-items\"\n />\n <LocationKpiCard\n title={t('wms.backend.location.kpis.pendingPicks.title', 'Pending picks')}\n caption={t('wms.backend.location.kpis.pendingPicks.caption', 'Reserved and allocated units')}\n value={String(totals.pendingPicks)}\n badgeLabel={\n totals.pendingPicks > 0\n ? t('wms.backend.location.kpis.pendingPicks.badge', '{count} units', { count: totals.pendingPicks })\n : null\n }\n badgeVariant=\"info\"\n ctaLabel={t('wms.backend.location.kpis.pendingPicks.cta', 'View reservations')}\n ctaHref=\"/backend/wms/reservations\"\n />\n <LocationKpiCard\n title={t('wms.backend.location.kpis.lastCounted.title', 'Last counted')}\n caption={t('wms.backend.location.kpis.lastCounted.caption', 'Most recent cycle count')}\n value={lastCounted?.label ?? '\u2014'}\n badgeLabel={\n lastCounted && nowMs - lastCounted.ts > 14 * 24 * 60 * 60 * 1000\n ? t('wms.backend.location.kpis.lastCounted.badgeStale', 'Due soon')\n : lastCounted\n ? t('wms.backend.location.kpis.lastCounted.badgeRecent', 'Recent')\n : t('wms.backend.location.kpis.lastCounted.badgeNever', 'Never')\n }\n badgeVariant={\n !lastCounted || nowMs - (lastCounted?.ts ?? 0) > 14 * 24 * 60 * 60 * 1000\n ? 'warning'\n : 'success'\n }\n ctaLabel={t('wms.backend.location.kpis.lastCounted.cta', 'Start cycle count')}\n ctaHref=\"#location-items\"\n />\n </section>\n\n <section\n id=\"location-items\"\n className=\"rounded-lg border bg-card text-card-foreground shadow-sm\"\n >\n <div className=\"border-b px-5 py-4\">\n <h2 className=\"text-base font-semibold\">\n {t('wms.backend.location.items.title', 'Items in this location')}\n </h2>\n </div>\n <div className=\"flex flex-col gap-4 border-b px-5 py-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap gap-2\">\n {filterChips.map((chip) => (\n <Button\n key={chip.id}\n type=\"button\"\n size=\"sm\"\n variant={itemFilter === chip.id ? 'default' : 'outline'}\n className={cn('rounded-full', itemFilter === chip.id && 'shadow-sm')}\n onClick={() => setItemFilter(chip.id)}\n >\n {chip.label}\n </Button>\n ))}\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={handleExportItemsCsv}>\n <Download className=\"size-4\" />\n {t('wms.backend.location.items.actions.exportCsv', 'Export CSV')}\n </Button>\n {access.canCycleCount ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() =>\n openCycleCountDialog({\n warehouseId: warehouseId || undefined,\n locationId: scopedLocationId,\n })\n }\n >\n {t('wms.backend.location.items.actions.cycleCountZone', 'Cycle count zone')}\n </Button>\n ) : null}\n {access.canAdjust && selectedBalances.length > 0 ? (\n <Button\n type=\"button\"\n variant=\"default\"\n onClick={() => openAdjustDialog(resolveMutationContext())}\n >\n {t('wms.backend.location.items.actions.adjustSelected', 'Adjust selected ({count})', { count: selectedBalances.length })}\n </Button>\n ) : null}\n </div>\n </div>\n <DataTable<InventoryBalanceRow>\n embedded\n columns={itemColumns}\n data={pagedBalances}\n disableRowClick\n entityId={E.wms.inventory_balance}\n perspective={{ tableId: extensionPoints.hosts.locationItemsTable.tableId }}\n pagination={{\n page: itemsPage,\n pageSize: itemsPageSize,\n total: itemsTotal,\n totalPages: itemsTotalPages,\n onPageChange: setItemsPage,\n }}\n emptyState={(\n <EmptyState\n title={t('wms.backend.location.items.empty.title', 'No items in this view')}\n description={t(\n 'wms.backend.location.items.empty.description',\n 'Try another filter or post inventory through receipts or adjustments.',\n )}\n />\n )}\n />\n {itemsTotal > 0 ? (\n <div className=\"flex flex-col gap-2 border-t px-5 py-3 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between\">\n <span>\n {t('wms.backend.location.items.footer.showing', 'Showing {shown} of {total} items', {\n shown: pagedBalances.length,\n total: itemsTotal,\n })}\n </span>\n {balancesQuery.data && balancesQuery.data.total > itemsTotal ? (\n <LinkButton asChild variant=\"gray\" size=\"sm\" className=\"h-auto px-0\">\n <Link href={inventoryConsoleHref}>\n {t('wms.backend.location.items.footer.viewAll', 'View all items \u2192')}\n </Link>\n </LinkButton>\n ) : null}\n </div>\n ) : null}\n </section>\n\n <DataTable<InventoryMovementRow>\n title={t('wms.backend.location.activity.title', 'Recent activity')}\n columns={activityColumns}\n data={movementsQuery.data ?? []}\n disableRowClick\n entityId={E.wms.inventory_movement}\n perspective={{ tableId: extensionPoints.hosts.locationActivityTable.tableId }}\n emptyState={t('wms.backend.location.activity.empty', 'No recent movements for this location.')}\n actions={(\n <Button asChild type=\"button\" variant=\"ghost\" size=\"sm\">\n <Link href={movementsHref}>\n {t('wms.backend.location.activity.viewAll', 'View all movements \u2192')}\n </Link>\n </Button>\n )}\n />\n\n <section className=\"flex flex-col gap-4 rounded-lg border bg-card px-5 py-4 shadow-sm lg:flex-row lg:items-center lg:justify-between\">\n <div>\n <h2 className=\"text-base font-semibold\">\n {t('wms.backend.location.quickActions.title', 'Quick actions')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'wms.backend.location.quickActions.description',\n 'Run common inventory actions without leaving this location view',\n )}\n </p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {access.canAdjust ? (\n <Button type=\"button\" variant=\"default\" onClick={() => openAdjustDialog(resolveMutationContext())}>\n <SlidersHorizontal className=\"size-4\" />\n {t('wms.backend.location.quickActions.adjust', 'Adjust inventory')}\n </Button>\n ) : null}\n {access.canAdjust ? (\n <Button asChild type=\"button\" variant=\"outline\">\n <Link href={inventoryConsoleHref}>\n <ArrowLeftRight className=\"size-4\" />\n {t('wms.backend.location.quickActions.move', 'Move stock')}\n </Link>\n </Button>\n ) : null}\n {access.canCycleCount ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() =>\n openCycleCountDialog({\n warehouseId: warehouseId || undefined,\n locationId: scopedLocationId,\n })\n }\n >\n <ClipboardList className=\"size-4\" />\n {t('wms.backend.location.quickActions.cycleCount', 'Cycle count')}\n </Button>\n ) : null}\n <Button asChild type=\"button\" variant=\"outline\">\n <Link href={movementsHref}>\n {t('wms.backend.location.quickActions.openLedger', 'Open ledger')}\n <ExternalLink className=\"size-4\" />\n </Link>\n </Button>\n </div>\n </section>\n </>\n ) : null}\n\n {!isLoading && !hasError && !locationQuery.data ? (\n <RecordNotFoundState\n label={t('wms.backend.location.errors.notFound', 'Location not found.')}\n backHref={locationsHref}\n backLabel={t('wms.backend.location.actions.backToLocations', 'Back to locations')}\n />\n ) : null}\n </PageBody>\n\n {access.canAdjust && scopedLocationId ? (\n <AdjustInventoryDialog\n open={adjustOpen}\n onOpenChange={setAdjustOpen}\n access={access}\n initialWarehouseId={adjustPreset.warehouseId ?? warehouseId}\n initialLocationId={adjustPreset.locationId ?? scopedLocationId}\n initialCatalogVariantId={adjustPreset.catalogVariantId}\n initialLotId={adjustPreset.lotId}\n />\n ) : null}\n {access.canCycleCount ? (\n <CycleCountWizardDialog\n open={cycleOpen}\n onOpenChange={setCycleOpen}\n access={access}\n initialWarehouseId={cyclePreset.warehouseId ?? warehouseId}\n initialLocationId={cyclePreset.locationId ?? scopedLocationId ?? undefined}\n />\n ) : null}\n {access.canManageLocations && locationQuery.data ? (\n <LocationEditDialog\n open={editOpen}\n onOpenChange={setEditOpen}\n mode=\"edit\"\n row={locationQuery.data}\n onSaved={async () => {\n await locationQuery.refetch()\n }}\n />\n ) : null}\n </Page>\n )\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|