@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.
Files changed (140) hide show
  1. package/dist/modules/api_keys/backend/api-keys/page.js.map +1 -1
  2. package/dist/modules/attachments/components/AttachmentLibrary.js.map +2 -2
  3. package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +1 -1
  4. package/dist/modules/audit_logs/components/AccessLogsTable.js.map +1 -1
  5. package/dist/modules/audit_logs/components/AuditLogsActions.js.map +1 -1
  6. package/dist/modules/auth/backend/roles/page.js.map +2 -2
  7. package/dist/modules/auth/backend/users/page.js.map +2 -2
  8. package/dist/modules/business_rules/backend/logs/page.js.map +1 -1
  9. package/dist/modules/business_rules/backend/rules/page.js.map +1 -1
  10. package/dist/modules/business_rules/backend/sets/page.js.map +1 -1
  11. package/dist/modules/catalog/components/PriceKindSettings.js.map +1 -1
  12. package/dist/modules/catalog/components/categories/CategoriesDataTable.js.map +1 -1
  13. package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
  14. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +1 -1
  15. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +1 -1
  16. package/dist/modules/currencies/backend/currencies/page.js.map +1 -1
  17. package/dist/modules/currencies/backend/exchange-rates/page.js.map +1 -1
  18. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +1 -1
  19. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +1 -1
  20. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  21. package/dist/modules/customers/backend/customers/deals/page.js.map +1 -1
  22. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  23. package/dist/modules/customers/components/CustomerTodosTable.js.map +1 -1
  24. package/dist/modules/data_sync/backend/data-sync/page.js.map +1 -1
  25. package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
  26. package/dist/modules/directory/backend/directory/organizations/page.js.map +1 -1
  27. package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
  28. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  29. package/dist/modules/entities/components/SystemEntitiesTable.js.map +2 -2
  30. package/dist/modules/entities/components/UserEntitiesTable.js.map +2 -2
  31. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js.map +2 -2
  32. package/dist/modules/feature_toggles/components/OverridesTable.js.map +2 -2
  33. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js.map +1 -1
  34. package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +1 -1
  35. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +1 -1
  36. package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +1 -1
  37. package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
  38. package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
  39. package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
  40. package/dist/modules/resources/backend/resources/resources/page.js.map +1 -1
  41. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  42. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  43. package/dist/modules/sales/components/AdjustmentKindSettings.js.map +1 -1
  44. package/dist/modules/sales/components/PaymentMethodsSettings.js.map +1 -1
  45. package/dist/modules/sales/components/ShippingMethodsSettings.js.map +1 -1
  46. package/dist/modules/sales/components/TaxRatesSettings.js.map +1 -1
  47. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
  48. package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +1 -1
  49. package/dist/modules/sales/components/documents/PaymentsSection.js.map +1 -1
  50. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  51. package/dist/modules/sales/components/documents/SalesOrderDraftLines.js.map +1 -1
  52. package/dist/modules/staff/backend/staff/leave-requests/page.js.map +2 -2
  53. package/dist/modules/staff/backend/staff/my-leave-requests/page.js.map +2 -2
  54. package/dist/modules/staff/backend/staff/team-members/page.js.map +2 -2
  55. package/dist/modules/staff/backend/staff/team-roles/page.js.map +1 -1
  56. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  57. package/dist/modules/staff/backend/staff/teams/page.js.map +2 -2
  58. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
  59. package/dist/modules/wms/components/backend/WmsConfigurationPage.js.map +2 -2
  60. package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js.map +2 -2
  61. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js.map +1 -1
  62. package/dist/modules/wms/components/backend/WmsLotDetailPage.js.map +1 -1
  63. package/dist/modules/wms/components/backend/WmsLotsListPage.js.map +2 -2
  64. package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js.map +1 -1
  65. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js.map +1 -1
  66. package/dist/modules/workflows/backend/definitions/page.js.map +1 -1
  67. package/dist/modules/workflows/backend/events/page.js.map +1 -1
  68. package/dist/modules/workflows/backend/instances/page.js.map +1 -1
  69. package/dist/modules/workflows/backend/tasks/page.js.map +1 -1
  70. package/jest.config.cjs +1 -1
  71. package/package.json +7 -7
  72. package/src/modules/api_keys/backend/api-keys/page.tsx +1 -1
  73. package/src/modules/attachments/components/AttachmentLibrary.tsx +2 -1
  74. package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +1 -1
  75. package/src/modules/audit_logs/components/AccessLogsTable.tsx +1 -1
  76. package/src/modules/audit_logs/components/AuditLogsActions.tsx +1 -1
  77. package/src/modules/auth/backend/roles/page.tsx +2 -1
  78. package/src/modules/auth/backend/users/page.tsx +2 -1
  79. package/src/modules/business_rules/backend/logs/page.tsx +1 -1
  80. package/src/modules/business_rules/backend/rules/page.tsx +1 -1
  81. package/src/modules/business_rules/backend/sets/page.tsx +1 -1
  82. package/src/modules/catalog/components/PriceKindSettings.tsx +1 -1
  83. package/src/modules/catalog/components/categories/CategoriesDataTable.tsx +1 -1
  84. package/src/modules/catalog/components/products/ProductsDataTable.tsx +2 -1
  85. package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +1 -1
  86. package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +1 -1
  87. package/src/modules/currencies/backend/currencies/page.tsx +1 -1
  88. package/src/modules/currencies/backend/exchange-rates/page.tsx +1 -1
  89. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +1 -1
  90. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +1 -1
  91. package/src/modules/customers/backend/customers/companies/page.tsx +2 -1
  92. package/src/modules/customers/backend/customers/deals/page.tsx +1 -1
  93. package/src/modules/customers/backend/customers/people/page.tsx +2 -1
  94. package/src/modules/customers/components/CustomerTodosTable.tsx +1 -1
  95. package/src/modules/data_sync/backend/data-sync/page.tsx +1 -1
  96. package/src/modules/dictionaries/components/DictionaryTable.tsx +2 -1
  97. package/src/modules/directory/backend/directory/organizations/page.tsx +1 -1
  98. package/src/modules/directory/backend/directory/tenants/page.tsx +2 -1
  99. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +2 -1
  100. package/src/modules/entities/components/SystemEntitiesTable.tsx +2 -1
  101. package/src/modules/entities/components/UserEntitiesTable.tsx +2 -1
  102. package/src/modules/feature_toggles/components/FeatureTogglesTable.tsx +2 -1
  103. package/src/modules/feature_toggles/components/OverridesTable.tsx +2 -1
  104. package/src/modules/inbox_ops/backend/inbox-ops/log/page.tsx +1 -1
  105. package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +1 -1
  106. package/src/modules/messages/components/MessagesInboxPageClient.tsx +1 -1
  107. package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +1 -1
  108. package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +2 -1
  109. package/src/modules/query_index/components/QueryIndexesTable.tsx +2 -1
  110. package/src/modules/resources/backend/resources/resource-types/page.tsx +2 -1
  111. package/src/modules/resources/backend/resources/resources/page.tsx +1 -1
  112. package/src/modules/sales/backend/sales/channels/offers/page.tsx +2 -1
  113. package/src/modules/sales/backend/sales/channels/page.tsx +2 -1
  114. package/src/modules/sales/components/AdjustmentKindSettings.tsx +1 -1
  115. package/src/modules/sales/components/PaymentMethodsSettings.tsx +1 -1
  116. package/src/modules/sales/components/ShippingMethodsSettings.tsx +1 -1
  117. package/src/modules/sales/components/TaxRatesSettings.tsx +1 -1
  118. package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +2 -1
  119. package/src/modules/sales/components/documents/AdjustmentsSection.tsx +1 -1
  120. package/src/modules/sales/components/documents/PaymentsSection.tsx +1 -1
  121. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +2 -1
  122. package/src/modules/sales/components/documents/SalesOrderDraftLines.tsx +1 -1
  123. package/src/modules/staff/backend/staff/leave-requests/page.tsx +2 -1
  124. package/src/modules/staff/backend/staff/my-leave-requests/page.tsx +2 -1
  125. package/src/modules/staff/backend/staff/team-members/page.tsx +3 -2
  126. package/src/modules/staff/backend/staff/team-roles/page.tsx +1 -1
  127. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +2 -1
  128. package/src/modules/staff/backend/staff/teams/page.tsx +2 -1
  129. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +2 -1
  130. package/src/modules/wms/components/backend/WmsConfigurationPage.tsx +2 -1
  131. package/src/modules/wms/components/backend/WmsInventoryConsolePage.tsx +4 -3
  132. package/src/modules/wms/components/backend/WmsLocationDetailPage.tsx +1 -1
  133. package/src/modules/wms/components/backend/WmsLotDetailPage.tsx +1 -1
  134. package/src/modules/wms/components/backend/WmsLotsListPage.tsx +2 -1
  135. package/src/modules/wms/components/backend/WmsOperationalDashboardPage.tsx +1 -1
  136. package/src/modules/wms/components/backend/WmsSkuDetailPage.tsx +1 -1
  137. package/src/modules/workflows/backend/definitions/page.tsx +1 -1
  138. package/src/modules/workflows/backend/events/page.tsx +1 -1
  139. package/src/modules/workflows/backend/instances/page.tsx +1 -1
  140. 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/query_index/components/QueryIndexesTable.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/query_index/extension-points'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { ColumnDef, SortingState } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { StatusBadge, type StatusBadgeVariant } from '@open-mercato/ui/primitives/status-badge'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('query_index').child({ component: 'indexes-table' })\n\nconst MUTATION_CONTEXT_ID = 'query_index.status.list:actions'\n\ntype Translator = (key: string, params?: Record<string, string | number>) => string\n\ntype PartitionStatus = {\n partitionIndex: number | null\n partitionCount: number | null\n status: 'reindexing' | 'purging' | 'stalled' | 'completed' | 'failed'\n processedCount?: number | null\n totalCount?: number | null\n heartbeatAt?: string | null\n startedAt?: string | null\n finishedAt?: string | null\n}\n\ntype JobStatus = {\n status: 'idle' | 'reindexing' | 'purging' | 'stalled' | 'failed'\n startedAt?: string | null\n finishedAt?: string | null\n heartbeatAt?: string | null\n processedCount?: number | null\n totalCount?: number | null\n partitions?: PartitionStatus[]\n scope?: {\n status?: 'reindexing' | 'purging' | 'stalled' | 'completed' | 'failed' | null\n processedCount?: number | null\n totalCount?: number | null\n } | null\n}\n\ntype Row = {\n entityId: string\n label: string\n baseCount: number | null\n indexCount: number | null\n vectorCount: number | null\n /** The entity is configured for vector search (declares `buildSource`). */\n vectorEnabled: boolean\n /** Vector indexing can actually run \u2014 provider reachable and auto-indexing on. */\n vectorIndexingActive?: boolean\n fulltextCount: number | null\n fulltextEnabled: boolean\n hasCustomFields?: boolean\n /** Legacy aggregate: query index AND vector coverage. Preserved for API consumers. */\n ok: boolean\n /** Query index alone is in sync with the base table \u2014 the signal this page reports. */\n queryIndexOk?: boolean\n job?: JobStatus\n}\n\ntype Resp = { items: Row[] }\n\nconst BACKEND_FILTER_ID = 'backend'\n// Kept at or below the repository-wide 100-row cap; the entity list grows with every\n// installed module, so it cannot be allowed to scale with the row count.\nconst PAGE_SIZE = 50\n\n/** Sort key for a column id. Mirrors the column `accessorFn`s so paging and sorting agree. */\nfunction sortValue(row: Row, columnId: string): string | number {\n if (columnId === 'entityId') return row.entityId\n if (columnId === 'baseCount') return row.baseCount ?? 0\n if (columnId === 'indexCount') return row.indexCount ?? 0\n if (columnId === 'vectorCount') return isVectorReportable(row) ? row.vectorCount ?? 0 : -1\n if (columnId === 'fulltextCount') return row.fulltextEnabled ? row.fulltextCount ?? 0 : -1\n return row.entityId\n}\n\ntype BackendFilter = 'vector' | 'fulltext' | 'custom_fields'\n\n/**\n * Vector coverage is only worth showing when embeddings can actually be written; otherwise\n * the count is a permanent 0 that reads as a gap. Older payloads without the flag are\n * treated as active so the column keeps working against an un-upgraded server.\n */\nfunction isVectorReportable(row: Row): boolean {\n return row.vectorEnabled && (row.vectorIndexingActive ?? true)\n}\n\nfunction matchesBackendFilter(row: Row, backend: BackendFilter): boolean {\n if (backend === 'vector') return row.vectorEnabled\n if (backend === 'fulltext') return row.fulltextEnabled\n return Boolean(row.hasCustomFields)\n}\n\nfunction formatCount(value: number | null): string {\n if (value == null) return '\u2014'\n return value.toLocaleString()\n}\n\nfunction formatNumeric(value: number | null | undefined): string | null {\n if (value == null) return null\n return Number(value).toLocaleString()\n}\n\nfunction formatProgressLabel(\n processed: number | null | undefined,\n total: number | null | undefined,\n t: Translator,\n): string | null {\n const processedText = formatNumeric(processed)\n if (!processedText) return null\n const totalText = formatNumeric(total)\n if (totalText) return t('query_index.table.status.progress', { processed: processedText, total: totalText })\n return t('query_index.table.status.progressSingle', { processed: processedText })\n}\n\nfunction translateJobStatus(\n t: Translator,\n status: JobStatus['status'] | undefined,\n ok: boolean,\n measured: boolean,\n): string {\n const idleLabel = () => {\n // No coverage snapshot yet \u2014 an async refresh is pending, so the index is not known to\n // be out of sync. Saying so would be the same false alarm this page used to raise.\n if (!measured) return t('query_index.table.status.not_measured')\n return ok ? t('query_index.table.status.in_sync') : t('query_index.table.status.out_of_sync')\n }\n if (!status || status === 'idle') return idleLabel()\n if (status === 'reindexing') return t('query_index.table.status.reindexing')\n if (status === 'purging') return t('query_index.table.status.purging')\n if (status === 'stalled') return t('query_index.table.status.stalled')\n if (status === 'failed') return t('query_index.table.status.failed')\n return idleLabel()\n}\n\nfunction translateScopeStatus(\n t: Translator,\n status: PartitionStatus['status'] | JobStatus['status'] | undefined | null,\n): string {\n if (status === 'reindexing') return t('query_index.table.status.scope.reindexing')\n if (status === 'purging') return t('query_index.table.status.scope.purging')\n if (status === 'stalled') return t('query_index.table.status.scope.stalled')\n if (status === 'failed') return t('query_index.table.status.scope.failed')\n return t('query_index.table.status.scope.completed')\n}\n\n\nfunction createColumns(t: Translator): ColumnDef<Row>[] {\n return [\n {\n id: 'entityId',\n header: () => t('query_index.table.columns.entity'),\n accessorKey: 'entityId',\n // The entity id is the only identifier on this page and ids run to ~45 characters\n // (`catalog:catalog_product_category_assignment`). DataTable's default 150px clamp\n // ellipsised them into indistinguishable `customers:customer\u2026` rows.\n meta: { priority: 1, maxWidth: '340px' },\n },\n {\n id: 'baseCount',\n header: () => t('query_index.table.columns.records'),\n accessorFn: (row) => row.baseCount ?? 0,\n cell: ({ row }) => <span>{formatCount(row.original.baseCount)}</span>,\n meta: { priority: 2 },\n },\n {\n id: 'indexCount',\n header: () => t('query_index.table.columns.indexed'),\n accessorFn: (row) => row.indexCount ?? 0,\n cell: ({ row }) => <span>{formatCount(row.original.indexCount)}</span>,\n meta: { priority: 2 },\n },\n {\n id: 'vectorCount',\n header: () => t('query_index.table.columns.vector'),\n accessorFn: (row) => (isVectorReportable(row) ? row.vectorCount ?? 0 : -1),\n cell: ({ row }) => {\n const record = row.original\n if (!isVectorReportable(record)) return <span>\u2014</span>\n const ok = record.vectorCount != null && record.baseCount != null && record.vectorCount === record.baseCount\n const display = formatCount(record.vectorCount)\n const className = ok ? 'text-status-success-text' : 'text-status-warning-text'\n return <span className={className}>{display}</span>\n },\n meta: { priority: 2 },\n },\n {\n id: 'fulltextCount',\n header: () => t('query_index.table.columns.fulltext'),\n accessorFn: (row) => (row.fulltextEnabled ? row.fulltextCount ?? 0 : -1),\n cell: ({ row }) => {\n const record = row.original\n if (!record.fulltextEnabled) return <span>\u2014</span>\n const ok = record.fulltextCount != null && record.baseCount != null && record.fulltextCount === record.baseCount\n const display = formatCount(record.fulltextCount)\n const className = ok ? 'text-status-success-text' : 'text-status-warning-text'\n return <span className={className}>{display}</span>\n },\n meta: { priority: 2 },\n },\n {\n id: 'status',\n header: () => t('query_index.table.columns.status'),\n cell: ({ row }) => {\n const record = row.original\n const job = record.job\n const partitions = job?.partitions ?? []\n const measured = record.baseCount != null || record.indexCount != null\n\n // A non-partitioned reindex \u2014 and every purge, which never partitions\n // (`lib/purge.ts` always passes `partitionIndex: null`) \u2014 is tracked as a scope row.\n // The server derives the top-level status from partition rows only, so it reports\n // `idle` for those runs; without folding the scope row in, the default reindex and\n // every purge would render as though nothing were happening.\n const scopeStatus = job?.scope?.status ?? null\n const scopeInFlight = scopeStatus != null && scopeStatus !== 'completed'\n const effectiveStatus: JobStatus['status'] | undefined = job\n ? (job.status !== 'idle'\n ? job.status\n : (scopeInFlight ? (scopeStatus as JobStatus['status']) : 'idle'))\n : undefined\n\n const indexOk = record.queryIndexOk ?? record.ok\n const ok = indexOk && (!job || effectiveStatus === 'idle')\n const jobFailed = effectiveStatus === 'failed'\n const statusText = translateJobStatus(t, effectiveStatus, ok, measured)\n\n const jobInFlight = Boolean(effectiveStatus && effectiveStatus !== 'idle')\n || partitions.some((part) => part.status !== 'completed')\n\n // Job counters and index coverage are different numbers: the counters describe the\n // last reindex run, the Records/Indexed columns describe the index right now. While\n // a job runs, \"Reindexing (5/14)\" is the only place progress is visible. Once it is\n // idle the counters are stale and duplicate the columns \u2014 worse, they can contradict\n // them: an entity whose last run processed 14 rows but landed 2 in the index rendered\n // \"Out of sync (14/14)\" next to Records 14 / Indexed 2.\n const jobProgress = jobInFlight && job\n ? formatProgressLabel(job.processedCount ?? null, job.totalCount ?? null, t)\n : null\n const label = jobProgress\n ? t('query_index.table.status.withProgress', { status: statusText, progress: jobProgress })\n : statusText\n let variant: StatusBadgeVariant = 'neutral'\n if (job) {\n if (effectiveStatus === 'stalled' || jobFailed) variant = 'error'\n else if (effectiveStatus === 'reindexing' || effectiveStatus === 'purging') variant = 'warning'\n else variant = ok ? 'success' : 'neutral'\n } else {\n variant = ok ? 'success' : 'neutral'\n }\n\n const lines: string[] = []\n\n // Per-partition detail earns its space only while work is in flight \u2014 that is when\n // it tells you which partition is running or stuck. Once the job is idle and every\n // partition is finished, each line just restates the badge, on every row.\n if (jobInFlight) {\n if (job?.scope && partitions.length <= 1) {\n const scopeStatusText = translateScopeStatus(t, scopeStatus)\n const scopeProgress = formatProgressLabel(job.scope.processedCount ?? null, job.scope.totalCount ?? null, t)\n const scopeLabel = t('query_index.table.status.scopeLabel')\n lines.push(`${scopeLabel}: ${scopeStatusText}${scopeProgress ? ` (${scopeProgress})` : ''}`)\n }\n\n if (partitions.length > 1) {\n for (const part of partitions) {\n const partitionLabel =\n part.partitionIndex != null\n ? t('query_index.table.status.partitionLabel', { index: Number(part.partitionIndex) + 1 })\n : t('query_index.table.status.scopeLabel')\n const partitionStatus = translateScopeStatus(t, part.status)\n const partitionProgress = formatProgressLabel(part.processedCount ?? null, part.totalCount ?? null, t)\n lines.push(`${partitionLabel}: ${partitionStatus}${partitionProgress ? ` (${partitionProgress})` : ''}`)\n }\n }\n }\n\n return (\n <div className=\"space-y-1.5\">\n <div>\n <StatusBadge variant={variant} dot>\n {label}\n </StatusBadge>\n </div>\n {lines.length > 0 && (\n <div className=\"text-xs text-muted-foreground\">\n {lines.map((line, idx) => (\n <div key={idx}>{line}</div>\n ))}\n </div>\n )}\n </div>\n )\n },\n meta: { priority: 1 },\n },\n ]\n}\n\nexport default function QueryIndexesTable() {\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'entityId', desc: false }])\n const [search, setSearch] = React.useState('')\n const [page, setPage] = React.useState(1)\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const qc = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n const [refreshSeq, setRefreshSeq] = React.useState(0)\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const columns = React.useMemo(() => createColumns(t), [t])\n const filters = React.useMemo<FilterDef[]>(() => [\n {\n id: BACKEND_FILTER_ID,\n label: t('query_index.table.filters.backend'),\n type: 'select',\n options: [\n { label: t('query_index.table.filters.backendOptions.vector'), value: 'vector' },\n { label: t('query_index.table.filters.backendOptions.fulltext'), value: 'fulltext' },\n { label: t('query_index.table.filters.backendOptions.customFields'), value: 'custom_fields' },\n ],\n },\n ], [t])\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: MUTATION_CONTEXT_ID,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const { data, isLoading } = useQuery<Resp>({\n queryKey: ['query-index-status', scopeVersion, refreshSeq],\n queryFn: async () => {\n const baseUrl = '/api/query_index/status'\n const url = refreshSeq > 0 ? `${baseUrl}?refresh=${refreshSeq}` : baseUrl\n return readApiResultOrThrow<Resp>(\n url,\n undefined,\n { errorMessage: t('query_index.table.errors.loadFailed') },\n )\n },\n refetchInterval: 4000,\n })\n\n const rowsAll = data?.items || []\n const matchedRows = React.useMemo(() => {\n const backend = filterValues[BACKEND_FILTER_ID] as BackendFilter | undefined\n const q = search.trim().toLowerCase()\n if (!q && !backend) return rowsAll\n return rowsAll.filter((row) => {\n if (q && !row.entityId.toLowerCase().includes(q)) return false\n if (backend && !matchesBackendFilter(row, backend)) return false\n return true\n })\n }, [rowsAll, search, filterValues])\n\n // The endpoint returns every entity in one payload, so paging is ours to do. Sorting has\n // to happen here rather than in the table: slicing first would let DataTable sort only the\n // visible page, so page 2 would not hold the rows that sort into it.\n const sortedRows = React.useMemo(() => {\n const sort = sorting[0]\n if (!sort) return matchedRows\n const direction = sort.desc ? -1 : 1\n return [...matchedRows].sort((a, b) => {\n const left = sortValue(a, sort.id)\n const right = sortValue(b, sort.id)\n if (typeof left === 'string' || typeof right === 'string') {\n return String(left).localeCompare(String(right)) * direction\n }\n return (Number(left) - Number(right)) * direction\n })\n }, [matchedRows, sorting])\n\n const total = sortedRows.length\n const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))\n const currentPage = Math.min(page, totalPages)\n const rows = React.useMemo(\n () => sortedRows.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE),\n [sortedRows, currentPage],\n )\n\n const pagination = React.useMemo(() => ({\n page: currentPage,\n pageSize: PAGE_SIZE,\n total,\n totalPages,\n onPageChange: setPage,\n }), [currentPage, total, totalPages])\n\n const trigger = React.useCallback(\n async (action: 'reindex' | 'purge', entityId: string, opts?: { force?: boolean }) => {\n const body: Record<string, unknown> = { entityType: entityId }\n if (opts?.force) body.force = true\n const actionLabel =\n action === 'purge' ? t('query_index.table.actions.purge') : t('query_index.table.actions.reindex')\n const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })\n try {\n await runMutation({\n operation: () =>\n apiCallOrThrow(`/api/query_index/${action}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n }, { errorMessage }),\n context: {\n formId: MUTATION_CONTEXT_ID,\n resourceKind: 'query_index',\n resourceId: entityId,\n retryLastMutation,\n },\n mutationPayload: { action, entityType: entityId, force: Boolean(opts?.force) },\n })\n } catch (err) {\n // Expected operational failures (e.g. a 503 when a search backend is not\n // configured) \u2014 surface a flash toast, not an alert or an error-level\n // stack dump that reads like an unhandled exception.\n const message = err instanceof Error && err.message ? err.message : errorMessage\n logger.warn('Index action failed', { message })\n flash(message, 'error')\n }\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n },\n [qc, t, runMutation, retryLastMutation],\n )\n\n const triggerVector = React.useCallback(\n async (action: 'reindex' | 'purge', entityId: string) => {\n if (action === 'purge') {\n const confirmed = await confirm({\n title: t('query_index.table.confirm.vectorPurge'),\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n\n const actionLabel = action === 'purge'\n ? t('query_index.table.actions.vectorPurge')\n : t('query_index.table.actions.vectorReindex')\n const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })\n try {\n await runMutation({\n operation: () =>\n apiCallOrThrow('/api/search/embeddings/reindex', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n entityId,\n purgeFirst: action === 'purge',\n }),\n }, { errorMessage }),\n context: {\n formId: MUTATION_CONTEXT_ID,\n resourceKind: 'query_index.vector',\n resourceId: entityId,\n retryLastMutation,\n },\n mutationPayload: { action, entityId, purgeFirst: action === 'purge' },\n })\n } catch (err) {\n const message = err instanceof Error && err.message ? err.message : errorMessage\n logger.warn('Vector action failed', { message })\n flash(message, 'error')\n }\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n },\n [confirm, qc, t, runMutation, retryLastMutation],\n )\n\n const triggerFulltext = React.useCallback(\n async (action: 'reindex' | 'purge', entityId: string) => {\n if (action === 'purge') {\n const confirmed = await confirm({\n title: t('query_index.table.confirm.fulltextPurge'),\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n\n const actionLabel = action === 'purge'\n ? t('query_index.table.actions.fulltextPurge')\n : t('query_index.table.actions.fulltextReindex')\n const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })\n try {\n await runMutation({\n operation: () =>\n apiCallOrThrow('/api/search/reindex', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n action: action === 'purge' ? 'clear' : 'reindex',\n entityId,\n }),\n }, { errorMessage }),\n context: {\n formId: MUTATION_CONTEXT_ID,\n resourceKind: 'query_index.fulltext',\n resourceId: entityId,\n retryLastMutation,\n },\n mutationPayload: { action, entityId },\n })\n } catch (err) {\n const message = err instanceof Error && err.message ? err.message : errorMessage\n logger.warn('Fulltext action failed', { message })\n flash(message, 'error')\n }\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n },\n [confirm, qc, t, runMutation, retryLastMutation],\n )\n\n return (\n <>\n <DataTable\n title={t('query_index.nav.queryIndexes')}\n actions={(\n <>\n <Button\n variant=\"outline\"\n onClick={() => {\n setRefreshSeq((v) => v + 1)\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n }}\n >\n {t('query_index.table.refresh')}\n </Button>\n </>\n )}\n columns={columns}\n data={rows}\n searchValue={search}\n searchPlaceholder={t('query_index.table.searchPlaceholder')}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(values) => {\n setFilterValues(values)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n sortable\n manualSorting\n sorting={sorting}\n onSortingChange={(next) => {\n setSorting(next)\n setPage(1)\n }}\n perspective={{ tableId: extensionPoints.hosts.statusTable.tableId }}\n rowActions={(row) => {\n const items: Array<{ id: string; label: string; onSelect: () => void; destructive?: boolean }> = [\n { id: 'reindex', label: t('query_index.table.actions.reindex'), onSelect: () => void trigger('reindex', row.entityId) },\n {\n id: 'reindex-force',\n label: t('query_index.table.actions.reindexForce'),\n onSelect: () => void trigger('reindex', row.entityId, { force: true }),\n },\n {\n id: 'purge',\n label: t('query_index.table.actions.purge'),\n destructive: true,\n onSelect: () => void trigger('purge', row.entityId),\n },\n ]\n\n // Manual vector actions stay available whenever the entity declares `buildSource`,\n // even when auto-indexing is off \u2014 a manual reindex is still a valid operation.\n if (row.vectorEnabled) {\n items.push(\n {\n id: 'vector-reindex',\n label: t('query_index.table.actions.vectorReindex'),\n onSelect: () => void triggerVector('reindex', row.entityId),\n },\n {\n id: 'vector-purge',\n label: t('query_index.table.actions.vectorPurge'),\n destructive: true,\n onSelect: () => void triggerVector('purge', row.entityId),\n },\n )\n }\n\n if (row.fulltextEnabled) {\n items.push(\n {\n id: 'fulltext-reindex',\n label: t('query_index.table.actions.fulltextReindex'),\n onSelect: () => void triggerFulltext('reindex', row.entityId),\n },\n {\n id: 'fulltext-purge',\n label: t('query_index.table.actions.fulltextPurge'),\n destructive: true,\n onSelect: () => void triggerFulltext('purge', row.entityId),\n },\n )\n }\n\n return <RowActions items={items} />\n }}\n pagination={pagination}\n isLoading={isLoading}\n />\n {ConfirmDialogElement}\n </>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/query_index/extension-points'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { StatusBadge, type StatusBadgeVariant } from '@open-mercato/ui/primitives/status-badge'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('query_index').child({ component: 'indexes-table' })\n\nconst MUTATION_CONTEXT_ID = 'query_index.status.list:actions'\n\ntype Translator = (key: string, params?: Record<string, string | number>) => string\n\ntype PartitionStatus = {\n partitionIndex: number | null\n partitionCount: number | null\n status: 'reindexing' | 'purging' | 'stalled' | 'completed' | 'failed'\n processedCount?: number | null\n totalCount?: number | null\n heartbeatAt?: string | null\n startedAt?: string | null\n finishedAt?: string | null\n}\n\ntype JobStatus = {\n status: 'idle' | 'reindexing' | 'purging' | 'stalled' | 'failed'\n startedAt?: string | null\n finishedAt?: string | null\n heartbeatAt?: string | null\n processedCount?: number | null\n totalCount?: number | null\n partitions?: PartitionStatus[]\n scope?: {\n status?: 'reindexing' | 'purging' | 'stalled' | 'completed' | 'failed' | null\n processedCount?: number | null\n totalCount?: number | null\n } | null\n}\n\ntype Row = {\n entityId: string\n label: string\n baseCount: number | null\n indexCount: number | null\n vectorCount: number | null\n /** The entity is configured for vector search (declares `buildSource`). */\n vectorEnabled: boolean\n /** Vector indexing can actually run \u2014 provider reachable and auto-indexing on. */\n vectorIndexingActive?: boolean\n fulltextCount: number | null\n fulltextEnabled: boolean\n hasCustomFields?: boolean\n /** Legacy aggregate: query index AND vector coverage. Preserved for API consumers. */\n ok: boolean\n /** Query index alone is in sync with the base table \u2014 the signal this page reports. */\n queryIndexOk?: boolean\n job?: JobStatus\n}\n\ntype Resp = { items: Row[] }\n\nconst BACKEND_FILTER_ID = 'backend'\n// Kept at or below the repository-wide 100-row cap; the entity list grows with every\n// installed module, so it cannot be allowed to scale with the row count.\nconst PAGE_SIZE = 50\n\n/** Sort key for a column id. Mirrors the column `accessorFn`s so paging and sorting agree. */\nfunction sortValue(row: Row, columnId: string): string | number {\n if (columnId === 'entityId') return row.entityId\n if (columnId === 'baseCount') return row.baseCount ?? 0\n if (columnId === 'indexCount') return row.indexCount ?? 0\n if (columnId === 'vectorCount') return isVectorReportable(row) ? row.vectorCount ?? 0 : -1\n if (columnId === 'fulltextCount') return row.fulltextEnabled ? row.fulltextCount ?? 0 : -1\n return row.entityId\n}\n\ntype BackendFilter = 'vector' | 'fulltext' | 'custom_fields'\n\n/**\n * Vector coverage is only worth showing when embeddings can actually be written; otherwise\n * the count is a permanent 0 that reads as a gap. Older payloads without the flag are\n * treated as active so the column keeps working against an un-upgraded server.\n */\nfunction isVectorReportable(row: Row): boolean {\n return row.vectorEnabled && (row.vectorIndexingActive ?? true)\n}\n\nfunction matchesBackendFilter(row: Row, backend: BackendFilter): boolean {\n if (backend === 'vector') return row.vectorEnabled\n if (backend === 'fulltext') return row.fulltextEnabled\n return Boolean(row.hasCustomFields)\n}\n\nfunction formatCount(value: number | null): string {\n if (value == null) return '\u2014'\n return value.toLocaleString()\n}\n\nfunction formatNumeric(value: number | null | undefined): string | null {\n if (value == null) return null\n return Number(value).toLocaleString()\n}\n\nfunction formatProgressLabel(\n processed: number | null | undefined,\n total: number | null | undefined,\n t: Translator,\n): string | null {\n const processedText = formatNumeric(processed)\n if (!processedText) return null\n const totalText = formatNumeric(total)\n if (totalText) return t('query_index.table.status.progress', { processed: processedText, total: totalText })\n return t('query_index.table.status.progressSingle', { processed: processedText })\n}\n\nfunction translateJobStatus(\n t: Translator,\n status: JobStatus['status'] | undefined,\n ok: boolean,\n measured: boolean,\n): string {\n const idleLabel = () => {\n // No coverage snapshot yet \u2014 an async refresh is pending, so the index is not known to\n // be out of sync. Saying so would be the same false alarm this page used to raise.\n if (!measured) return t('query_index.table.status.not_measured')\n return ok ? t('query_index.table.status.in_sync') : t('query_index.table.status.out_of_sync')\n }\n if (!status || status === 'idle') return idleLabel()\n if (status === 'reindexing') return t('query_index.table.status.reindexing')\n if (status === 'purging') return t('query_index.table.status.purging')\n if (status === 'stalled') return t('query_index.table.status.stalled')\n if (status === 'failed') return t('query_index.table.status.failed')\n return idleLabel()\n}\n\nfunction translateScopeStatus(\n t: Translator,\n status: PartitionStatus['status'] | JobStatus['status'] | undefined | null,\n): string {\n if (status === 'reindexing') return t('query_index.table.status.scope.reindexing')\n if (status === 'purging') return t('query_index.table.status.scope.purging')\n if (status === 'stalled') return t('query_index.table.status.scope.stalled')\n if (status === 'failed') return t('query_index.table.status.scope.failed')\n return t('query_index.table.status.scope.completed')\n}\n\n\nfunction createColumns(t: Translator): ColumnDef<Row>[] {\n return [\n {\n id: 'entityId',\n header: () => t('query_index.table.columns.entity'),\n accessorKey: 'entityId',\n // The entity id is the only identifier on this page and ids run to ~45 characters\n // (`catalog:catalog_product_category_assignment`). DataTable's default 150px clamp\n // ellipsised them into indistinguishable `customers:customer\u2026` rows.\n meta: { priority: 1, maxWidth: '340px' },\n },\n {\n id: 'baseCount',\n header: () => t('query_index.table.columns.records'),\n accessorFn: (row) => row.baseCount ?? 0,\n cell: ({ row }) => <span>{formatCount(row.original.baseCount)}</span>,\n meta: { priority: 2 },\n },\n {\n id: 'indexCount',\n header: () => t('query_index.table.columns.indexed'),\n accessorFn: (row) => row.indexCount ?? 0,\n cell: ({ row }) => <span>{formatCount(row.original.indexCount)}</span>,\n meta: { priority: 2 },\n },\n {\n id: 'vectorCount',\n header: () => t('query_index.table.columns.vector'),\n accessorFn: (row) => (isVectorReportable(row) ? row.vectorCount ?? 0 : -1),\n cell: ({ row }) => {\n const record = row.original\n if (!isVectorReportable(record)) return <span>\u2014</span>\n const ok = record.vectorCount != null && record.baseCount != null && record.vectorCount === record.baseCount\n const display = formatCount(record.vectorCount)\n const className = ok ? 'text-status-success-text' : 'text-status-warning-text'\n return <span className={className}>{display}</span>\n },\n meta: { priority: 2 },\n },\n {\n id: 'fulltextCount',\n header: () => t('query_index.table.columns.fulltext'),\n accessorFn: (row) => (row.fulltextEnabled ? row.fulltextCount ?? 0 : -1),\n cell: ({ row }) => {\n const record = row.original\n if (!record.fulltextEnabled) return <span>\u2014</span>\n const ok = record.fulltextCount != null && record.baseCount != null && record.fulltextCount === record.baseCount\n const display = formatCount(record.fulltextCount)\n const className = ok ? 'text-status-success-text' : 'text-status-warning-text'\n return <span className={className}>{display}</span>\n },\n meta: { priority: 2 },\n },\n {\n id: 'status',\n header: () => t('query_index.table.columns.status'),\n cell: ({ row }) => {\n const record = row.original\n const job = record.job\n const partitions = job?.partitions ?? []\n const measured = record.baseCount != null || record.indexCount != null\n\n // A non-partitioned reindex \u2014 and every purge, which never partitions\n // (`lib/purge.ts` always passes `partitionIndex: null`) \u2014 is tracked as a scope row.\n // The server derives the top-level status from partition rows only, so it reports\n // `idle` for those runs; without folding the scope row in, the default reindex and\n // every purge would render as though nothing were happening.\n const scopeStatus = job?.scope?.status ?? null\n const scopeInFlight = scopeStatus != null && scopeStatus !== 'completed'\n const effectiveStatus: JobStatus['status'] | undefined = job\n ? (job.status !== 'idle'\n ? job.status\n : (scopeInFlight ? (scopeStatus as JobStatus['status']) : 'idle'))\n : undefined\n\n const indexOk = record.queryIndexOk ?? record.ok\n const ok = indexOk && (!job || effectiveStatus === 'idle')\n const jobFailed = effectiveStatus === 'failed'\n const statusText = translateJobStatus(t, effectiveStatus, ok, measured)\n\n const jobInFlight = Boolean(effectiveStatus && effectiveStatus !== 'idle')\n || partitions.some((part) => part.status !== 'completed')\n\n // Job counters and index coverage are different numbers: the counters describe the\n // last reindex run, the Records/Indexed columns describe the index right now. While\n // a job runs, \"Reindexing (5/14)\" is the only place progress is visible. Once it is\n // idle the counters are stale and duplicate the columns \u2014 worse, they can contradict\n // them: an entity whose last run processed 14 rows but landed 2 in the index rendered\n // \"Out of sync (14/14)\" next to Records 14 / Indexed 2.\n const jobProgress = jobInFlight && job\n ? formatProgressLabel(job.processedCount ?? null, job.totalCount ?? null, t)\n : null\n const label = jobProgress\n ? t('query_index.table.status.withProgress', { status: statusText, progress: jobProgress })\n : statusText\n let variant: StatusBadgeVariant = 'neutral'\n if (job) {\n if (effectiveStatus === 'stalled' || jobFailed) variant = 'error'\n else if (effectiveStatus === 'reindexing' || effectiveStatus === 'purging') variant = 'warning'\n else variant = ok ? 'success' : 'neutral'\n } else {\n variant = ok ? 'success' : 'neutral'\n }\n\n const lines: string[] = []\n\n // Per-partition detail earns its space only while work is in flight \u2014 that is when\n // it tells you which partition is running or stuck. Once the job is idle and every\n // partition is finished, each line just restates the badge, on every row.\n if (jobInFlight) {\n if (job?.scope && partitions.length <= 1) {\n const scopeStatusText = translateScopeStatus(t, scopeStatus)\n const scopeProgress = formatProgressLabel(job.scope.processedCount ?? null, job.scope.totalCount ?? null, t)\n const scopeLabel = t('query_index.table.status.scopeLabel')\n lines.push(`${scopeLabel}: ${scopeStatusText}${scopeProgress ? ` (${scopeProgress})` : ''}`)\n }\n\n if (partitions.length > 1) {\n for (const part of partitions) {\n const partitionLabel =\n part.partitionIndex != null\n ? t('query_index.table.status.partitionLabel', { index: Number(part.partitionIndex) + 1 })\n : t('query_index.table.status.scopeLabel')\n const partitionStatus = translateScopeStatus(t, part.status)\n const partitionProgress = formatProgressLabel(part.processedCount ?? null, part.totalCount ?? null, t)\n lines.push(`${partitionLabel}: ${partitionStatus}${partitionProgress ? ` (${partitionProgress})` : ''}`)\n }\n }\n }\n\n return (\n <div className=\"space-y-1.5\">\n <div>\n <StatusBadge variant={variant} dot>\n {label}\n </StatusBadge>\n </div>\n {lines.length > 0 && (\n <div className=\"text-xs text-muted-foreground\">\n {lines.map((line, idx) => (\n <div key={idx}>{line}</div>\n ))}\n </div>\n )}\n </div>\n )\n },\n meta: { priority: 1 },\n },\n ]\n}\n\nexport default function QueryIndexesTable() {\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'entityId', desc: false }])\n const [search, setSearch] = React.useState('')\n const [page, setPage] = React.useState(1)\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const qc = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n const [refreshSeq, setRefreshSeq] = React.useState(0)\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const columns = React.useMemo(() => createColumns(t), [t])\n const filters = React.useMemo<FilterDef[]>(() => [\n {\n id: BACKEND_FILTER_ID,\n label: t('query_index.table.filters.backend'),\n type: 'select',\n options: [\n { label: t('query_index.table.filters.backendOptions.vector'), value: 'vector' },\n { label: t('query_index.table.filters.backendOptions.fulltext'), value: 'fulltext' },\n { label: t('query_index.table.filters.backendOptions.customFields'), value: 'custom_fields' },\n ],\n },\n ], [t])\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: MUTATION_CONTEXT_ID,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const { data, isLoading } = useQuery<Resp>({\n queryKey: ['query-index-status', scopeVersion, refreshSeq],\n queryFn: async () => {\n const baseUrl = '/api/query_index/status'\n const url = refreshSeq > 0 ? `${baseUrl}?refresh=${refreshSeq}` : baseUrl\n return readApiResultOrThrow<Resp>(\n url,\n undefined,\n { errorMessage: t('query_index.table.errors.loadFailed') },\n )\n },\n refetchInterval: 4000,\n })\n\n const rowsAll = data?.items || []\n const matchedRows = React.useMemo(() => {\n const backend = filterValues[BACKEND_FILTER_ID] as BackendFilter | undefined\n const q = search.trim().toLowerCase()\n if (!q && !backend) return rowsAll\n return rowsAll.filter((row) => {\n if (q && !row.entityId.toLowerCase().includes(q)) return false\n if (backend && !matchesBackendFilter(row, backend)) return false\n return true\n })\n }, [rowsAll, search, filterValues])\n\n // The endpoint returns every entity in one payload, so paging is ours to do. Sorting has\n // to happen here rather than in the table: slicing first would let DataTable sort only the\n // visible page, so page 2 would not hold the rows that sort into it.\n const sortedRows = React.useMemo(() => {\n const sort = sorting[0]\n if (!sort) return matchedRows\n const direction = sort.desc ? -1 : 1\n return [...matchedRows].sort((a, b) => {\n const left = sortValue(a, sort.id)\n const right = sortValue(b, sort.id)\n if (typeof left === 'string' || typeof right === 'string') {\n return String(left).localeCompare(String(right)) * direction\n }\n return (Number(left) - Number(right)) * direction\n })\n }, [matchedRows, sorting])\n\n const total = sortedRows.length\n const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))\n const currentPage = Math.min(page, totalPages)\n const rows = React.useMemo(\n () => sortedRows.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE),\n [sortedRows, currentPage],\n )\n\n const pagination = React.useMemo(() => ({\n page: currentPage,\n pageSize: PAGE_SIZE,\n total,\n totalPages,\n onPageChange: setPage,\n }), [currentPage, total, totalPages])\n\n const trigger = React.useCallback(\n async (action: 'reindex' | 'purge', entityId: string, opts?: { force?: boolean }) => {\n const body: Record<string, unknown> = { entityType: entityId }\n if (opts?.force) body.force = true\n const actionLabel =\n action === 'purge' ? t('query_index.table.actions.purge') : t('query_index.table.actions.reindex')\n const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })\n try {\n await runMutation({\n operation: () =>\n apiCallOrThrow(`/api/query_index/${action}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n }, { errorMessage }),\n context: {\n formId: MUTATION_CONTEXT_ID,\n resourceKind: 'query_index',\n resourceId: entityId,\n retryLastMutation,\n },\n mutationPayload: { action, entityType: entityId, force: Boolean(opts?.force) },\n })\n } catch (err) {\n // Expected operational failures (e.g. a 503 when a search backend is not\n // configured) \u2014 surface a flash toast, not an alert or an error-level\n // stack dump that reads like an unhandled exception.\n const message = err instanceof Error && err.message ? err.message : errorMessage\n logger.warn('Index action failed', { message })\n flash(message, 'error')\n }\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n },\n [qc, t, runMutation, retryLastMutation],\n )\n\n const triggerVector = React.useCallback(\n async (action: 'reindex' | 'purge', entityId: string) => {\n if (action === 'purge') {\n const confirmed = await confirm({\n title: t('query_index.table.confirm.vectorPurge'),\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n\n const actionLabel = action === 'purge'\n ? t('query_index.table.actions.vectorPurge')\n : t('query_index.table.actions.vectorReindex')\n const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })\n try {\n await runMutation({\n operation: () =>\n apiCallOrThrow('/api/search/embeddings/reindex', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n entityId,\n purgeFirst: action === 'purge',\n }),\n }, { errorMessage }),\n context: {\n formId: MUTATION_CONTEXT_ID,\n resourceKind: 'query_index.vector',\n resourceId: entityId,\n retryLastMutation,\n },\n mutationPayload: { action, entityId, purgeFirst: action === 'purge' },\n })\n } catch (err) {\n const message = err instanceof Error && err.message ? err.message : errorMessage\n logger.warn('Vector action failed', { message })\n flash(message, 'error')\n }\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n },\n [confirm, qc, t, runMutation, retryLastMutation],\n )\n\n const triggerFulltext = React.useCallback(\n async (action: 'reindex' | 'purge', entityId: string) => {\n if (action === 'purge') {\n const confirmed = await confirm({\n title: t('query_index.table.confirm.fulltextPurge'),\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n\n const actionLabel = action === 'purge'\n ? t('query_index.table.actions.fulltextPurge')\n : t('query_index.table.actions.fulltextReindex')\n const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })\n try {\n await runMutation({\n operation: () =>\n apiCallOrThrow('/api/search/reindex', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n action: action === 'purge' ? 'clear' : 'reindex',\n entityId,\n }),\n }, { errorMessage }),\n context: {\n formId: MUTATION_CONTEXT_ID,\n resourceKind: 'query_index.fulltext',\n resourceId: entityId,\n retryLastMutation,\n },\n mutationPayload: { action, entityId },\n })\n } catch (err) {\n const message = err instanceof Error && err.message ? err.message : errorMessage\n logger.warn('Fulltext action failed', { message })\n flash(message, 'error')\n }\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n },\n [confirm, qc, t, runMutation, retryLastMutation],\n )\n\n return (\n <>\n <DataTable\n title={t('query_index.nav.queryIndexes')}\n actions={(\n <>\n <Button\n variant=\"outline\"\n onClick={() => {\n setRefreshSeq((v) => v + 1)\n qc.invalidateQueries({ queryKey: ['query-index-status'] })\n }}\n >\n {t('query_index.table.refresh')}\n </Button>\n </>\n )}\n columns={columns}\n data={rows}\n searchValue={search}\n searchPlaceholder={t('query_index.table.searchPlaceholder')}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(values) => {\n setFilterValues(values)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n sortable\n manualSorting\n sorting={sorting}\n onSortingChange={(next) => {\n setSorting(next)\n setPage(1)\n }}\n perspective={{ tableId: extensionPoints.hosts.statusTable.tableId }}\n rowActions={(row) => {\n const items: Array<{ id: string; label: string; onSelect: () => void; destructive?: boolean }> = [\n { id: 'reindex', label: t('query_index.table.actions.reindex'), onSelect: () => void trigger('reindex', row.entityId) },\n {\n id: 'reindex-force',\n label: t('query_index.table.actions.reindexForce'),\n onSelect: () => void trigger('reindex', row.entityId, { force: true }),\n },\n {\n id: 'purge',\n label: t('query_index.table.actions.purge'),\n destructive: true,\n onSelect: () => void trigger('purge', row.entityId),\n },\n ]\n\n // Manual vector actions stay available whenever the entity declares `buildSource`,\n // even when auto-indexing is off \u2014 a manual reindex is still a valid operation.\n if (row.vectorEnabled) {\n items.push(\n {\n id: 'vector-reindex',\n label: t('query_index.table.actions.vectorReindex'),\n onSelect: () => void triggerVector('reindex', row.entityId),\n },\n {\n id: 'vector-purge',\n label: t('query_index.table.actions.vectorPurge'),\n destructive: true,\n onSelect: () => void triggerVector('purge', row.entityId),\n },\n )\n }\n\n if (row.fulltextEnabled) {\n items.push(\n {\n id: 'fulltext-reindex',\n label: t('query_index.table.actions.fulltextReindex'),\n onSelect: () => void triggerFulltext('reindex', row.entityId),\n },\n {\n id: 'fulltext-purge',\n label: t('query_index.table.actions.fulltextPurge'),\n destructive: true,\n onSelect: () => void triggerFulltext('purge', row.entityId),\n },\n )\n }\n\n return <RowActions items={items} />\n }}\n pagination={pagination}\n isLoading={isLoading}\n />\n {ConfirmDialogElement}\n </>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/resources/backend/resources/resource-types/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/resources/extension-points'\nimport { useRouter } from 'next/navigation'\nimport Link from 'next/link'\nimport type { ColumnDef, SortingState } from '@tanstack/react-table'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { markdownToPlainText } from '@open-mercato/ui/backend/markdown/markdownToPlainText'\nimport { DataTable, withDataTableNamespaces } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { renderDictionaryColor, renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { Package } from 'lucide-react'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { formatDateTime } from '@open-mercato/shared/lib/time'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('resources').child({ component: 'resource-types-page' })\n\nconst PAGE_SIZE = 50\nconst DESCRIPTION_CLASSNAME = 'line-clamp-3 whitespace-pre-line text-sm text-foreground'\nconst SUBTEXT_CLASSNAME = 'line-clamp-2 text-xs text-muted-foreground'\nconst RESOURCE_TYPES_MUTATION_CONTEXT_ID = 'resources.resource-types.list'\n\ntype ResourceTypeRow = {\n id: string\n name: string\n description: string | null\n appearanceIcon: string | null\n appearanceColor: string | null\n updatedAt: string | null\n resourceCount: number\n}\n\ntype ResourceTypesResponse = {\n items?: Array<Record<string, unknown>>\n total?: number\n totalPages?: number\n}\n\ntype ResourceTypesMutationContext = {\n formId: string\n resourceKind: string\n resourceId?: string\n retryLastMutation: () => Promise<boolean>\n}\n\nexport default function ResourcesResourceTypesPage() {\n const translate = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const router = useRouter()\n const scopeVersion = useOrganizationScopeVersion()\n const [rows, setRows] = React.useState<ResourceTypeRow[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'name', desc: false }])\n const [search, setSearch] = React.useState('')\n const [isLoading, setIsLoading] = React.useState(true)\n const [reloadToken, setReloadToken] = React.useState(0)\n const { runMutation, retryLastMutation } = useGuardedMutation<ResourceTypesMutationContext>({\n contextId: RESOURCE_TYPES_MUTATION_CONTEXT_ID,\n blockedMessage: translate('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const runResourceTypeMutation = React.useCallback(\n async <T,>(\n operation: () => Promise<T>,\n mutationPayload: Record<string, unknown>,\n resourceId?: string,\n ): Promise<T> => runMutation({\n operation,\n mutationPayload,\n context: {\n formId: RESOURCE_TYPES_MUTATION_CONTEXT_ID,\n resourceKind: 'resources.resourceType',\n resourceId,\n retryLastMutation,\n },\n }),\n [retryLastMutation, runMutation],\n )\n\n const translations = React.useMemo(() => ({\n title: translate('resources.resourceTypes.page.title', 'Resource types'),\n description: translate('resources.resourceTypes.page.description', 'Organize shared resources by category.'),\n table: {\n name: translate('resources.resourceTypes.table.name', 'Name'),\n description: translate('resources.resourceTypes.table.description', 'Description'),\n appearance: translate('resources.resourceTypes.table.appearance', 'Appearance'),\n resources: translate('resources.resourceTypes.table.resources', 'Resources'),\n updatedAt: translate('resources.resourceTypes.table.updatedAt', 'Updated'),\n empty: translate('resources.resourceTypes.table.empty', 'No resource types yet.'),\n search: translate('resources.resourceTypes.table.search', 'Search resource types\u2026'),\n },\n actions: {\n add: translate('resources.resourceTypes.actions.add', 'Add resource type'),\n edit: translate('resources.resourceTypes.actions.edit', 'Edit'),\n delete: translate('resources.resourceTypes.actions.delete', 'Delete'),\n deleteConfirm: translate('resources.resourceTypes.actions.deleteConfirm', 'Delete resource type \"{{name}}\"?'),\n showResources: translate('resources.resourceTypes.actions.showResources', 'Show resources ({{count}})'),\n refresh: translate('resources.resourceTypes.actions.refresh', 'Refresh'),\n },\n form: {\n createTitle: translate('resources.resourceTypes.form.createTitle', 'Add resource type'),\n editTitle: translate('resources.resourceTypes.form.editTitle', 'Edit resource type'),\n name: translate('resources.resourceTypes.form.name', 'Name'),\n description: translate('resources.resourceTypes.form.description', 'Description'),\n save: translate('resources.resourceTypes.form.save', 'Save'),\n cancel: translate('resources.resourceTypes.form.cancel', 'Cancel'),\n },\n messages: {\n saved: translate('resources.resourceTypes.messages.saved', 'Resource type saved.'),\n deleted: translate('resources.resourceTypes.messages.deleted', 'Resource type deleted.'),\n },\n errors: {\n load: translate('resources.resourceTypes.errors.load', 'Failed to load resource types.'),\n save: translate('resources.resourceTypes.errors.save', 'Failed to save resource type.'),\n delete: translate('resources.resourceTypes.errors.delete', 'Failed to delete resource type.'),\n deleteAssigned: translate('resources.resourceTypes.errors.deleteAssigned', 'Resource type has assigned resources.'),\n },\n }), [translate])\n\n const columns = React.useMemo<ColumnDef<ResourceTypeRow>[]>(() => [\n {\n accessorKey: 'name',\n header: translations.table.name,\n meta: { priority: 1, sticky: true },\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{row.original.name}</span>\n {row.original.description ? (\n <span className={SUBTEXT_CLASSNAME}>\n {markdownToPlainText(row.original.description)}\n </span>\n ) : null}\n </div>\n ),\n },\n {\n accessorKey: 'appearance',\n header: translations.table.appearance,\n meta: { priority: 2 },\n cell: ({ row }) => {\n const icon = row.original.appearanceIcon\n const color = row.original.appearanceColor\n if (!icon && !color) {\n return <span className=\"text-xs text-muted-foreground\">\u2014</span>\n }\n return (\n <div className=\"flex items-center gap-2\">\n {color ? renderDictionaryColor(color) : null}\n {icon ? renderDictionaryIcon(icon) : null}\n </div>\n )\n },\n },\n {\n accessorKey: 'resourceCount',\n header: translations.table.resources,\n meta: { priority: 3 },\n cell: ({ row }) => (\n <Link\n className=\"inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground\"\n href={`/backend/resources/resources?resourceTypeId=${encodeURIComponent(row.original.id)}`}\n onClick={(event) => event.stopPropagation()}\n >\n <Package className=\"h-4 w-4\" aria-hidden />\n {translations.actions.showResources.replace('{{count}}', String(row.original.resourceCount))}\n </Link>\n ),\n },\n {\n accessorKey: 'description',\n header: translations.table.description,\n meta: { priority: 5 },\n cell: ({ row }) => row.original.description ? (\n <span className={DESCRIPTION_CLASSNAME}>\n {markdownToPlainText(row.original.description)}\n </span>\n ) : (\n <span className=\"text-xs text-muted-foreground\">\u2014</span>\n ),\n },\n {\n accessorKey: 'updatedAt',\n header: translations.table.updatedAt,\n meta: { priority: 4 },\n cell: ({ row }) => row.original.updatedAt\n ? <span className=\"text-xs text-muted-foreground\">{formatDateTime(row.original.updatedAt)}</span>\n : <span className=\"text-xs text-muted-foreground\">\u2014</span>,\n },\n ], [\n translations.actions.showResources,\n translations.table.appearance,\n translations.table.description,\n translations.table.name,\n translations.table.resources,\n translations.table.updatedAt,\n ])\n\n const loadResourceTypes = React.useCallback(async () => {\n setIsLoading(true)\n try {\n const params = new URLSearchParams({\n page: String(page),\n pageSize: String(PAGE_SIZE),\n withResourceCounts: 'true',\n })\n const sort = sorting[0]\n if (sort?.id) {\n params.set('sortField', sort.id)\n params.set('sortDir', sort.desc ? 'desc' : 'asc')\n }\n if (search.trim()) {\n params.set('search', search.trim())\n }\n const payload = await readApiResultOrThrow<ResourceTypesResponse>(\n `/api/resources/resource-types?${params.toString()}`,\n undefined,\n { errorMessage: translations.errors.load, fallback: { items: [], total: 0, totalPages: 1 } },\n )\n const items = Array.isArray(payload.items) ? payload.items : []\n setRows(items.map(mapApiResourceType))\n setTotal(typeof payload.total === 'number' ? payload.total : items.length)\n setTotalPages(typeof payload.totalPages === 'number' ? payload.totalPages : Math.max(1, Math.ceil(items.length / PAGE_SIZE)))\n } catch (error) {\n logger.error('Failed to list resource types', { err: error })\n flash(translations.errors.load, 'error')\n } finally {\n setIsLoading(false)\n }\n }, [page, search, sorting, translations.errors.load])\n\n React.useEffect(() => {\n void loadResourceTypes()\n }, [loadResourceTypes, scopeVersion, reloadToken])\n\n const handleSearchChange = React.useCallback((value: string) => {\n setSearch(value)\n setPage(1)\n }, [])\n\n const handleRefresh = React.useCallback(() => {\n setReloadToken((token) => token + 1)\n }, [])\n\n const handleDelete = React.useCallback(async (entry: ResourceTypeRow) => {\n if (entry.resourceCount > 0) {\n flash(translations.errors.deleteAssigned, 'error')\n return\n }\n const message = translations.actions.deleteConfirm.replace('{{name}}', entry.name)\n const confirmed = await confirm({\n title: message,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const headers = buildOptimisticLockHeader(entry.updatedAt)\n await runResourceTypeMutation(\n () => withScopedApiRequestHeaders(headers, () => (\n deleteCrud('resources/resource-types', entry.id, { errorMessage: translations.errors.delete })\n )),\n { operation: 'deleteResourceType', id: entry.id, updatedAt: entry.updatedAt ?? null },\n entry.id,\n )\n flash(translations.messages.deleted, 'success')\n handleRefresh()\n } catch (error) {\n logger.error('Failed to delete resource type', { err: error })\n flash(translations.errors.delete, 'error')\n }\n }, [confirm, handleRefresh, runResourceTypeMutation, translations.actions.deleteConfirm, translations.errors.delete, translations.errors.deleteAssigned, translations.messages.deleted])\n\n return (\n <Page>\n <PageBody>\n <DataTable<ResourceTypeRow>\n title={translations.title}\n data={rows}\n columns={columns}\n isLoading={isLoading}\n searchValue={search}\n onSearchChange={handleSearchChange}\n searchPlaceholder={translations.table.search}\n emptyState={<p className=\"py-8 text-center text-sm text-muted-foreground\">{translations.table.empty}</p>}\n actions={(\n <Button asChild size=\"sm\">\n <Link href=\"/backend/resources/resource-types/create\">\n {translations.actions.add}\n </Link>\n </Button>\n )}\n refreshButton={{\n label: translations.actions.refresh,\n onRefresh: handleRefresh,\n isRefreshing: isLoading,\n }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n pagination={{ page, pageSize: PAGE_SIZE, total, totalPages, onPageChange: setPage }}\n rowActions={(row) => (\n <RowActions\n items={[\n { id: 'edit', label: translations.actions.edit, href: `/backend/resources/resource-types/${row.id}/edit` },\n ...(row.resourceCount > 0\n ? []\n : [{ id: 'delete', label: translations.actions.delete, destructive: true, onSelect: () => handleDelete(row) }]),\n ]}\n />\n )}\n onRowClick={(row) => router.push(`/backend/resources/resource-types/${row.id}/edit`)}\n perspective={{ tableId: extensionPoints.hosts.resourceTypesTable.tableId }}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n\nfunction mapApiResourceType(item: Record<string, unknown>): ResourceTypeRow {\n const id = typeof item.id === 'string' ? item.id : ''\n const name = typeof item.name === 'string' && item.name.length ? item.name : id\n const description = typeof item.description === 'string' && item.description.length\n ? item.description\n : typeof item.description === 'string'\n ? item.description\n : null\n const appearanceIcon = typeof item.appearanceIcon === 'string'\n ? item.appearanceIcon\n : typeof item.appearance_icon === 'string'\n ? item.appearance_icon\n : null\n const appearanceColor = typeof item.appearanceColor === 'string'\n ? item.appearanceColor\n : typeof item.appearance_color === 'string'\n ? item.appearance_color\n : null\n const updatedAt = typeof item.updatedAt === 'string'\n ? item.updatedAt\n : typeof item.updated_at === 'string'\n ? item.updated_at\n : null\n const resourceCount = typeof item.resourceCount === 'number'\n ? item.resourceCount\n : typeof item.resource_count === 'number'\n ? item.resource_count\n : 0\n return withDataTableNamespaces({ id, name, description, appearanceIcon, appearanceColor, updatedAt, resourceCount }, item)\n}\n"],
5
- "mappings": ";AAwIQ,SACE,KADF;AAtIR,YAAY,WAAW;AACvB,SAAS,uBAAuB;AAChC,SAAS,iBAAiB;AAC1B,OAAO,UAAU;AAEjB,SAAS,MAAM,gBAAgB;AAC/B,SAAS,2BAA2B;AACpC,SAAS,WAAW,+BAA+B;AACnD,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B,SAAS,aAAa;AACtB,SAAS,sBAAsB,mCAAmC;AAClE,SAAS,kBAAkB;AAC3B,SAAS,iCAAiC;AAC1C,SAAS,0BAA0B;AACnC,SAAS,uBAAuB,4BAA4B;AAC5D,SAAS,wBAAwB;AACjC,SAAS,eAAe;AACxB,SAAS,mCAAmC;AAC5C,SAAS,YAAY;AACrB,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,WAAW,EAAE,MAAM,EAAE,WAAW,sBAAsB,CAAC;AAEnF,MAAM,YAAY;AAClB,MAAM,wBAAwB;AAC9B,MAAM,oBAAoB;AAC1B,MAAM,qCAAqC;AAyB5B,SAAR,6BAA8C;AACnD,QAAM,YAAY,KAAK;AACvB,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAC3D,QAAM,SAAS,UAAU;AACzB,QAAM,eAAe,4BAA4B;AACjD,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAA4B,CAAC,CAAC;AAC5D,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,CAAC;AACxC,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,CAAC;AAC1C,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,CAAC;AACpD,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAuB,CAAC,EAAE,IAAI,QAAQ,MAAM,MAAM,CAAC,CAAC;AACxF,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,IAAI;AACrD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,CAAC;AACtD,QAAM,EAAE,aAAa,kBAAkB,IAAI,mBAAiD;AAAA,IAC1F,WAAW;AAAA,IACX,gBAAgB,UAAU,8BAA8B,4BAA4B;AAAA,EACtF,CAAC;AACD,QAAM,0BAA0B,MAAM;AAAA,IACpC,OACE,WACA,iBACA,eACe,YAAY;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,SAAS;AAAA,QACP,QAAQ;AAAA,QACR,cAAc;AAAA,QACd;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAC;AAAA,IACD,CAAC,mBAAmB,WAAW;AAAA,EACjC;AAEA,QAAM,eAAe,MAAM,QAAQ,OAAO;AAAA,IACxC,OAAO,UAAU,sCAAsC,gBAAgB;AAAA,IACvE,aAAa,UAAU,4CAA4C,wCAAwC;AAAA,IAC3G,OAAO;AAAA,MACL,MAAM,UAAU,sCAAsC,MAAM;AAAA,MAC5D,aAAa,UAAU,6CAA6C,aAAa;AAAA,MACjF,YAAY,UAAU,4CAA4C,YAAY;AAAA,MAC9E,WAAW,UAAU,2CAA2C,WAAW;AAAA,MAC3E,WAAW,UAAU,2CAA2C,SAAS;AAAA,MACzE,OAAO,UAAU,uCAAuC,wBAAwB;AAAA,MAChF,QAAQ,UAAU,wCAAwC,6BAAwB;AAAA,IACpF;AAAA,IACA,SAAS;AAAA,MACP,KAAK,UAAU,uCAAuC,mBAAmB;AAAA,MACzE,MAAM,UAAU,wCAAwC,MAAM;AAAA,MAC9D,QAAQ,UAAU,0CAA0C,QAAQ;AAAA,MACpE,eAAe,UAAU,iDAAiD,kCAAkC;AAAA,MAC5G,eAAe,UAAU,iDAAiD,4BAA4B;AAAA,MACtG,SAAS,UAAU,2CAA2C,SAAS;AAAA,IACzE;AAAA,IACA,MAAM;AAAA,MACJ,aAAa,UAAU,4CAA4C,mBAAmB;AAAA,MACtF,WAAW,UAAU,0CAA0C,oBAAoB;AAAA,MACnF,MAAM,UAAU,qCAAqC,MAAM;AAAA,MAC3D,aAAa,UAAU,4CAA4C,aAAa;AAAA,MAChF,MAAM,UAAU,qCAAqC,MAAM;AAAA,MAC3D,QAAQ,UAAU,uCAAuC,QAAQ;AAAA,IACnE;AAAA,IACA,UAAU;AAAA,MACR,OAAO,UAAU,0CAA0C,sBAAsB;AAAA,MACjF,SAAS,UAAU,4CAA4C,wBAAwB;AAAA,IACzF;AAAA,IACA,QAAQ;AAAA,MACN,MAAM,UAAU,uCAAuC,gCAAgC;AAAA,MACvF,MAAM,UAAU,uCAAuC,+BAA+B;AAAA,MACtF,QAAQ,UAAU,yCAAyC,iCAAiC;AAAA,MAC5F,gBAAgB,UAAU,iDAAiD,uCAAuC;AAAA,IACpH;AAAA,EACF,IAAI,CAAC,SAAS,CAAC;AAEf,QAAM,UAAU,MAAM,QAAsC,MAAM;AAAA,IAChE;AAAA,MACE,aAAa;AAAA,MACb,QAAQ,aAAa,MAAM;AAAA,MAC3B,MAAM,EAAE,UAAU,GAAG,QAAQ,KAAK;AAAA,MAClC,MAAM,CAAC,EAAE,IAAI,MACX,qBAAC,SAAI,WAAU,iBACb;AAAA,4BAAC,UAAK,WAAU,eAAe,cAAI,SAAS,MAAK;AAAA,QAChD,IAAI,SAAS,cACZ,oBAAC,UAAK,WAAW,mBACd,8BAAoB,IAAI,SAAS,WAAW,GAC/C,IACE;AAAA,SACN;AAAA,IAEJ;AAAA,IACA;AAAA,MACE,aAAa;AAAA,MACb,QAAQ,aAAa,MAAM;AAAA,MAC3B,MAAM,EAAE,UAAU,EAAE;AAAA,MACpB,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,cAAM,OAAO,IAAI,SAAS;AAC1B,cAAM,QAAQ,IAAI,SAAS;AAC3B,YAAI,CAAC,QAAQ,CAAC,OAAO;AACnB,iBAAO,oBAAC,UAAK,WAAU,iCAAgC,oBAAC;AAAA,QAC1D;AACA,eACE,qBAAC,SAAI,WAAU,2BACZ;AAAA,kBAAQ,sBAAsB,KAAK,IAAI;AAAA,UACvC,OAAO,qBAAqB,IAAI,IAAI;AAAA,WACvC;AAAA,MAEJ;AAAA,IACF;AAAA,IACA;AAAA,MACE,aAAa;AAAA,MACb,QAAQ,aAAa,MAAM;AAAA,MAC3B,MAAM,EAAE,UAAU,EAAE;AAAA,MACpB,MAAM,CAAC,EAAE,IAAI,MACX;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,MAAM,+CAA+C,mBAAmB,IAAI,SAAS,EAAE,CAAC;AAAA,UACxF,SAAS,CAAC,UAAU,MAAM,gBAAgB;AAAA,UAE1C;AAAA,gCAAC,WAAQ,WAAU,WAAU,eAAW,MAAC;AAAA,YACxC,aAAa,QAAQ,cAAc,QAAQ,aAAa,OAAO,IAAI,SAAS,aAAa,CAAC;AAAA;AAAA;AAAA,MAC7F;AAAA,IAEJ;AAAA,IACA;AAAA,MACE,aAAa;AAAA,MACb,QAAQ,aAAa,MAAM;AAAA,MAC3B,MAAM,EAAE,UAAU,EAAE;AAAA,MACpB,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,SAAS,cAC9B,oBAAC,UAAK,WAAW,uBACd,8BAAoB,IAAI,SAAS,WAAW,GAC/C,IAEA,oBAAC,UAAK,WAAU,iCAAgC,oBAAC;AAAA,IAErD;AAAA,IACA;AAAA,MACE,aAAa;AAAA,MACb,QAAQ,aAAa,MAAM;AAAA,MAC3B,MAAM,EAAE,UAAU,EAAE;AAAA,MACpB,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,SAAS,YAC5B,oBAAC,UAAK,WAAU,iCAAiC,yBAAe,IAAI,SAAS,SAAS,GAAE,IACxF,oBAAC,UAAK,WAAU,iCAAgC,oBAAC;AAAA,IACvD;AAAA,EACF,GAAG;AAAA,IACD,aAAa,QAAQ;AAAA,IACrB,aAAa,MAAM;AAAA,IACnB,aAAa,MAAM;AAAA,IACnB,aAAa,MAAM;AAAA,IACnB,aAAa,MAAM;AAAA,IACnB,aAAa,MAAM;AAAA,EACrB,CAAC;AAED,QAAM,oBAAoB,MAAM,YAAY,YAAY;AACtD,iBAAa,IAAI;AACjB,QAAI;AACF,YAAM,SAAS,IAAI,gBAAgB;AAAA,QACjC,MAAM,OAAO,IAAI;AAAA,QACjB,UAAU,OAAO,SAAS;AAAA,QAC1B,oBAAoB;AAAA,MACtB,CAAC;AACD,YAAM,OAAO,QAAQ,CAAC;AACtB,UAAI,MAAM,IAAI;AACZ,eAAO,IAAI,aAAa,KAAK,EAAE;AAC/B,eAAO,IAAI,WAAW,KAAK,OAAO,SAAS,KAAK;AAAA,MAClD;AACA,UAAI,OAAO,KAAK,GAAG;AACjB,eAAO,IAAI,UAAU,OAAO,KAAK,CAAC;AAAA,MACpC;AACA,YAAM,UAAU,MAAM;AAAA,QACpB,iCAAiC,OAAO,SAAS,CAAC;AAAA,QAClD;AAAA,QACA,EAAE,cAAc,aAAa,OAAO,MAAM,UAAU,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,YAAY,EAAE,EAAE;AAAA,MAC7F;AACA,YAAM,QAAQ,MAAM,QAAQ,QAAQ,KAAK,IAAI,QAAQ,QAAQ,CAAC;AAC9D,cAAQ,MAAM,IAAI,kBAAkB,CAAC;AACrC,eAAS,OAAO,QAAQ,UAAU,WAAW,QAAQ,QAAQ,MAAM,MAAM;AACzE,oBAAc,OAAO,QAAQ,eAAe,WAAW,QAAQ,aAAa,KAAK,IAAI,GAAG,KAAK,KAAK,MAAM,SAAS,SAAS,CAAC,CAAC;AAAA,IAC9H,SAAS,OAAO;AACd,aAAO,MAAM,iCAAiC,EAAE,KAAK,MAAM,CAAC;AAC5D,YAAM,aAAa,OAAO,MAAM,OAAO;AAAA,IACzC,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,MAAM,QAAQ,SAAS,aAAa,OAAO,IAAI,CAAC;AAEpD,QAAM,UAAU,MAAM;AACpB,SAAK,kBAAkB;AAAA,EACzB,GAAG,CAAC,mBAAmB,cAAc,WAAW,CAAC;AAEjD,QAAM,qBAAqB,MAAM,YAAY,CAAC,UAAkB;AAC9D,cAAU,KAAK;AACf,YAAQ,CAAC;AAAA,EACX,GAAG,CAAC,CAAC;AAEL,QAAM,gBAAgB,MAAM,YAAY,MAAM;AAC5C,mBAAe,CAAC,UAAU,QAAQ,CAAC;AAAA,EACrC,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,MAAM,YAAY,OAAO,UAA2B;AACvE,QAAI,MAAM,gBAAgB,GAAG;AAC3B,YAAM,aAAa,OAAO,gBAAgB,OAAO;AACjD;AAAA,IACF;AACA,UAAM,UAAU,aAAa,QAAQ,cAAc,QAAQ,YAAY,MAAM,IAAI;AACjF,UAAM,YAAY,MAAM,QAAQ;AAAA,MAC9B,OAAO;AAAA,MACP,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,UAAW;AAChB,QAAI;AACF,YAAM,UAAU,0BAA0B,MAAM,SAAS;AACzD,YAAM;AAAA,QACJ,MAAM,4BAA4B,SAAS,MACzC,WAAW,4BAA4B,MAAM,IAAI,EAAE,cAAc,aAAa,OAAO,OAAO,CAAC,CAC9F;AAAA,QACD,EAAE,WAAW,sBAAsB,IAAI,MAAM,IAAI,WAAW,MAAM,aAAa,KAAK;AAAA,QACpF,MAAM;AAAA,MACR;AACA,YAAM,aAAa,SAAS,SAAS,SAAS;AAC9C,oBAAc;AAAA,IAChB,SAAS,OAAO;AACd,aAAO,MAAM,kCAAkC,EAAE,KAAK,MAAM,CAAC;AAC7D,YAAM,aAAa,OAAO,QAAQ,OAAO;AAAA,IAC3C;AAAA,EACF,GAAG,CAAC,SAAS,eAAe,yBAAyB,aAAa,QAAQ,eAAe,aAAa,OAAO,QAAQ,aAAa,OAAO,gBAAgB,aAAa,SAAS,OAAO,CAAC;AAEvL,SACE,qBAAC,QACC;AAAA,wBAAC,YACC;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,aAAa;AAAA,QACpB,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,aAAa;AAAA,QACb,gBAAgB;AAAA,QAChB,mBAAmB,aAAa,MAAM;AAAA,QACtC,YAAY,oBAAC,OAAE,WAAU,kDAAkD,uBAAa,MAAM,OAAM;AAAA,QACpG,SACE,oBAAC,UAAO,SAAO,MAAC,MAAK,MACnB,8BAAC,QAAK,MAAK,4CACR,uBAAa,QAAQ,KACxB,GACF;AAAA,QAEF,eAAe;AAAA,UACb,OAAO,aAAa,QAAQ;AAAA,UAC5B,WAAW;AAAA,UACX,cAAc;AAAA,QAChB;AAAA,QACA,UAAQ;AAAA,QACR;AAAA,QACA,iBAAiB;AAAA,QACjB,YAAY,EAAE,MAAM,UAAU,WAAW,OAAO,YAAY,cAAc,QAAQ;AAAA,QAClF,YAAY,CAAC,QACX;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,EAAE,IAAI,QAAQ,OAAO,aAAa,QAAQ,MAAM,MAAM,qCAAqC,IAAI,EAAE,QAAQ;AAAA,cACzG,GAAI,IAAI,gBAAgB,IACpB,CAAC,IACD,CAAC,EAAE,IAAI,UAAU,OAAO,aAAa,QAAQ,QAAQ,aAAa,MAAM,UAAU,MAAM,aAAa,GAAG,EAAE,CAAC;AAAA,YACjH;AAAA;AAAA,QACF;AAAA,QAEF,YAAY,CAAC,QAAQ,OAAO,KAAK,qCAAqC,IAAI,EAAE,OAAO;AAAA,QACnF,aAAa,EAAE,SAAS,gBAAgB,MAAM,mBAAmB,QAAQ;AAAA;AAAA,IAC3E,GACF;AAAA,IACC;AAAA,KACH;AAEJ;AAEA,SAAS,mBAAmB,MAAgD;AAC1E,QAAM,KAAK,OAAO,KAAK,OAAO,WAAW,KAAK,KAAK;AACnD,QAAM,OAAO,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,KAAK,OAAO;AAC7E,QAAM,cAAc,OAAO,KAAK,gBAAgB,YAAY,KAAK,YAAY,SACzE,KAAK,cACL,OAAO,KAAK,gBAAgB,WAC1B,KAAK,cACL;AACN,QAAM,iBAAiB,OAAO,KAAK,mBAAmB,WAClD,KAAK,iBACL,OAAO,KAAK,oBAAoB,WAC9B,KAAK,kBACL;AACN,QAAM,kBAAkB,OAAO,KAAK,oBAAoB,WACpD,KAAK,kBACL,OAAO,KAAK,qBAAqB,WAC/B,KAAK,mBACL;AACN,QAAM,YAAY,OAAO,KAAK,cAAc,WACxC,KAAK,YACL,OAAO,KAAK,eAAe,WACzB,KAAK,aACL;AACN,QAAM,gBAAgB,OAAO,KAAK,kBAAkB,WAChD,KAAK,gBACL,OAAO,KAAK,mBAAmB,WAC7B,KAAK,iBACL;AACN,SAAO,wBAAwB,EAAE,IAAI,MAAM,aAAa,gBAAgB,iBAAiB,WAAW,cAAc,GAAG,IAAI;AAC3H;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/resources/extension-points'\nimport { useRouter } from 'next/navigation'\nimport Link from 'next/link'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { markdownToPlainText } from '@open-mercato/ui/backend/markdown/markdownToPlainText'\nimport { DataTable, withDataTableNamespaces } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { renderDictionaryColor, renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { Package } from 'lucide-react'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { formatDateTime } from '@open-mercato/shared/lib/time'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('resources').child({ component: 'resource-types-page' })\n\nconst PAGE_SIZE = 50\nconst DESCRIPTION_CLASSNAME = 'line-clamp-3 whitespace-pre-line text-sm text-foreground'\nconst SUBTEXT_CLASSNAME = 'line-clamp-2 text-xs text-muted-foreground'\nconst RESOURCE_TYPES_MUTATION_CONTEXT_ID = 'resources.resource-types.list'\n\ntype ResourceTypeRow = {\n id: string\n name: string\n description: string | null\n appearanceIcon: string | null\n appearanceColor: string | null\n updatedAt: string | null\n resourceCount: number\n}\n\ntype ResourceTypesResponse = {\n items?: Array<Record<string, unknown>>\n total?: number\n totalPages?: number\n}\n\ntype ResourceTypesMutationContext = {\n formId: string\n resourceKind: string\n resourceId?: string\n retryLastMutation: () => Promise<boolean>\n}\n\nexport default function ResourcesResourceTypesPage() {\n const translate = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const router = useRouter()\n const scopeVersion = useOrganizationScopeVersion()\n const [rows, setRows] = React.useState<ResourceTypeRow[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'name', desc: false }])\n const [search, setSearch] = React.useState('')\n const [isLoading, setIsLoading] = React.useState(true)\n const [reloadToken, setReloadToken] = React.useState(0)\n const { runMutation, retryLastMutation } = useGuardedMutation<ResourceTypesMutationContext>({\n contextId: RESOURCE_TYPES_MUTATION_CONTEXT_ID,\n blockedMessage: translate('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const runResourceTypeMutation = React.useCallback(\n async <T,>(\n operation: () => Promise<T>,\n mutationPayload: Record<string, unknown>,\n resourceId?: string,\n ): Promise<T> => runMutation({\n operation,\n mutationPayload,\n context: {\n formId: RESOURCE_TYPES_MUTATION_CONTEXT_ID,\n resourceKind: 'resources.resourceType',\n resourceId,\n retryLastMutation,\n },\n }),\n [retryLastMutation, runMutation],\n )\n\n const translations = React.useMemo(() => ({\n title: translate('resources.resourceTypes.page.title', 'Resource types'),\n description: translate('resources.resourceTypes.page.description', 'Organize shared resources by category.'),\n table: {\n name: translate('resources.resourceTypes.table.name', 'Name'),\n description: translate('resources.resourceTypes.table.description', 'Description'),\n appearance: translate('resources.resourceTypes.table.appearance', 'Appearance'),\n resources: translate('resources.resourceTypes.table.resources', 'Resources'),\n updatedAt: translate('resources.resourceTypes.table.updatedAt', 'Updated'),\n empty: translate('resources.resourceTypes.table.empty', 'No resource types yet.'),\n search: translate('resources.resourceTypes.table.search', 'Search resource types\u2026'),\n },\n actions: {\n add: translate('resources.resourceTypes.actions.add', 'Add resource type'),\n edit: translate('resources.resourceTypes.actions.edit', 'Edit'),\n delete: translate('resources.resourceTypes.actions.delete', 'Delete'),\n deleteConfirm: translate('resources.resourceTypes.actions.deleteConfirm', 'Delete resource type \"{{name}}\"?'),\n showResources: translate('resources.resourceTypes.actions.showResources', 'Show resources ({{count}})'),\n refresh: translate('resources.resourceTypes.actions.refresh', 'Refresh'),\n },\n form: {\n createTitle: translate('resources.resourceTypes.form.createTitle', 'Add resource type'),\n editTitle: translate('resources.resourceTypes.form.editTitle', 'Edit resource type'),\n name: translate('resources.resourceTypes.form.name', 'Name'),\n description: translate('resources.resourceTypes.form.description', 'Description'),\n save: translate('resources.resourceTypes.form.save', 'Save'),\n cancel: translate('resources.resourceTypes.form.cancel', 'Cancel'),\n },\n messages: {\n saved: translate('resources.resourceTypes.messages.saved', 'Resource type saved.'),\n deleted: translate('resources.resourceTypes.messages.deleted', 'Resource type deleted.'),\n },\n errors: {\n load: translate('resources.resourceTypes.errors.load', 'Failed to load resource types.'),\n save: translate('resources.resourceTypes.errors.save', 'Failed to save resource type.'),\n delete: translate('resources.resourceTypes.errors.delete', 'Failed to delete resource type.'),\n deleteAssigned: translate('resources.resourceTypes.errors.deleteAssigned', 'Resource type has assigned resources.'),\n },\n }), [translate])\n\n const columns = React.useMemo<ColumnDef<ResourceTypeRow>[]>(() => [\n {\n accessorKey: 'name',\n header: translations.table.name,\n meta: { priority: 1, sticky: true },\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{row.original.name}</span>\n {row.original.description ? (\n <span className={SUBTEXT_CLASSNAME}>\n {markdownToPlainText(row.original.description)}\n </span>\n ) : null}\n </div>\n ),\n },\n {\n accessorKey: 'appearance',\n header: translations.table.appearance,\n meta: { priority: 2 },\n cell: ({ row }) => {\n const icon = row.original.appearanceIcon\n const color = row.original.appearanceColor\n if (!icon && !color) {\n return <span className=\"text-xs text-muted-foreground\">\u2014</span>\n }\n return (\n <div className=\"flex items-center gap-2\">\n {color ? renderDictionaryColor(color) : null}\n {icon ? renderDictionaryIcon(icon) : null}\n </div>\n )\n },\n },\n {\n accessorKey: 'resourceCount',\n header: translations.table.resources,\n meta: { priority: 3 },\n cell: ({ row }) => (\n <Link\n className=\"inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground\"\n href={`/backend/resources/resources?resourceTypeId=${encodeURIComponent(row.original.id)}`}\n onClick={(event) => event.stopPropagation()}\n >\n <Package className=\"h-4 w-4\" aria-hidden />\n {translations.actions.showResources.replace('{{count}}', String(row.original.resourceCount))}\n </Link>\n ),\n },\n {\n accessorKey: 'description',\n header: translations.table.description,\n meta: { priority: 5 },\n cell: ({ row }) => row.original.description ? (\n <span className={DESCRIPTION_CLASSNAME}>\n {markdownToPlainText(row.original.description)}\n </span>\n ) : (\n <span className=\"text-xs text-muted-foreground\">\u2014</span>\n ),\n },\n {\n accessorKey: 'updatedAt',\n header: translations.table.updatedAt,\n meta: { priority: 4 },\n cell: ({ row }) => row.original.updatedAt\n ? <span className=\"text-xs text-muted-foreground\">{formatDateTime(row.original.updatedAt)}</span>\n : <span className=\"text-xs text-muted-foreground\">\u2014</span>,\n },\n ], [\n translations.actions.showResources,\n translations.table.appearance,\n translations.table.description,\n translations.table.name,\n translations.table.resources,\n translations.table.updatedAt,\n ])\n\n const loadResourceTypes = React.useCallback(async () => {\n setIsLoading(true)\n try {\n const params = new URLSearchParams({\n page: String(page),\n pageSize: String(PAGE_SIZE),\n withResourceCounts: 'true',\n })\n const sort = sorting[0]\n if (sort?.id) {\n params.set('sortField', sort.id)\n params.set('sortDir', sort.desc ? 'desc' : 'asc')\n }\n if (search.trim()) {\n params.set('search', search.trim())\n }\n const payload = await readApiResultOrThrow<ResourceTypesResponse>(\n `/api/resources/resource-types?${params.toString()}`,\n undefined,\n { errorMessage: translations.errors.load, fallback: { items: [], total: 0, totalPages: 1 } },\n )\n const items = Array.isArray(payload.items) ? payload.items : []\n setRows(items.map(mapApiResourceType))\n setTotal(typeof payload.total === 'number' ? payload.total : items.length)\n setTotalPages(typeof payload.totalPages === 'number' ? payload.totalPages : Math.max(1, Math.ceil(items.length / PAGE_SIZE)))\n } catch (error) {\n logger.error('Failed to list resource types', { err: error })\n flash(translations.errors.load, 'error')\n } finally {\n setIsLoading(false)\n }\n }, [page, search, sorting, translations.errors.load])\n\n React.useEffect(() => {\n void loadResourceTypes()\n }, [loadResourceTypes, scopeVersion, reloadToken])\n\n const handleSearchChange = React.useCallback((value: string) => {\n setSearch(value)\n setPage(1)\n }, [])\n\n const handleRefresh = React.useCallback(() => {\n setReloadToken((token) => token + 1)\n }, [])\n\n const handleDelete = React.useCallback(async (entry: ResourceTypeRow) => {\n if (entry.resourceCount > 0) {\n flash(translations.errors.deleteAssigned, 'error')\n return\n }\n const message = translations.actions.deleteConfirm.replace('{{name}}', entry.name)\n const confirmed = await confirm({\n title: message,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const headers = buildOptimisticLockHeader(entry.updatedAt)\n await runResourceTypeMutation(\n () => withScopedApiRequestHeaders(headers, () => (\n deleteCrud('resources/resource-types', entry.id, { errorMessage: translations.errors.delete })\n )),\n { operation: 'deleteResourceType', id: entry.id, updatedAt: entry.updatedAt ?? null },\n entry.id,\n )\n flash(translations.messages.deleted, 'success')\n handleRefresh()\n } catch (error) {\n logger.error('Failed to delete resource type', { err: error })\n flash(translations.errors.delete, 'error')\n }\n }, [confirm, handleRefresh, runResourceTypeMutation, translations.actions.deleteConfirm, translations.errors.delete, translations.errors.deleteAssigned, translations.messages.deleted])\n\n return (\n <Page>\n <PageBody>\n <DataTable<ResourceTypeRow>\n title={translations.title}\n data={rows}\n columns={columns}\n isLoading={isLoading}\n searchValue={search}\n onSearchChange={handleSearchChange}\n searchPlaceholder={translations.table.search}\n emptyState={<p className=\"py-8 text-center text-sm text-muted-foreground\">{translations.table.empty}</p>}\n actions={(\n <Button asChild size=\"sm\">\n <Link href=\"/backend/resources/resource-types/create\">\n {translations.actions.add}\n </Link>\n </Button>\n )}\n refreshButton={{\n label: translations.actions.refresh,\n onRefresh: handleRefresh,\n isRefreshing: isLoading,\n }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n pagination={{ page, pageSize: PAGE_SIZE, total, totalPages, onPageChange: setPage }}\n rowActions={(row) => (\n <RowActions\n items={[\n { id: 'edit', label: translations.actions.edit, href: `/backend/resources/resource-types/${row.id}/edit` },\n ...(row.resourceCount > 0\n ? []\n : [{ id: 'delete', label: translations.actions.delete, destructive: true, onSelect: () => handleDelete(row) }]),\n ]}\n />\n )}\n onRowClick={(row) => router.push(`/backend/resources/resource-types/${row.id}/edit`)}\n perspective={{ tableId: extensionPoints.hosts.resourceTypesTable.tableId }}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n\nfunction mapApiResourceType(item: Record<string, unknown>): ResourceTypeRow {\n const id = typeof item.id === 'string' ? item.id : ''\n const name = typeof item.name === 'string' && item.name.length ? item.name : id\n const description = typeof item.description === 'string' && item.description.length\n ? item.description\n : typeof item.description === 'string'\n ? item.description\n : null\n const appearanceIcon = typeof item.appearanceIcon === 'string'\n ? item.appearanceIcon\n : typeof item.appearance_icon === 'string'\n ? item.appearance_icon\n : null\n const appearanceColor = typeof item.appearanceColor === 'string'\n ? item.appearanceColor\n : typeof item.appearance_color === 'string'\n ? item.appearance_color\n : null\n const updatedAt = typeof item.updatedAt === 'string'\n ? item.updatedAt\n : typeof item.updated_at === 'string'\n ? item.updated_at\n : null\n const resourceCount = typeof item.resourceCount === 'number'\n ? item.resourceCount\n : typeof item.resource_count === 'number'\n ? item.resource_count\n : 0\n return withDataTableNamespaces({ id, name, description, appearanceIcon, appearanceColor, updatedAt, resourceCount }, item)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/resources/backend/resources/resources/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/resources/extension-points'\nimport Link from 'next/link'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable, withDataTableNamespaces } from '@open-mercato/ui/backend/DataTable'\nimport { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { BooleanIcon } from '@open-mercato/ui/backend/ValueIcons'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport type { FilterDef, FilterOption, FilterValues } from '@open-mercato/ui/backend/FilterOverlay'\nimport type { TagOption } from '@open-mercato/ui/backend/detail'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { renderDictionaryColor, renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { Pencil } from 'lucide-react'\n\nconst PAGE_SIZE = 20\nconst RESOURCE_LIST_MUTATION_CONTEXT_ID = 'resources.resources.list'\n\ntype ResourceRow = {\n id: string\n name: string\n resourceTypeId: string | null\n capacity: number | null\n tags?: TagOption[] | null\n isActive: boolean\n appearanceIcon?: string | null\n appearanceColor?: string | null\n updatedAt?: string | null\n}\n\ntype ResourceTypeRow = {\n id: string\n name: string\n appearanceIcon: string | null\n appearanceColor: string | null\n}\n\ntype ResourceGroupRow = {\n id: string\n name: string\n resourceTypeId: string | null\n appearanceIcon: string | null\n appearanceColor: string | null\n rowKind: 'group'\n depth: number\n}\n\ntype ResourceTableRow = (ResourceRow & { rowKind: 'resource'; depth: number }) | ResourceGroupRow\n\ntype ResourcesResponse = {\n items: Array<Record<string, unknown>>\n total: number\n page: number\n totalPages: number\n}\n\ntype ResourceTypesResponse = {\n items: Array<Record<string, unknown>>\n}\n\ntype ResourceListMutationContext = {\n formId: string\n resourceKind: string\n resourceId?: string\n retryLastMutation: () => Promise<boolean>\n}\n\nexport default function ResourcesResourcesPage() {\n const [rows, setRows] = React.useState<ResourceRow[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [resourceTypes, setResourceTypes] = React.useState<Map<string, ResourceTypeRow>>(new Map())\n const [canManage, setCanManage] = React.useState(false)\n const [tagOptions, setTagOptions] = React.useState<FilterOption[]>([])\n const scopeVersion = useOrganizationScopeVersion()\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const router = useRouter()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const resourceTypeFilter = searchParams.get('resourceTypeId')\n const selectedResourceTypeId = typeof filterValues.resourceTypeId === 'string'\n ? filterValues.resourceTypeId\n : resourceTypeFilter\n const { runMutation, retryLastMutation } = useGuardedMutation<ResourceListMutationContext>({\n contextId: RESOURCE_LIST_MUTATION_CONTEXT_ID,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const runResourceMutation = React.useCallback(\n async <T,>(\n operation: () => Promise<T>,\n mutationPayload: Record<string, unknown>,\n resourceId?: string,\n ): Promise<T> => runMutation({\n operation,\n mutationPayload,\n context: {\n formId: RESOURCE_LIST_MUTATION_CONTEXT_ID,\n resourceKind: 'resources.resource',\n resourceId,\n retryLastMutation,\n },\n }),\n [retryLastMutation, runMutation],\n )\n\n React.useEffect(() => {\n setPage(1)\n }, [resourceTypeFilter])\n\n React.useEffect(() => {\n if (!resourceTypeFilter) return\n setFilterValues((prev) => {\n if (prev.resourceTypeId === resourceTypeFilter) return prev\n if (typeof prev.resourceTypeId === 'string' && prev.resourceTypeId.length > 0) return prev\n return { ...prev, resourceTypeId: resourceTypeFilter }\n })\n }, [resourceTypeFilter])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadPermissions() {\n try {\n const call = await apiCall<{ granted?: string[]; ok?: boolean }>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features: ['resources.manage_resources'] }),\n })\n if (!cancelled) {\n const granted = Array.isArray(call.result?.granted) ? call.result?.granted : []\n setCanManage(call.result?.ok === true || granted.includes('resources.manage_resources'))\n }\n } catch {\n if (!cancelled) setCanManage(false)\n }\n }\n loadPermissions()\n return () => { cancelled = true }\n }, [])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadResourceTypes() {\n try {\n const params = new URLSearchParams({ page: '1', pageSize: '100' })\n const call = await apiCall<ResourceTypesResponse>(`/api/resources/resource-types?${params.toString()}`)\n const items = Array.isArray(call.result?.items) ? call.result.items : []\n const map = new Map<string, ResourceTypeRow>()\n for (const item of items) {\n const raw = item as Record<string, unknown>\n const id = typeof raw.id === 'string' ? raw.id : ''\n const name = typeof raw.name === 'string' ? raw.name : id\n const appearanceIcon = typeof raw.appearanceIcon === 'string'\n ? raw.appearanceIcon\n : typeof raw.appearance_icon === 'string'\n ? raw.appearance_icon\n : null\n const appearanceColor = typeof raw.appearanceColor === 'string'\n ? raw.appearanceColor\n : typeof raw.appearance_color === 'string'\n ? raw.appearance_color\n : null\n map.set(id, {\n id,\n name,\n appearanceIcon,\n appearanceColor,\n })\n }\n if (!cancelled) setResourceTypes(map)\n } catch {\n if (!cancelled) setResourceTypes(new Map())\n }\n }\n loadResourceTypes()\n return () => { cancelled = true }\n }, [scopeVersion])\n\n const loadTagOptions = React.useCallback(\n async (query?: string): Promise<FilterOption[]> => {\n try {\n const params = new URLSearchParams({ pageSize: '100' })\n if (query && query.trim().length) params.set('search', query.trim())\n const call = await apiCall<{ items?: Array<{ id?: string; label?: string; slug?: string }> }>(`/api/resources/tags?${params.toString()}`)\n const items = Array.isArray(call.result?.items) ? call.result.items : []\n const options = items\n .map((entry) => {\n const value = typeof entry.id === 'string' ? entry.id : null\n if (!value) return null\n const label = typeof entry.label === 'string' && entry.label.trim().length\n ? entry.label.trim()\n : typeof entry.slug === 'string' && entry.slug.trim().length\n ? entry.slug.trim()\n : value\n return { value, label }\n })\n .filter((option): option is FilterOption => option !== null)\n if (options.length > 0) {\n setTagOptions((prev) => {\n const map = new Map(prev.map((opt) => [opt.value, opt]))\n options.forEach((opt) => map.set(opt.value, opt))\n return Array.from(map.values())\n })\n }\n return options\n } catch {\n return []\n }\n },\n [],\n )\n\n const resourceTypeOptions = React.useMemo<FilterOption[]>(() => {\n const entries = Array.from(resourceTypes.values())\n entries.sort((a, b) => a.name.localeCompare(b.name))\n return entries.map((entry) => ({ value: entry.id, label: entry.name }))\n }, [resourceTypes])\n\n const filters = React.useMemo<FilterDef[]>(() => [\n {\n id: 'resourceTypeId',\n label: t('resources.resources.list.filters.resourceType', 'Resource type'),\n type: 'select',\n options: resourceTypeOptions,\n },\n {\n id: 'tagIds',\n label: t('resources.resources.list.filters.tags', 'Tags'),\n type: 'tags',\n loadOptions: loadTagOptions,\n options: tagOptions,\n formatValue: (val: string) => tagOptions.find((o) => o.value === val)?.label ?? val,\n },\n ], [loadTagOptions, resourceTypeOptions, tagOptions, t])\n\n const handleFiltersApply = React.useCallback((values: FilterValues) => {\n setFilterValues(values)\n setPage(1)\n\n const params = new URLSearchParams(searchParams?.toString())\n const hasResourceType = typeof values.resourceTypeId === 'string' && values.resourceTypeId.length > 0\n if (!hasResourceType && params.has('resourceTypeId')) {\n params.delete('resourceTypeId')\n const query = params.toString()\n router.replace(query ? `${pathname}?${query}` : pathname)\n }\n }, [pathname, router, searchParams])\n\n const handleFiltersClear = React.useCallback(() => {\n setFilterValues({})\n setPage(1)\n\n const params = new URLSearchParams(searchParams?.toString())\n if (params.has('resourceTypeId')) {\n params.delete('resourceTypeId')\n const query = params.toString()\n router.replace(query ? `${pathname}?${query}` : pathname)\n }\n }, [pathname, router, searchParams])\n\n const groupedRows = React.useMemo(() => {\n const grouped: ResourceTableRow[] = []\n if (!rows.length) return grouped\n const byType = new Map<string, ResourceRow[]>()\n const unassigned: ResourceRow[] = []\n rows.forEach((row) => {\n if (!row.resourceTypeId) {\n unassigned.push(row)\n return\n }\n const list = byType.get(row.resourceTypeId) ?? []\n list.push(row)\n byType.set(row.resourceTypeId, list)\n })\n const typeEntries = Array.from(byType.entries())\n .map(([typeId, list]) => ({\n typeId,\n list,\n type: resourceTypes.get(typeId),\n }))\n .sort((a, b) => {\n const nameA = a.type?.name ?? ''\n const nameB = b.type?.name ?? ''\n return nameA.localeCompare(nameB)\n })\n for (const entry of typeEntries) {\n const label = entry.type?.name ?? t('resources.resources.list.group.unknown', 'Unknown type')\n grouped.push({\n id: `group:${entry.typeId}`,\n name: label,\n resourceTypeId: entry.typeId,\n appearanceIcon: entry.type?.appearanceIcon ?? null,\n appearanceColor: entry.type?.appearanceColor ?? null,\n rowKind: 'group',\n depth: 0,\n })\n entry.list.forEach((resource) => {\n grouped.push({ ...resource, rowKind: 'resource', depth: 1 })\n })\n }\n if (unassigned.length) {\n grouped.push({\n id: 'group:unassigned',\n name: t('resources.resources.list.group.unassigned', 'Unassigned'),\n resourceTypeId: null,\n appearanceIcon: null,\n appearanceColor: null,\n rowKind: 'group',\n depth: 0,\n })\n unassigned.forEach((resource) => {\n grouped.push({ ...resource, rowKind: 'resource', depth: 1 })\n })\n }\n return grouped\n }, [resourceTypes, rows, t])\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n try {\n const params = new URLSearchParams({\n page: String(page),\n pageSize: String(PAGE_SIZE),\n })\n if (search) params.set('search', search)\n if (selectedResourceTypeId) params.set('resourceTypeId', selectedResourceTypeId)\n const tagIds = Array.isArray(filterValues.tagIds)\n ? filterValues.tagIds\n .map((value) => (typeof value === 'string' ? value.trim() : String(value || '').trim()))\n .filter((value) => value.length > 0)\n : []\n if (tagIds.length > 0) params.set('tagIds', tagIds.join(','))\n const fallback: ResourcesResponse = { items: [], total: 0, page, totalPages: 1 }\n const call = await apiCall<ResourcesResponse>(`/api/resources/resources?${params.toString()}`, undefined, { fallback })\n if (!call.ok) {\n flash(t('resources.resources.list.error.load', 'Failed to load resources.'), 'error')\n return\n }\n const payload = call.result ?? fallback\n if (!cancelled) {\n const items = Array.isArray(payload.items) ? payload.items : []\n const mapped = items.map(mapApiResource)\n setRows(mapped)\n setTotal(payload.total || 0)\n setTotalPages(payload.totalPages || 1)\n }\n } catch (error) {\n if (!cancelled) {\n const message = error instanceof Error ? error.message : t('resources.resources.list.error.load', 'Failed to load resources.')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [filterValues, page, search, scopeVersion, selectedResourceTypeId, t])\n\n const handleDelete = React.useCallback(async (row: ResourceTableRow) => {\n if (row.rowKind !== 'resource') return\n const confirmLabel = t('resources.resources.list.confirmDelete', 'Delete resource \"{name}\"?', { name: row.name })\n const confirmed = await confirm({\n title: confirmLabel,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const headers = buildOptimisticLockHeader(row.updatedAt)\n await runResourceMutation(\n () => withScopedApiRequestHeaders(headers, () => (\n deleteCrud('resources/resources', row.id, {\n errorMessage: t('resources.resources.list.error.delete', 'Failed to delete resource.'),\n })\n )),\n { operation: 'deleteResource', id: row.id, updatedAt: row.updatedAt ?? null },\n row.id,\n )\n flash(t('resources.resources.list.flash.deleted', 'Resource deleted.'), 'success')\n setPage(1)\n router.refresh()\n } catch (error) {\n const message = error instanceof Error ? error.message : t('resources.resources.list.error.delete', 'Failed to delete resource.')\n flash(message, 'error')\n }\n }, [confirm, router, runResourceMutation, t])\n\n const columns = React.useMemo<ColumnDef<ResourceTableRow>[]>(() => [\n {\n accessorKey: 'name',\n header: t('resources.resources.list.columns.name', 'Resource'),\n meta: { priority: 1 },\n cell: ({ row }) => {\n const depth = row.original.depth ?? 0\n const indent = depth > 0 ? 18 : 0\n const isGroup = row.original.rowKind === 'group'\n const showEdit = isGroup && canManage && row.original.resourceTypeId\n return (\n <div className={isGroup ? 'flex items-center justify-between gap-3' : 'flex items-center gap-2'}>\n <span style={{ marginLeft: indent }} className={isGroup ? 'text-sm font-semibold text-foreground' : 'text-sm font-medium text-foreground'}>\n {row.original.name}\n </span>\n {showEdit ? (\n <Button\n asChild\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-7 w-7\"\n title={t('resources.resourceTypes.actions.edit', 'Edit')}\n aria-label={t('resources.resourceTypes.actions.edit', 'Edit')}\n >\n <Link href={`/backend/resources/resource-types/${encodeURIComponent(row.original.resourceTypeId ?? '')}/edit`}>\n <Pencil className=\"h-4 w-4\" />\n </Link>\n </Button>\n ) : null}\n </div>\n )\n },\n },\n {\n accessorKey: 'appearance',\n header: t('resources.resources.list.columns.appearance', 'Appearance'),\n meta: { priority: 2 },\n cell: ({ row }) => {\n const isGroup = row.original.rowKind === 'group'\n const typeId = row.original.resourceTypeId ?? ''\n const type = resourceTypes.get(typeId) ?? null\n const icon = isGroup\n ? row.original.appearanceIcon\n : row.original.appearanceIcon ?? type?.appearanceIcon\n const color = isGroup\n ? row.original.appearanceColor\n : row.original.appearanceColor ?? type?.appearanceColor\n if (!icon && !color) {\n return <span className=\"text-xs text-muted-foreground\">\u2014</span>\n }\n return (\n <div className=\"flex items-center gap-2\">\n {color ? renderDictionaryColor(color) : null}\n {icon ? renderDictionaryIcon(icon) : null}\n </div>\n )\n },\n },\n {\n accessorKey: 'resourceTypeId',\n header: t('resources.resources.list.columns.type', 'Type'),\n meta: { priority: 3 },\n cell: ({ row }) => {\n if (row.original.rowKind === 'group') return null\n return resourceTypes.get(row.original.resourceTypeId ?? '')?.name || t('resources.resources.list.columns.type.empty', 'Unassigned')\n },\n },\n {\n accessorKey: 'capacity',\n header: t('resources.resources.list.columns.capacity', 'Capacity'),\n meta: { priority: 4 },\n cell: ({ row }) => row.original.rowKind === 'group'\n ? null\n : row.original.capacity ?? t('resources.resources.list.columns.capacity.empty', '-'),\n },\n {\n accessorKey: 'tags',\n header: t('resources.resources.list.columns.tags', 'Tags'),\n meta: { priority: 5 },\n cell: ({ row }) => {\n if (row.original.rowKind === 'group') {\n return null\n }\n const tags = row.original.tags ?? []\n if (!tags.length) return <span className=\"text-xs text-muted-foreground\">{t('resources.resources.list.columns.tags.empty', '-')}</span>\n return (\n <div className=\"flex flex-wrap gap-1\">\n {tags.map((tag) => (\n <span key={tag.id} className=\"rounded-full border px-2 py-0.5 text-xs font-medium\">\n {tag.label}\n </span>\n ))}\n </div>\n )\n },\n },\n {\n accessorKey: 'isActive',\n header: t('resources.resources.list.columns.active', 'Active'),\n meta: { priority: 6 },\n cell: ({ row }) => row.original.rowKind === 'group' ? null : <BooleanIcon value={row.original.isActive} />,\n },\n ], [canManage, resourceTypes, t])\n\n return (\n <Page>\n <PageBody>\n <DataTable\n stickyActionsColumn\n title={t('resources.resources.page.title', 'Resources')}\n actions={canManage ? (\n <Button asChild>\n <Link href=\"/backend/resources/resources/create\">{t('resources.resources.list.actions.create', 'New resource')}</Link>\n </Button>\n ) : null}\n columns={columns}\n data={groupedRows}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={handleFiltersApply}\n onFiltersClear={handleFiltersClear}\n perspective={{ tableId: extensionPoints.hosts.resourcesTable.tableId }}\n rowActions={(row) => {\n if (!canManage || row.rowKind !== 'resource') return null\n return (\n <RowActions items={[\n { id: 'edit', label: t('common.edit', 'Edit'), href: `/backend/resources/resources/${encodeURIComponent(row.id)}` },\n { id: 'delete', label: t('common.delete', 'Delete'), destructive: true, onSelect: () => { void handleDelete(row) } },\n ]} />\n )\n }}\n onRowClick={canManage ? (row) => {\n if (row.rowKind !== 'resource') return\n router.push(`/backend/resources/resources/${encodeURIComponent(row.id)}`)\n } : undefined}\n emptyState={(\n <ListEmptyState\n entityName={t('resources.resources.page.title', 'Resources')}\n createHref=\"/backend/resources/resources/create\"\n createLabel={t('resources.resources.list.actions.create', 'New resource')}\n />\n )}\n pagination={{ page, pageSize: PAGE_SIZE, total, totalPages, onPageChange: setPage }}\n isLoading={isLoading}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n\nfunction mapApiResource(item: Record<string, unknown>): ResourceRow {\n const id = typeof item.id === 'string' ? item.id : ''\n const name = typeof item.name === 'string' ? item.name : id\n const resourceTypeId = typeof item.resourceTypeId === 'string'\n ? item.resourceTypeId\n : typeof item.resource_type_id === 'string'\n ? item.resource_type_id\n : null\n const capacity = typeof item.capacity === 'number'\n ? item.capacity\n : typeof item.capacity === 'string'\n ? Number(item.capacity)\n : null\n const isActive = typeof item.isActive === 'boolean'\n ? item.isActive\n : typeof item.is_active === 'boolean'\n ? item.is_active\n : false\n const appearanceIcon = typeof item.appearanceIcon === 'string'\n ? item.appearanceIcon\n : typeof item.appearance_icon === 'string'\n ? item.appearance_icon\n : null\n const appearanceColor = typeof item.appearanceColor === 'string'\n ? item.appearanceColor\n : typeof item.appearance_color === 'string'\n ? item.appearance_color\n : null\n const tags = Array.isArray(item.tags) ? item.tags as TagOption[] : []\n const updatedAt = typeof item.updatedAt === 'string'\n ? item.updatedAt\n : typeof item.updated_at === 'string'\n ? item.updated_at\n : null\n return withDataTableNamespaces({\n id,\n name,\n resourceTypeId,\n capacity: Number.isFinite(capacity as number) ? capacity as number : null,\n tags,\n isActive,\n appearanceIcon,\n appearanceColor,\n updatedAt,\n }, item)\n}\n"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/resources/extension-points'\nimport Link from 'next/link'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable, withDataTableNamespaces } from '@open-mercato/ui/backend/DataTable'\nimport { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { BooleanIcon } from '@open-mercato/ui/backend/ValueIcons'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport type { FilterDef, FilterOption, FilterValues } from '@open-mercato/ui/backend/FilterOverlay'\nimport type { TagOption } from '@open-mercato/ui/backend/detail'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { renderDictionaryColor, renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { Pencil } from 'lucide-react'\n\nconst PAGE_SIZE = 20\nconst RESOURCE_LIST_MUTATION_CONTEXT_ID = 'resources.resources.list'\n\ntype ResourceRow = {\n id: string\n name: string\n resourceTypeId: string | null\n capacity: number | null\n tags?: TagOption[] | null\n isActive: boolean\n appearanceIcon?: string | null\n appearanceColor?: string | null\n updatedAt?: string | null\n}\n\ntype ResourceTypeRow = {\n id: string\n name: string\n appearanceIcon: string | null\n appearanceColor: string | null\n}\n\ntype ResourceGroupRow = {\n id: string\n name: string\n resourceTypeId: string | null\n appearanceIcon: string | null\n appearanceColor: string | null\n rowKind: 'group'\n depth: number\n}\n\ntype ResourceTableRow = (ResourceRow & { rowKind: 'resource'; depth: number }) | ResourceGroupRow\n\ntype ResourcesResponse = {\n items: Array<Record<string, unknown>>\n total: number\n page: number\n totalPages: number\n}\n\ntype ResourceTypesResponse = {\n items: Array<Record<string, unknown>>\n}\n\ntype ResourceListMutationContext = {\n formId: string\n resourceKind: string\n resourceId?: string\n retryLastMutation: () => Promise<boolean>\n}\n\nexport default function ResourcesResourcesPage() {\n const [rows, setRows] = React.useState<ResourceRow[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [resourceTypes, setResourceTypes] = React.useState<Map<string, ResourceTypeRow>>(new Map())\n const [canManage, setCanManage] = React.useState(false)\n const [tagOptions, setTagOptions] = React.useState<FilterOption[]>([])\n const scopeVersion = useOrganizationScopeVersion()\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const router = useRouter()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const resourceTypeFilter = searchParams.get('resourceTypeId')\n const selectedResourceTypeId = typeof filterValues.resourceTypeId === 'string'\n ? filterValues.resourceTypeId\n : resourceTypeFilter\n const { runMutation, retryLastMutation } = useGuardedMutation<ResourceListMutationContext>({\n contextId: RESOURCE_LIST_MUTATION_CONTEXT_ID,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const runResourceMutation = React.useCallback(\n async <T,>(\n operation: () => Promise<T>,\n mutationPayload: Record<string, unknown>,\n resourceId?: string,\n ): Promise<T> => runMutation({\n operation,\n mutationPayload,\n context: {\n formId: RESOURCE_LIST_MUTATION_CONTEXT_ID,\n resourceKind: 'resources.resource',\n resourceId,\n retryLastMutation,\n },\n }),\n [retryLastMutation, runMutation],\n )\n\n React.useEffect(() => {\n setPage(1)\n }, [resourceTypeFilter])\n\n React.useEffect(() => {\n if (!resourceTypeFilter) return\n setFilterValues((prev) => {\n if (prev.resourceTypeId === resourceTypeFilter) return prev\n if (typeof prev.resourceTypeId === 'string' && prev.resourceTypeId.length > 0) return prev\n return { ...prev, resourceTypeId: resourceTypeFilter }\n })\n }, [resourceTypeFilter])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadPermissions() {\n try {\n const call = await apiCall<{ granted?: string[]; ok?: boolean }>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features: ['resources.manage_resources'] }),\n })\n if (!cancelled) {\n const granted = Array.isArray(call.result?.granted) ? call.result?.granted : []\n setCanManage(call.result?.ok === true || granted.includes('resources.manage_resources'))\n }\n } catch {\n if (!cancelled) setCanManage(false)\n }\n }\n loadPermissions()\n return () => { cancelled = true }\n }, [])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadResourceTypes() {\n try {\n const params = new URLSearchParams({ page: '1', pageSize: '100' })\n const call = await apiCall<ResourceTypesResponse>(`/api/resources/resource-types?${params.toString()}`)\n const items = Array.isArray(call.result?.items) ? call.result.items : []\n const map = new Map<string, ResourceTypeRow>()\n for (const item of items) {\n const raw = item as Record<string, unknown>\n const id = typeof raw.id === 'string' ? raw.id : ''\n const name = typeof raw.name === 'string' ? raw.name : id\n const appearanceIcon = typeof raw.appearanceIcon === 'string'\n ? raw.appearanceIcon\n : typeof raw.appearance_icon === 'string'\n ? raw.appearance_icon\n : null\n const appearanceColor = typeof raw.appearanceColor === 'string'\n ? raw.appearanceColor\n : typeof raw.appearance_color === 'string'\n ? raw.appearance_color\n : null\n map.set(id, {\n id,\n name,\n appearanceIcon,\n appearanceColor,\n })\n }\n if (!cancelled) setResourceTypes(map)\n } catch {\n if (!cancelled) setResourceTypes(new Map())\n }\n }\n loadResourceTypes()\n return () => { cancelled = true }\n }, [scopeVersion])\n\n const loadTagOptions = React.useCallback(\n async (query?: string): Promise<FilterOption[]> => {\n try {\n const params = new URLSearchParams({ pageSize: '100' })\n if (query && query.trim().length) params.set('search', query.trim())\n const call = await apiCall<{ items?: Array<{ id?: string; label?: string; slug?: string }> }>(`/api/resources/tags?${params.toString()}`)\n const items = Array.isArray(call.result?.items) ? call.result.items : []\n const options = items\n .map((entry) => {\n const value = typeof entry.id === 'string' ? entry.id : null\n if (!value) return null\n const label = typeof entry.label === 'string' && entry.label.trim().length\n ? entry.label.trim()\n : typeof entry.slug === 'string' && entry.slug.trim().length\n ? entry.slug.trim()\n : value\n return { value, label }\n })\n .filter((option): option is FilterOption => option !== null)\n if (options.length > 0) {\n setTagOptions((prev) => {\n const map = new Map(prev.map((opt) => [opt.value, opt]))\n options.forEach((opt) => map.set(opt.value, opt))\n return Array.from(map.values())\n })\n }\n return options\n } catch {\n return []\n }\n },\n [],\n )\n\n const resourceTypeOptions = React.useMemo<FilterOption[]>(() => {\n const entries = Array.from(resourceTypes.values())\n entries.sort((a, b) => a.name.localeCompare(b.name))\n return entries.map((entry) => ({ value: entry.id, label: entry.name }))\n }, [resourceTypes])\n\n const filters = React.useMemo<FilterDef[]>(() => [\n {\n id: 'resourceTypeId',\n label: t('resources.resources.list.filters.resourceType', 'Resource type'),\n type: 'select',\n options: resourceTypeOptions,\n },\n {\n id: 'tagIds',\n label: t('resources.resources.list.filters.tags', 'Tags'),\n type: 'tags',\n loadOptions: loadTagOptions,\n options: tagOptions,\n formatValue: (val: string) => tagOptions.find((o) => o.value === val)?.label ?? val,\n },\n ], [loadTagOptions, resourceTypeOptions, tagOptions, t])\n\n const handleFiltersApply = React.useCallback((values: FilterValues) => {\n setFilterValues(values)\n setPage(1)\n\n const params = new URLSearchParams(searchParams?.toString())\n const hasResourceType = typeof values.resourceTypeId === 'string' && values.resourceTypeId.length > 0\n if (!hasResourceType && params.has('resourceTypeId')) {\n params.delete('resourceTypeId')\n const query = params.toString()\n router.replace(query ? `${pathname}?${query}` : pathname)\n }\n }, [pathname, router, searchParams])\n\n const handleFiltersClear = React.useCallback(() => {\n setFilterValues({})\n setPage(1)\n\n const params = new URLSearchParams(searchParams?.toString())\n if (params.has('resourceTypeId')) {\n params.delete('resourceTypeId')\n const query = params.toString()\n router.replace(query ? `${pathname}?${query}` : pathname)\n }\n }, [pathname, router, searchParams])\n\n const groupedRows = React.useMemo(() => {\n const grouped: ResourceTableRow[] = []\n if (!rows.length) return grouped\n const byType = new Map<string, ResourceRow[]>()\n const unassigned: ResourceRow[] = []\n rows.forEach((row) => {\n if (!row.resourceTypeId) {\n unassigned.push(row)\n return\n }\n const list = byType.get(row.resourceTypeId) ?? []\n list.push(row)\n byType.set(row.resourceTypeId, list)\n })\n const typeEntries = Array.from(byType.entries())\n .map(([typeId, list]) => ({\n typeId,\n list,\n type: resourceTypes.get(typeId),\n }))\n .sort((a, b) => {\n const nameA = a.type?.name ?? ''\n const nameB = b.type?.name ?? ''\n return nameA.localeCompare(nameB)\n })\n for (const entry of typeEntries) {\n const label = entry.type?.name ?? t('resources.resources.list.group.unknown', 'Unknown type')\n grouped.push({\n id: `group:${entry.typeId}`,\n name: label,\n resourceTypeId: entry.typeId,\n appearanceIcon: entry.type?.appearanceIcon ?? null,\n appearanceColor: entry.type?.appearanceColor ?? null,\n rowKind: 'group',\n depth: 0,\n })\n entry.list.forEach((resource) => {\n grouped.push({ ...resource, rowKind: 'resource', depth: 1 })\n })\n }\n if (unassigned.length) {\n grouped.push({\n id: 'group:unassigned',\n name: t('resources.resources.list.group.unassigned', 'Unassigned'),\n resourceTypeId: null,\n appearanceIcon: null,\n appearanceColor: null,\n rowKind: 'group',\n depth: 0,\n })\n unassigned.forEach((resource) => {\n grouped.push({ ...resource, rowKind: 'resource', depth: 1 })\n })\n }\n return grouped\n }, [resourceTypes, rows, t])\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n try {\n const params = new URLSearchParams({\n page: String(page),\n pageSize: String(PAGE_SIZE),\n })\n if (search) params.set('search', search)\n if (selectedResourceTypeId) params.set('resourceTypeId', selectedResourceTypeId)\n const tagIds = Array.isArray(filterValues.tagIds)\n ? filterValues.tagIds\n .map((value) => (typeof value === 'string' ? value.trim() : String(value || '').trim()))\n .filter((value) => value.length > 0)\n : []\n if (tagIds.length > 0) params.set('tagIds', tagIds.join(','))\n const fallback: ResourcesResponse = { items: [], total: 0, page, totalPages: 1 }\n const call = await apiCall<ResourcesResponse>(`/api/resources/resources?${params.toString()}`, undefined, { fallback })\n if (!call.ok) {\n flash(t('resources.resources.list.error.load', 'Failed to load resources.'), 'error')\n return\n }\n const payload = call.result ?? fallback\n if (!cancelled) {\n const items = Array.isArray(payload.items) ? payload.items : []\n const mapped = items.map(mapApiResource)\n setRows(mapped)\n setTotal(payload.total || 0)\n setTotalPages(payload.totalPages || 1)\n }\n } catch (error) {\n if (!cancelled) {\n const message = error instanceof Error ? error.message : t('resources.resources.list.error.load', 'Failed to load resources.')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [filterValues, page, search, scopeVersion, selectedResourceTypeId, t])\n\n const handleDelete = React.useCallback(async (row: ResourceTableRow) => {\n if (row.rowKind !== 'resource') return\n const confirmLabel = t('resources.resources.list.confirmDelete', 'Delete resource \"{name}\"?', { name: row.name })\n const confirmed = await confirm({\n title: confirmLabel,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const headers = buildOptimisticLockHeader(row.updatedAt)\n await runResourceMutation(\n () => withScopedApiRequestHeaders(headers, () => (\n deleteCrud('resources/resources', row.id, {\n errorMessage: t('resources.resources.list.error.delete', 'Failed to delete resource.'),\n })\n )),\n { operation: 'deleteResource', id: row.id, updatedAt: row.updatedAt ?? null },\n row.id,\n )\n flash(t('resources.resources.list.flash.deleted', 'Resource deleted.'), 'success')\n setPage(1)\n router.refresh()\n } catch (error) {\n const message = error instanceof Error ? error.message : t('resources.resources.list.error.delete', 'Failed to delete resource.')\n flash(message, 'error')\n }\n }, [confirm, router, runResourceMutation, t])\n\n const columns = React.useMemo<ColumnDef<ResourceTableRow>[]>(() => [\n {\n accessorKey: 'name',\n header: t('resources.resources.list.columns.name', 'Resource'),\n meta: { priority: 1 },\n cell: ({ row }) => {\n const depth = row.original.depth ?? 0\n const indent = depth > 0 ? 18 : 0\n const isGroup = row.original.rowKind === 'group'\n const showEdit = isGroup && canManage && row.original.resourceTypeId\n return (\n <div className={isGroup ? 'flex items-center justify-between gap-3' : 'flex items-center gap-2'}>\n <span style={{ marginLeft: indent }} className={isGroup ? 'text-sm font-semibold text-foreground' : 'text-sm font-medium text-foreground'}>\n {row.original.name}\n </span>\n {showEdit ? (\n <Button\n asChild\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-7 w-7\"\n title={t('resources.resourceTypes.actions.edit', 'Edit')}\n aria-label={t('resources.resourceTypes.actions.edit', 'Edit')}\n >\n <Link href={`/backend/resources/resource-types/${encodeURIComponent(row.original.resourceTypeId ?? '')}/edit`}>\n <Pencil className=\"h-4 w-4\" />\n </Link>\n </Button>\n ) : null}\n </div>\n )\n },\n },\n {\n accessorKey: 'appearance',\n header: t('resources.resources.list.columns.appearance', 'Appearance'),\n meta: { priority: 2 },\n cell: ({ row }) => {\n const isGroup = row.original.rowKind === 'group'\n const typeId = row.original.resourceTypeId ?? ''\n const type = resourceTypes.get(typeId) ?? null\n const icon = isGroup\n ? row.original.appearanceIcon\n : row.original.appearanceIcon ?? type?.appearanceIcon\n const color = isGroup\n ? row.original.appearanceColor\n : row.original.appearanceColor ?? type?.appearanceColor\n if (!icon && !color) {\n return <span className=\"text-xs text-muted-foreground\">\u2014</span>\n }\n return (\n <div className=\"flex items-center gap-2\">\n {color ? renderDictionaryColor(color) : null}\n {icon ? renderDictionaryIcon(icon) : null}\n </div>\n )\n },\n },\n {\n accessorKey: 'resourceTypeId',\n header: t('resources.resources.list.columns.type', 'Type'),\n meta: { priority: 3 },\n cell: ({ row }) => {\n if (row.original.rowKind === 'group') return null\n return resourceTypes.get(row.original.resourceTypeId ?? '')?.name || t('resources.resources.list.columns.type.empty', 'Unassigned')\n },\n },\n {\n accessorKey: 'capacity',\n header: t('resources.resources.list.columns.capacity', 'Capacity'),\n meta: { priority: 4 },\n cell: ({ row }) => row.original.rowKind === 'group'\n ? null\n : row.original.capacity ?? t('resources.resources.list.columns.capacity.empty', '-'),\n },\n {\n accessorKey: 'tags',\n header: t('resources.resources.list.columns.tags', 'Tags'),\n meta: { priority: 5 },\n cell: ({ row }) => {\n if (row.original.rowKind === 'group') {\n return null\n }\n const tags = row.original.tags ?? []\n if (!tags.length) return <span className=\"text-xs text-muted-foreground\">{t('resources.resources.list.columns.tags.empty', '-')}</span>\n return (\n <div className=\"flex flex-wrap gap-1\">\n {tags.map((tag) => (\n <span key={tag.id} className=\"rounded-full border px-2 py-0.5 text-xs font-medium\">\n {tag.label}\n </span>\n ))}\n </div>\n )\n },\n },\n {\n accessorKey: 'isActive',\n header: t('resources.resources.list.columns.active', 'Active'),\n meta: { priority: 6 },\n cell: ({ row }) => row.original.rowKind === 'group' ? null : <BooleanIcon value={row.original.isActive} />,\n },\n ], [canManage, resourceTypes, t])\n\n return (\n <Page>\n <PageBody>\n <DataTable\n stickyActionsColumn\n title={t('resources.resources.page.title', 'Resources')}\n actions={canManage ? (\n <Button asChild>\n <Link href=\"/backend/resources/resources/create\">{t('resources.resources.list.actions.create', 'New resource')}</Link>\n </Button>\n ) : null}\n columns={columns}\n data={groupedRows}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={handleFiltersApply}\n onFiltersClear={handleFiltersClear}\n perspective={{ tableId: extensionPoints.hosts.resourcesTable.tableId }}\n rowActions={(row) => {\n if (!canManage || row.rowKind !== 'resource') return null\n return (\n <RowActions items={[\n { id: 'edit', label: t('common.edit', 'Edit'), href: `/backend/resources/resources/${encodeURIComponent(row.id)}` },\n { id: 'delete', label: t('common.delete', 'Delete'), destructive: true, onSelect: () => { void handleDelete(row) } },\n ]} />\n )\n }}\n onRowClick={canManage ? (row) => {\n if (row.rowKind !== 'resource') return\n router.push(`/backend/resources/resources/${encodeURIComponent(row.id)}`)\n } : undefined}\n emptyState={(\n <ListEmptyState\n entityName={t('resources.resources.page.title', 'Resources')}\n createHref=\"/backend/resources/resources/create\"\n createLabel={t('resources.resources.list.actions.create', 'New resource')}\n />\n )}\n pagination={{ page, pageSize: PAGE_SIZE, total, totalPages, onPageChange: setPage }}\n isLoading={isLoading}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n\nfunction mapApiResource(item: Record<string, unknown>): ResourceRow {\n const id = typeof item.id === 'string' ? item.id : ''\n const name = typeof item.name === 'string' ? item.name : id\n const resourceTypeId = typeof item.resourceTypeId === 'string'\n ? item.resourceTypeId\n : typeof item.resource_type_id === 'string'\n ? item.resource_type_id\n : null\n const capacity = typeof item.capacity === 'number'\n ? item.capacity\n : typeof item.capacity === 'string'\n ? Number(item.capacity)\n : null\n const isActive = typeof item.isActive === 'boolean'\n ? item.isActive\n : typeof item.is_active === 'boolean'\n ? item.is_active\n : false\n const appearanceIcon = typeof item.appearanceIcon === 'string'\n ? item.appearanceIcon\n : typeof item.appearance_icon === 'string'\n ? item.appearance_icon\n : null\n const appearanceColor = typeof item.appearanceColor === 'string'\n ? item.appearanceColor\n : typeof item.appearance_color === 'string'\n ? item.appearance_color\n : null\n const tags = Array.isArray(item.tags) ? item.tags as TagOption[] : []\n const updatedAt = typeof item.updatedAt === 'string'\n ? item.updatedAt\n : typeof item.updated_at === 'string'\n ? item.updated_at\n : null\n return withDataTableNamespaces({\n id,\n name,\n resourceTypeId,\n capacity: Number.isFinite(capacity as number) ? capacity as number : null,\n tags,\n isActive,\n appearanceIcon,\n appearanceColor,\n updatedAt,\n }, item)\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }