@open-mercato/core 0.10.1-develop.7857.1.259928eb85 → 0.10.1-develop.7863.1.335fb1fe52

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 (22) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js +5 -4
  3. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js.map +3 -3
  4. package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js +1 -4
  5. package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js.map +2 -2
  6. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +7 -1
  7. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  8. package/dist/modules/warranty_claims/backend/components/WarrantyWorkspace.js +8 -1
  9. package/dist/modules/warranty_claims/backend/components/WarrantyWorkspace.js.map +2 -2
  10. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js +18 -3
  11. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js.map +2 -2
  12. package/package.json +7 -7
  13. package/src/modules/audit_logs/api/audit-logs/actions/redo/route.ts +3 -1
  14. package/src/modules/audit_logs/i18n/de.json +1 -0
  15. package/src/modules/audit_logs/i18n/en.json +1 -0
  16. package/src/modules/audit_logs/i18n/es.json +1 -0
  17. package/src/modules/audit_logs/i18n/ko.json +1 -0
  18. package/src/modules/audit_logs/i18n/pl.json +1 -0
  19. package/src/modules/customers/widgets/dashboard/customer-todos/widget.client.tsx +0 -3
  20. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +8 -1
  21. package/src/modules/warranty_claims/backend/components/WarrantyWorkspace.tsx +8 -1
  22. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.tsx +22 -3
@@ -1,4 +1,4 @@
1
- [build:core] found 6248 entry points
1
+ [build:core] found 6249 entry points
2
2
  [build:core] built successfully
3
3
  [build:core:generated] found 271 entry points
4
4
  [build:core:generated] built successfully
@@ -4,10 +4,10 @@ import { resolveSingleOrganizationIdOrDeny } from "@open-mercato/shared/lib/auth
4
4
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
5
5
  import { resolveFeatureCheckContext, resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
6
6
  import { isCrudHttpError, translateCrudErrorBody } from "@open-mercato/shared/lib/crud/errors";
7
- import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
8
7
  import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
9
8
  import { z } from "zod";
10
9
  import { createLogger } from "@open-mercato/shared/lib/logger";
10
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
11
11
  import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
12
12
  import { authorizeAuditReplayWithEntityManager } from "@open-mercato/core/modules/audit_logs/lib/replayAuthorization";
13
13
  import {
@@ -151,15 +151,16 @@ async function POST(req) {
151
151
  } catch (err) {
152
152
  if (isCrudHttpError(err)) {
153
153
  logger.warn("Redo rejected", { err });
154
- const { translate } = await resolveTranslations();
155
- return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status });
154
+ const { translate: translate2 } = await resolveTranslations();
155
+ return NextResponse.json(translateCrudErrorBody(err.body, translate2), { status: err.status });
156
156
  }
157
157
  const interceptorRejection = getCommandInterceptorHttpRejection(err);
158
158
  if (interceptorRejection) {
159
159
  return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
160
160
  }
161
161
  logger.error("Redo failed", { err });
162
- return NextResponse.json({ error: "Redo failed" }, { status: 400 });
162
+ const { translate } = await resolveTranslations();
163
+ return NextResponse.json({ error: translate("audit_logs.error.redo_failed", "Redo failed") }, { status: 400 });
163
164
  }
164
165
  }
165
166
  async function createRuntimeContext(container, auth, request) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/audit_logs/api/audit-logs/actions/redo/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest, type AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { resolveSingleOrganizationIdOrDeny } from '@open-mercato/shared/lib/auth/organizationScope'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveFeatureCheckContext, resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport { isCrudHttpError, translateCrudErrorBody } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport type { CommandRuntimeContext, CommandLogMetadata } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { AwilixContainer } from 'awilix'\nimport type { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { authorizeAuditReplayWithEntityManager } from '@open-mercato/core/modules/audit_logs/lib/replayAuthorization'\nimport {\n actionLogBelongsToAuth,\n resolveCanonicalAuthSubject,\n resolveActionLogActorSubject,\n} from '@open-mercato/core/modules/audit_logs/lib/actorSubject'\n\nconst logger = createLogger('audit_logs').child({ component: 'redo' })\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['audit_logs.redo_self'] },\n}\n\ntype RedoRequestBody = {\n logId?: string\n}\n\nconst redoRequestSchema = z.object({\n logId: z.string().min(1).describe('Identifier of the previously undone action log'),\n})\n\nconst redoResponseSchema = z.object({\n ok: z.literal(true),\n logId: z.string().nullable().describe('Identifier of the new redo log entry, if available'),\n undoToken: z.string().nullable().describe('New undo token associated with the redone action'),\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n})\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const authSubject = resolveCanonicalAuthSubject(auth)\n if (!authSubject) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = (await req.json().catch(() => null)) as RedoRequestBody | null\n const logId = typeof body?.logId === 'string' ? body.logId.trim() : ''\n if (!logId) return NextResponse.json({ error: 'Invalid log id' }, { status: 400 })\n\n const container = await createRequestContainer()\n const commandBus = (container.resolve('commandBus') as CommandBus)\n const logs = (container.resolve('actionLogService') as ActionLogService)\n let rbac: RbacService | null = null\n try {\n rbac = (container.resolve('rbacService') as RbacService)\n } catch {\n rbac = null\n }\n\n const { organizationId, scope } = await resolveFeatureCheckContext({ container, auth, request: req })\n let singleOrganizationId: string | null\n try {\n singleOrganizationId = organizationId ?? resolveSingleOrganizationIdOrDeny(scope, auth)\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n const canRedoTenant = rbac\n ? await rbac.userHasAllFeatures(auth.sub, ['audit_logs.redo_tenant'], {\n tenantId: auth.tenantId ?? null,\n organizationId,\n })\n : false\n\n const scopedOrgId = canRedoTenant ? organizationId ?? null : singleOrganizationId\n const log = await logs.findById(logId)\n\n if (!log || log.executionState !== 'undone') {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n const logActorSubject = resolveActionLogActorSubject(log)\n const isLegacyApiKeyOwner = log.actorUserId === authSubject.storageId\n && authSubject.kind === 'api_key'\n && log.contextJson?.actorSubject === undefined\n if (log.actorUserId && !logActorSubject && !isLegacyApiKeyOwner) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n if (log.actorUserId && !actionLogBelongsToAuth(log, auth) && !canRedoTenant) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n // Fail closed on tenant scope: `audit_logs.redo_tenant` only widens scope WITHIN a\n // tenant, never across tenants, so a tenant-scoped target always requires a caller\n // bound to that same tenant. A caller whose tenantId is null (tenant-less global\n // account or unscoped API key) must never redo a tenant-scoped row. Mirrors the\n // hardened undo route (issue #2685, ported in #2931).\n if (log.tenantId && log.tenantId !== (auth.tenantId ?? null)) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n // Tenant-level redoers may redo across organizations within the tenant, so an\n // unresolved (null) caller org is allowed and only an explicit mismatch is rejected.\n // Every other caller must resolve to the target's own organization \u2014 a null caller\n // org must not bypass an org-scoped target (issue #2685, ported in #2931).\n const orgScopeMismatch = canRedoTenant\n ? Boolean(log.organizationId && scopedOrgId && log.organizationId !== scopedOrgId)\n : Boolean(log.organizationId && log.organizationId !== scopedOrgId)\n if (orgScopeMismatch) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n\n const lookupActorId = canRedoTenant\n ? (logActorSubject ?? (isLegacyApiKeyOwner ? authSubject.subject : auth.sub))\n : authSubject.subject\n const latestUndoneOrganizationId = log.organizationId ?? null\n const latestUndone = await logs.latestUndoneForActor(lookupActorId, {\n tenantId: auth.tenantId ?? null,\n organizationId: latestUndoneOrganizationId,\n })\n if (!latestUndone || latestUndone.id !== log.id) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n if (!rbac) return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n const replayRbac = rbac\n\n try {\n const ctx = await createRuntimeContext(container, auth, req)\n ctx.replayTransactionGuard = ({ logEntry, transactionalEm }) =>\n authorizeAuditReplayWithEntityManager(transactionalEm, replayRbac, logEntry, {\n auth,\n organizationId,\n selfFeature: 'audit_logs.redo_self',\n tenantFeature: 'audit_logs.redo_tenant',\n unavailableMessage: 'Redo target not available',\n })\n const contextRecord = log.contextJson && typeof log.contextJson === 'object' ? (log.contextJson as Record<string, unknown>) : null\n const cacheAliasesRaw = Array.isArray(contextRecord?.cacheAliases as unknown[])\n ? (contextRecord!.cacheAliases as unknown[])\n : []\n const cacheAliases = cacheAliasesRaw\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0)\n .map((value) => value.trim())\n const metadataContext: Record<string, unknown> = {\n historyAction: 'redo',\n sourceLogId: log.id,\n sourceCommandId: log.commandId,\n }\n if (cacheAliases.length) metadataContext.cacheAliases = cacheAliases\n const metadata: CommandLogMetadata = {\n tenantId: log.tenantId,\n organizationId: log.organizationId,\n actorUserId: auth.sub,\n actionLabel: log.actionLabel,\n resourceKind: log.resourceKind,\n resourceId: log.resourceId,\n context: metadataContext,\n }\n const resolvedInput = resolveRedoInput(log.commandPayload, log)\n if (!resolvedInput) {\n return NextResponse.json({ error: 'Redo data unavailable for this action' }, { status: 400 })\n }\n const commandInput = resolvedInput\n const { logEntry, replaySourceFinalized } = await commandBus.execute(log.commandId, {\n input: commandInput,\n ctx,\n metadata,\n redoLogEntry: log,\n })\n if (replaySourceFinalized !== true) await logs.markRedone(log.id)\n const actionLog = asActionLog(logEntry)\n const response = NextResponse.json({\n ok: true,\n logId: actionLog?.id ?? null,\n undoToken: actionLog?.undoToken ?? null,\n })\n if (actionLog?.undoToken && actionLog.id) {\n const createdAt = actionLog.createdAt instanceof Date\n ? actionLog.createdAt.toISOString()\n : (typeof actionLog.createdAt === 'string' ? actionLog.createdAt : new Date().toISOString())\n response.headers.set('x-om-operation', serializeOperationMetadata({\n id: actionLog.id,\n undoToken: actionLog.undoToken,\n commandId: actionLog.commandId ?? log.commandId,\n actionLabel: actionLog.actionLabel ?? log.actionLabel ?? null,\n resourceKind: typeof actionLog.resourceKind === 'string' ? actionLog.resourceKind : log.resourceKind ?? null,\n resourceId: typeof actionLog.resourceId === 'string' ? actionLog.resourceId : log.resourceId ?? null,\n executedAt: createdAt,\n }))\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n logger.warn('Redo rejected', { err })\n const { translate } = await resolveTranslations()\n return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n logger.error('Redo failed', { err })\n return NextResponse.json({ error: 'Redo failed' }, { status: 400 })\n }\n}\n\nasync function createRuntimeContext(container: AwilixContainer, auth: AuthContext, request: Request): Promise<CommandRuntimeContext> {\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n return {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope.selectedId,\n organizationIds: scope.filterIds,\n request,\n }\n}\n\nfunction asActionLog(entry: unknown): ActionLog | null {\n if (!entry || typeof entry !== 'object') return null\n if (typeof (entry as { id?: unknown }).id !== 'string') return null\n return entry as ActionLog\n}\n\nfunction resolveRedoInput(payload: unknown, log: ActionLog): unknown | null {\n if (payload && typeof payload === 'object' && !Array.isArray(payload) && '__redoInput' in payload) {\n const envelope = payload as { __redoInput?: unknown }\n return envelope.__redoInput ?? {}\n }\n const updateFallback = deriveUpdateInput(log)\n if (updateFallback) return updateFallback\n return null\n}\n\nfunction deriveUpdateInput(log: ActionLog): Record<string, unknown> | null {\n if (!log.commandId.endsWith('.update')) return null\n if (!log.resourceId) return null\n const changes = log.changesJson\n if (!changes || typeof changes !== 'object' || Array.isArray(changes)) return { id: log.resourceId }\n const payload: Record<string, unknown> = { id: log.resourceId }\n for (const [key, value] of Object.entries(changes)) {\n if (value && typeof value === 'object' && !Array.isArray(value) && 'to' in value) {\n payload[key] = (value as Record<string, unknown>).to\n } else {\n payload[key] = value\n }\n }\n return payload\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Redo a previously undone action',\n description: 'Replays the command associated with a recently undone action, reapplying the change and issuing a fresh undo token.',\n methods: {\n POST: {\n summary: 'Redo by action log id',\n description:\n 'Redoes the latest undone command owned by the caller. Requires the action to still be eligible for redo within tenant and organization scope.',\n requestBody: {\n contentType: 'application/json',\n schema: redoRequestSchema,\n },\n responses: [\n { status: 200, description: 'Redo executed successfully', schema: redoResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Log not eligible for redo', schema: errorSchema },\n { status: 401, description: 'Authentication required', schema: errorSchema },\n { status: 403, description: 'Redo blocked by scope checks', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
6
- "names": ["logEntry", "metadata"]
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest, type AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { resolveSingleOrganizationIdOrDeny } from '@open-mercato/shared/lib/auth/organizationScope'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveFeatureCheckContext, resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport { isCrudHttpError, translateCrudErrorBody } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport type { CommandRuntimeContext, CommandLogMetadata } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { AwilixContainer } from 'awilix'\nimport type { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { authorizeAuditReplayWithEntityManager } from '@open-mercato/core/modules/audit_logs/lib/replayAuthorization'\nimport {\n actionLogBelongsToAuth,\n resolveCanonicalAuthSubject,\n resolveActionLogActorSubject,\n} from '@open-mercato/core/modules/audit_logs/lib/actorSubject'\n\nconst logger = createLogger('audit_logs').child({ component: 'redo' })\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['audit_logs.redo_self'] },\n}\n\ntype RedoRequestBody = {\n logId?: string\n}\n\nconst redoRequestSchema = z.object({\n logId: z.string().min(1).describe('Identifier of the previously undone action log'),\n})\n\nconst redoResponseSchema = z.object({\n ok: z.literal(true),\n logId: z.string().nullable().describe('Identifier of the new redo log entry, if available'),\n undoToken: z.string().nullable().describe('New undo token associated with the redone action'),\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n})\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const authSubject = resolveCanonicalAuthSubject(auth)\n if (!authSubject) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = (await req.json().catch(() => null)) as RedoRequestBody | null\n const logId = typeof body?.logId === 'string' ? body.logId.trim() : ''\n if (!logId) return NextResponse.json({ error: 'Invalid log id' }, { status: 400 })\n\n const container = await createRequestContainer()\n const commandBus = (container.resolve('commandBus') as CommandBus)\n const logs = (container.resolve('actionLogService') as ActionLogService)\n let rbac: RbacService | null = null\n try {\n rbac = (container.resolve('rbacService') as RbacService)\n } catch {\n rbac = null\n }\n\n const { organizationId, scope } = await resolveFeatureCheckContext({ container, auth, request: req })\n let singleOrganizationId: string | null\n try {\n singleOrganizationId = organizationId ?? resolveSingleOrganizationIdOrDeny(scope, auth)\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n const canRedoTenant = rbac\n ? await rbac.userHasAllFeatures(auth.sub, ['audit_logs.redo_tenant'], {\n tenantId: auth.tenantId ?? null,\n organizationId,\n })\n : false\n\n const scopedOrgId = canRedoTenant ? organizationId ?? null : singleOrganizationId\n const log = await logs.findById(logId)\n\n if (!log || log.executionState !== 'undone') {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n const logActorSubject = resolveActionLogActorSubject(log)\n const isLegacyApiKeyOwner = log.actorUserId === authSubject.storageId\n && authSubject.kind === 'api_key'\n && log.contextJson?.actorSubject === undefined\n if (log.actorUserId && !logActorSubject && !isLegacyApiKeyOwner) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n if (log.actorUserId && !actionLogBelongsToAuth(log, auth) && !canRedoTenant) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n // Fail closed on tenant scope: `audit_logs.redo_tenant` only widens scope WITHIN a\n // tenant, never across tenants, so a tenant-scoped target always requires a caller\n // bound to that same tenant. A caller whose tenantId is null (tenant-less global\n // account or unscoped API key) must never redo a tenant-scoped row. Mirrors the\n // hardened undo route (issue #2685, ported in #2931).\n if (log.tenantId && log.tenantId !== (auth.tenantId ?? null)) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n // Tenant-level redoers may redo across organizations within the tenant, so an\n // unresolved (null) caller org is allowed and only an explicit mismatch is rejected.\n // Every other caller must resolve to the target's own organization \u2014 a null caller\n // org must not bypass an org-scoped target (issue #2685, ported in #2931).\n const orgScopeMismatch = canRedoTenant\n ? Boolean(log.organizationId && scopedOrgId && log.organizationId !== scopedOrgId)\n : Boolean(log.organizationId && log.organizationId !== scopedOrgId)\n if (orgScopeMismatch) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n\n const lookupActorId = canRedoTenant\n ? (logActorSubject ?? (isLegacyApiKeyOwner ? authSubject.subject : auth.sub))\n : authSubject.subject\n const latestUndoneOrganizationId = log.organizationId ?? null\n const latestUndone = await logs.latestUndoneForActor(lookupActorId, {\n tenantId: auth.tenantId ?? null,\n organizationId: latestUndoneOrganizationId,\n })\n if (!latestUndone || latestUndone.id !== log.id) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n if (!rbac) return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n const replayRbac = rbac\n\n try {\n const ctx = await createRuntimeContext(container, auth, req)\n ctx.replayTransactionGuard = ({ logEntry, transactionalEm }) =>\n authorizeAuditReplayWithEntityManager(transactionalEm, replayRbac, logEntry, {\n auth,\n organizationId,\n selfFeature: 'audit_logs.redo_self',\n tenantFeature: 'audit_logs.redo_tenant',\n unavailableMessage: 'Redo target not available',\n })\n const contextRecord = log.contextJson && typeof log.contextJson === 'object' ? (log.contextJson as Record<string, unknown>) : null\n const cacheAliasesRaw = Array.isArray(contextRecord?.cacheAliases as unknown[])\n ? (contextRecord!.cacheAliases as unknown[])\n : []\n const cacheAliases = cacheAliasesRaw\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0)\n .map((value) => value.trim())\n const metadataContext: Record<string, unknown> = {\n historyAction: 'redo',\n sourceLogId: log.id,\n sourceCommandId: log.commandId,\n }\n if (cacheAliases.length) metadataContext.cacheAliases = cacheAliases\n const metadata: CommandLogMetadata = {\n tenantId: log.tenantId,\n organizationId: log.organizationId,\n actorUserId: auth.sub,\n actionLabel: log.actionLabel,\n resourceKind: log.resourceKind,\n resourceId: log.resourceId,\n context: metadataContext,\n }\n const resolvedInput = resolveRedoInput(log.commandPayload, log)\n if (!resolvedInput) {\n return NextResponse.json({ error: 'Redo data unavailable for this action' }, { status: 400 })\n }\n const commandInput = resolvedInput\n const { logEntry, replaySourceFinalized } = await commandBus.execute(log.commandId, {\n input: commandInput,\n ctx,\n metadata,\n redoLogEntry: log,\n })\n if (replaySourceFinalized !== true) await logs.markRedone(log.id)\n const actionLog = asActionLog(logEntry)\n const response = NextResponse.json({\n ok: true,\n logId: actionLog?.id ?? null,\n undoToken: actionLog?.undoToken ?? null,\n })\n if (actionLog?.undoToken && actionLog.id) {\n const createdAt = actionLog.createdAt instanceof Date\n ? actionLog.createdAt.toISOString()\n : (typeof actionLog.createdAt === 'string' ? actionLog.createdAt : new Date().toISOString())\n response.headers.set('x-om-operation', serializeOperationMetadata({\n id: actionLog.id,\n undoToken: actionLog.undoToken,\n commandId: actionLog.commandId ?? log.commandId,\n actionLabel: actionLog.actionLabel ?? log.actionLabel ?? null,\n resourceKind: typeof actionLog.resourceKind === 'string' ? actionLog.resourceKind : log.resourceKind ?? null,\n resourceId: typeof actionLog.resourceId === 'string' ? actionLog.resourceId : log.resourceId ?? null,\n executedAt: createdAt,\n }))\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n logger.warn('Redo rejected', { err })\n const { translate } = await resolveTranslations()\n return NextResponse.json(translateCrudErrorBody(err.body, translate), { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n logger.error('Redo failed', { err })\n const { translate } = await resolveTranslations()\n return NextResponse.json({ error: translate('audit_logs.error.redo_failed', 'Redo failed') }, { status: 400 })\n }\n}\n\nasync function createRuntimeContext(container: AwilixContainer, auth: AuthContext, request: Request): Promise<CommandRuntimeContext> {\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n return {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope.selectedId,\n organizationIds: scope.filterIds,\n request,\n }\n}\n\nfunction asActionLog(entry: unknown): ActionLog | null {\n if (!entry || typeof entry !== 'object') return null\n if (typeof (entry as { id?: unknown }).id !== 'string') return null\n return entry as ActionLog\n}\n\nfunction resolveRedoInput(payload: unknown, log: ActionLog): unknown | null {\n if (payload && typeof payload === 'object' && !Array.isArray(payload) && '__redoInput' in payload) {\n const envelope = payload as { __redoInput?: unknown }\n return envelope.__redoInput ?? {}\n }\n const updateFallback = deriveUpdateInput(log)\n if (updateFallback) return updateFallback\n return null\n}\n\nfunction deriveUpdateInput(log: ActionLog): Record<string, unknown> | null {\n if (!log.commandId.endsWith('.update')) return null\n if (!log.resourceId) return null\n const changes = log.changesJson\n if (!changes || typeof changes !== 'object' || Array.isArray(changes)) return { id: log.resourceId }\n const payload: Record<string, unknown> = { id: log.resourceId }\n for (const [key, value] of Object.entries(changes)) {\n if (value && typeof value === 'object' && !Array.isArray(value) && 'to' in value) {\n payload[key] = (value as Record<string, unknown>).to\n } else {\n payload[key] = value\n }\n }\n return payload\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Redo a previously undone action',\n description: 'Replays the command associated with a recently undone action, reapplying the change and issuing a fresh undo token.',\n methods: {\n POST: {\n summary: 'Redo by action log id',\n description:\n 'Redoes the latest undone command owned by the caller. Requires the action to still be eligible for redo within tenant and organization scope.',\n requestBody: {\n contentType: 'application/json',\n schema: redoRequestSchema,\n },\n responses: [\n { status: 200, description: 'Redo executed successfully', schema: redoResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Log not eligible for redo', schema: errorSchema },\n { status: 401, description: 'Authentication required', schema: errorSchema },\n { status: 403, description: 'Redo blocked by scope checks', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["logEntry", "metadata", "translate"]
7
7
  }
@@ -125,10 +125,7 @@ const CustomerTodosWidget = ({
125
125
  /* @__PURE__ */ jsx("span", { children: item.entity.displayName ?? t("customers.widgets.common.unknown") }),
126
126
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: createdLabel || t("customers.widgets.common.unknownDate") })
127
127
  ] }),
128
- /* @__PURE__ */ jsxs("div", { className: "mt-1 space-y-0.5", children: [
129
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: item.todoTitle ?? t("customers.widgets.todos.untitled") }),
130
- item.todoSource ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: item.todoSource }) : null
131
- ] }),
128
+ /* @__PURE__ */ jsx("div", { className: "mt-1 space-y-0.5", children: /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: item.todoTitle ?? t("customers.widgets.todos.untitled") }) }),
132
129
  /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap gap-3 text-xs", children: [
133
130
  href ? /* @__PURE__ */ jsx(Link, { className: "text-primary hover:underline", href, children: t("customers.widgets.common.viewRecord") }) : null,
134
131
  taskHref ? /* @__PURE__ */ jsx(Link, { className: "text-primary hover:underline", href: taskHref, children: t("customers.workPlan.customerTodos.table.actions.openTask") }) : null
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/customers/widgets/dashboard/customer-todos/widget.client.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { DEFAULT_SETTINGS, hydrateCustomerTodoSettings, type CustomerTodoWidgetSettings } from './config'\nimport { resolveExampleIntegrationHref } from '../../../lib/interactionCompatibility'\nimport { resolveTodoHref } from '../../../components/detail/utils'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\ntype TodoLinkSummary = {\n id: string\n todoId: string\n todoSource: string\n todoTitle: string | null\n createdAt: string\n _integrations?: {\n example?: {\n href?: string | null\n }\n [key: string]: unknown\n }\n entity: {\n id: string | null\n displayName: string | null\n kind: string | null\n }\n}\n\nasync function loadTodos(settings: CustomerTodoWidgetSettings): Promise<TodoLinkSummary[]> {\n const params = new URLSearchParams({\n limit: String(settings.pageSize),\n })\n const call = await apiCall<{ items?: unknown[]; error?: string }>(\n `/api/customers/dashboard/widgets/customer-todos?${params.toString()}`,\n )\n if (!call.ok) {\n const message =\n typeof (call.result as Record<string, unknown> | null)?.error === 'string'\n ? ((call.result as Record<string, unknown>).error as string)\n : `Request failed with status ${call.status}`\n throw new Error(message)\n }\n const payload = call.result ?? {}\n const rawItems = Array.isArray((payload as { items?: unknown }).items)\n ? ((payload as { items: unknown[] }).items)\n : []\n return rawItems\n .map((item): TodoLinkSummary | null => {\n if (!item || typeof item !== 'object') return null\n const data = item as any\n const entity = data.entity ?? {}\n return {\n id: typeof data.id === 'string' ? data.id : null,\n todoId: typeof data.todoId === 'string' ? data.todoId : '',\n todoSource: typeof data.todoSource === 'string' ? data.todoSource : '',\n todoTitle: typeof data.todoTitle === 'string' ? data.todoTitle : null,\n createdAt: typeof data.createdAt === 'string' ? data.createdAt : '',\n _integrations: data._integrations && typeof data._integrations === 'object'\n ? (data._integrations as TodoLinkSummary['_integrations'])\n : undefined,\n entity: {\n id: typeof entity.id === 'string' ? entity.id : null,\n displayName: typeof entity.displayName === 'string' ? entity.displayName : null,\n kind: typeof entity.kind === 'string' ? entity.kind : null,\n },\n }\n })\n .filter((item): item is TodoLinkSummary => !!item && !!item.id)\n}\n\nfunction formatDate(value: string | null, locale?: string): string {\n if (!value) return ''\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n return date.toLocaleString(locale ?? undefined)\n}\n\nfunction resolveDetailHref(entity: { id: string | null; kind: string | null } | null | undefined): string | null {\n if (!entity?.id) return null\n if (entity.kind === 'company') return `/backend/customers/companies-v2/${encodeURIComponent(entity.id)}`\n if (entity.kind === 'person') return `/backend/customers/people-v2/${encodeURIComponent(entity.id)}`\n return `/backend/customers/people-v2/${encodeURIComponent(entity.id)}`\n}\n\nconst CustomerTodosWidget: React.FC<DashboardWidgetComponentProps<CustomerTodoWidgetSettings>> = ({\n mode,\n settings = DEFAULT_SETTINGS,\n onSettingsChange,\n refreshToken,\n onRefreshStateChange,\n}) => {\n const t = useT()\n const hydrated = React.useMemo(() => hydrateCustomerTodoSettings(settings), [settings])\n const [items, setItems] = React.useState<TodoLinkSummary[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [locale, setLocale] = React.useState<string | undefined>(undefined)\n\n React.useEffect(() => {\n if (typeof navigator !== 'undefined') {\n setLocale(navigator.language)\n }\n }, [])\n\n const refresh = React.useCallback(async () => {\n onRefreshStateChange?.(true)\n setLoading(true)\n setError(null)\n try {\n const data = await loadTodos(hydrated)\n setItems(data)\n } catch (err) {\n logger.error('Failed to load customer todos widget data', { err })\n setError(t('customers.widgets.todos.error'))\n } finally {\n setLoading(false)\n onRefreshStateChange?.(false)\n }\n }, [hydrated, onRefreshStateChange, t])\n\n React.useEffect(() => {\n refresh().catch(() => {})\n }, [refresh, refreshToken])\n\n if (mode === 'settings') {\n return (\n <div className=\"space-y-4 text-sm\">\n <div className=\"space-y-1.5\">\n <label htmlFor=\"customer-todos-page-size\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\n {t('customers.widgets.todos.settings.pageSize')}\n </label>\n <Input\n id=\"customer-todos-page-size\"\n type=\"number\"\n min={1}\n max={20}\n className=\"w-24\"\n value={hydrated.pageSize}\n onChange={(event) => {\n const next = Number(event.target.value)\n onSettingsChange({ ...hydrated, pageSize: Number.isFinite(next) ? next : hydrated.pageSize })\n }}\n />\n </div>\n <p className=\"text-xs text-muted-foreground\">{t('customers.widgets.todos.settings.help')}</p>\n </div>\n )\n }\n\n return (\n <div className=\"space-y-4\">\n {error ? (\n <p className=\"text-sm text-destructive\">{error}</p>\n ) : loading ? (\n <div className=\"flex h-32 items-center justify-center\">\n <Spinner className=\"h-6 w-6 text-muted-foreground\" />\n </div>\n ) : items.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">{t('customers.widgets.todos.empty')}</p>\n ) : (\n <ul className=\"space-y-3\">\n {items.map((item) => {\n const createdLabel = formatDate(item.createdAt, locale)\n const href = resolveDetailHref(item.entity)\n const exampleHref = resolveExampleIntegrationHref(item)\n const taskHref = exampleHref ?? resolveTodoHref(item.todoSource, item.todoId)\n return (\n <li key={item.id} className=\"rounded-md border p-3\">\n <div className=\"flex items-start justify-between gap-3 text-sm font-medium\">\n <span>{item.entity.displayName ?? t('customers.widgets.common.unknown')}</span>\n <span className=\"text-xs text-muted-foreground\">{createdLabel || t('customers.widgets.common.unknownDate')}</span>\n </div>\n <div className=\"mt-1 space-y-0.5\">\n <p className=\"text-sm font-medium text-foreground\">\n {item.todoTitle ?? t('customers.widgets.todos.untitled')}\n </p>\n {item.todoSource ? (\n <p className=\"text-xs text-muted-foreground\">{item.todoSource}</p>\n ) : null}\n </div>\n <div className=\"mt-2 flex flex-wrap gap-3 text-xs\">\n {href ? (\n <Link className=\"text-primary hover:underline\" href={href}>\n {t('customers.widgets.common.viewRecord')}\n </Link>\n ) : null}\n {taskHref ? (\n <Link className=\"text-primary hover:underline\" href={taskHref}>\n {t('customers.workPlan.customerTodos.table.actions.openTask')}\n </Link>\n ) : null}\n </div>\n </li>\n )\n })}\n </ul>\n )}\n </div>\n )\n}\n\nexport default CustomerTodosWidget\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { DEFAULT_SETTINGS, hydrateCustomerTodoSettings, type CustomerTodoWidgetSettings } from './config'\nimport { resolveExampleIntegrationHref } from '../../../lib/interactionCompatibility'\nimport { resolveTodoHref } from '../../../components/detail/utils'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\ntype TodoLinkSummary = {\n id: string\n todoId: string\n todoSource: string\n todoTitle: string | null\n createdAt: string\n _integrations?: {\n example?: {\n href?: string | null\n }\n [key: string]: unknown\n }\n entity: {\n id: string | null\n displayName: string | null\n kind: string | null\n }\n}\n\nasync function loadTodos(settings: CustomerTodoWidgetSettings): Promise<TodoLinkSummary[]> {\n const params = new URLSearchParams({\n limit: String(settings.pageSize),\n })\n const call = await apiCall<{ items?: unknown[]; error?: string }>(\n `/api/customers/dashboard/widgets/customer-todos?${params.toString()}`,\n )\n if (!call.ok) {\n const message =\n typeof (call.result as Record<string, unknown> | null)?.error === 'string'\n ? ((call.result as Record<string, unknown>).error as string)\n : `Request failed with status ${call.status}`\n throw new Error(message)\n }\n const payload = call.result ?? {}\n const rawItems = Array.isArray((payload as { items?: unknown }).items)\n ? ((payload as { items: unknown[] }).items)\n : []\n return rawItems\n .map((item): TodoLinkSummary | null => {\n if (!item || typeof item !== 'object') return null\n const data = item as any\n const entity = data.entity ?? {}\n return {\n id: typeof data.id === 'string' ? data.id : null,\n todoId: typeof data.todoId === 'string' ? data.todoId : '',\n todoSource: typeof data.todoSource === 'string' ? data.todoSource : '',\n todoTitle: typeof data.todoTitle === 'string' ? data.todoTitle : null,\n createdAt: typeof data.createdAt === 'string' ? data.createdAt : '',\n _integrations: data._integrations && typeof data._integrations === 'object'\n ? (data._integrations as TodoLinkSummary['_integrations'])\n : undefined,\n entity: {\n id: typeof entity.id === 'string' ? entity.id : null,\n displayName: typeof entity.displayName === 'string' ? entity.displayName : null,\n kind: typeof entity.kind === 'string' ? entity.kind : null,\n },\n }\n })\n .filter((item): item is TodoLinkSummary => !!item && !!item.id)\n}\n\nfunction formatDate(value: string | null, locale?: string): string {\n if (!value) return ''\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n return date.toLocaleString(locale ?? undefined)\n}\n\nfunction resolveDetailHref(entity: { id: string | null; kind: string | null } | null | undefined): string | null {\n if (!entity?.id) return null\n if (entity.kind === 'company') return `/backend/customers/companies-v2/${encodeURIComponent(entity.id)}`\n if (entity.kind === 'person') return `/backend/customers/people-v2/${encodeURIComponent(entity.id)}`\n return `/backend/customers/people-v2/${encodeURIComponent(entity.id)}`\n}\n\nconst CustomerTodosWidget: React.FC<DashboardWidgetComponentProps<CustomerTodoWidgetSettings>> = ({\n mode,\n settings = DEFAULT_SETTINGS,\n onSettingsChange,\n refreshToken,\n onRefreshStateChange,\n}) => {\n const t = useT()\n const hydrated = React.useMemo(() => hydrateCustomerTodoSettings(settings), [settings])\n const [items, setItems] = React.useState<TodoLinkSummary[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [locale, setLocale] = React.useState<string | undefined>(undefined)\n\n React.useEffect(() => {\n if (typeof navigator !== 'undefined') {\n setLocale(navigator.language)\n }\n }, [])\n\n const refresh = React.useCallback(async () => {\n onRefreshStateChange?.(true)\n setLoading(true)\n setError(null)\n try {\n const data = await loadTodos(hydrated)\n setItems(data)\n } catch (err) {\n logger.error('Failed to load customer todos widget data', { err })\n setError(t('customers.widgets.todos.error'))\n } finally {\n setLoading(false)\n onRefreshStateChange?.(false)\n }\n }, [hydrated, onRefreshStateChange, t])\n\n React.useEffect(() => {\n refresh().catch(() => {})\n }, [refresh, refreshToken])\n\n if (mode === 'settings') {\n return (\n <div className=\"space-y-4 text-sm\">\n <div className=\"space-y-1.5\">\n <label htmlFor=\"customer-todos-page-size\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\n {t('customers.widgets.todos.settings.pageSize')}\n </label>\n <Input\n id=\"customer-todos-page-size\"\n type=\"number\"\n min={1}\n max={20}\n className=\"w-24\"\n value={hydrated.pageSize}\n onChange={(event) => {\n const next = Number(event.target.value)\n onSettingsChange({ ...hydrated, pageSize: Number.isFinite(next) ? next : hydrated.pageSize })\n }}\n />\n </div>\n <p className=\"text-xs text-muted-foreground\">{t('customers.widgets.todos.settings.help')}</p>\n </div>\n )\n }\n\n return (\n <div className=\"space-y-4\">\n {error ? (\n <p className=\"text-sm text-destructive\">{error}</p>\n ) : loading ? (\n <div className=\"flex h-32 items-center justify-center\">\n <Spinner className=\"h-6 w-6 text-muted-foreground\" />\n </div>\n ) : items.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">{t('customers.widgets.todos.empty')}</p>\n ) : (\n <ul className=\"space-y-3\">\n {items.map((item) => {\n const createdLabel = formatDate(item.createdAt, locale)\n const href = resolveDetailHref(item.entity)\n const exampleHref = resolveExampleIntegrationHref(item)\n const taskHref = exampleHref ?? resolveTodoHref(item.todoSource, item.todoId)\n return (\n <li key={item.id} className=\"rounded-md border p-3\">\n <div className=\"flex items-start justify-between gap-3 text-sm font-medium\">\n <span>{item.entity.displayName ?? t('customers.widgets.common.unknown')}</span>\n <span className=\"text-xs text-muted-foreground\">{createdLabel || t('customers.widgets.common.unknownDate')}</span>\n </div>\n <div className=\"mt-1 space-y-0.5\">\n <p className=\"text-sm font-medium text-foreground\">\n {item.todoTitle ?? t('customers.widgets.todos.untitled')}\n </p>\n </div>\n <div className=\"mt-2 flex flex-wrap gap-3 text-xs\">\n {href ? (\n <Link className=\"text-primary hover:underline\" href={href}>\n {t('customers.widgets.common.viewRecord')}\n </Link>\n ) : null}\n {taskHref ? (\n <Link className=\"text-primary hover:underline\" href={taskHref}>\n {t('customers.workPlan.customerTodos.table.actions.openTask')}\n </Link>\n ) : null}\n </div>\n </li>\n )\n })}\n </ul>\n )}\n </div>\n )\n}\n\nexport default CustomerTodosWidget\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -468,6 +468,10 @@ function SalesDocumentsTable({ kind }) {
468
468
  setSearch(value);
469
469
  setPage(1);
470
470
  }, []);
471
+ const handleSortingChange = React.useCallback((nextSorting) => {
472
+ setSorting(nextSorting);
473
+ setPage(1);
474
+ }, []);
471
475
  const handleRefresh = React.useCallback(() => {
472
476
  setReloadToken((token) => token + 1);
473
477
  }, []);
@@ -590,8 +594,10 @@ function SalesDocumentsTable({ kind }) {
590
594
  actions: /* @__PURE__ */ jsx(Button, { asChild: true, children: /* @__PURE__ */ jsx(Link, { href: `/backend/sales/documents/create?kind=${kind}`, children: t("sales.documents.create.title", "Create sales document") }) }),
591
595
  columns,
592
596
  data: rows,
597
+ sortable: true,
598
+ manualSorting: true,
593
599
  sorting,
594
- onSortingChange: setSorting,
600
+ onSortingChange: handleSortingChange,
595
601
  isLoading,
596
602
  searchValue: search,
597
603
  onSearchChange: handleSearchChange,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/sales/components/documents/SalesDocumentsTable.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/sales/extension-points'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\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 { DataTable, type DataTableExportFormat, withDataTableNamespaces } 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 { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { buildCrudExportUrl, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\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 { E } from '#generated/entities.ids.generated'\nimport {\n DictionaryValue,\n type DictionaryMap,\n createDictionaryMap,\n normalizeDictionaryEntries,\n} from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { SALES_DOCUMENT_NUMBER_COLUMN_META } from './salesDocumentsColumns'\nimport { useSalesChannelsEnabled } from '../useSalesChannelsEnabled'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('sales')\n\ntype SalesDocumentKind = 'order' | 'quote'\n\ntype FilterOption = { value: string; label: string; description?: string | null }\n\ntype CustomerSnapshot = {\n customer?: {\n displayName?: string | null\n primaryEmail?: string | null\n } | null\n contact?: {\n firstName?: string | null\n lastName?: string | null\n preferredName?: string | null\n } | null\n}\n\ntype ApiDocument = {\n id: string\n orderNumber?: string | null\n quoteNumber?: string | null\n status?: string | null\n customerEntityId?: string | null\n customerSnapshot?: Record<string, unknown> | null\n channelId?: string | null\n channelName?: string | null\n lineItemCount?: number | null\n grandTotalNetAmount?: number | null\n grandTotalGrossAmount?: number | null\n currencyCode?: string | null\n placedAt?: string | null\n validUntil?: string | null\n validFrom?: string | null\n createdAt?: string | null\n updatedAt?: string | null\n}\n\ntype DocumentsResponse = {\n items?: Array<Record<string, unknown>>\n total?: number\n totalPages?: number\n totalIsCapped?: boolean\n}\n\ntype SalesDocumentRow = {\n id: string\n number: string\n status?: string | null\n customerName?: string | null\n customerEmail?: string | null\n channelId?: string | null\n channelName?: string | null\n lineItemCount?: number | null\n totalNet?: number | null\n totalGross?: number | null\n currency?: string | null\n date?: string | null\n updatedAt?: string | null\n}\n\nconst PAGE_SIZE = 20\n\n// Not a uuid on purpose: the API drops non-uuid entries from channelIds, so a leak of this sentinel\n// into the id list narrows the filter instead of erroring.\nconst UNASSIGNED_CHANNEL_VALUE = '__unassigned__'\n\n// Perspectives persist filterValues, so snapshots saved while this filter was a single-value select\n// hold a bare string. Without this the multi-select would read it as an unknown shape and silently\n// clear the user's saved filter.\nfunction normalizeChannelSelection(value: unknown): string[] {\n const raw = Array.isArray(value) ? value : typeof value === 'string' ? [value] : []\n return raw\n .map((entry) => (typeof entry === 'string' ? entry.trim() : String(entry || '').trim()))\n .filter((entry) => entry.length > 0)\n}\n\nfunction resolveCustomerName(snapshot: CustomerSnapshot | null | undefined, fallback?: string | null) {\n if (!snapshot) return fallback ?? null\n const base = snapshot.customer?.displayName ?? null\n if (base) return base\n const contact = snapshot.contact\n if (contact) {\n const parts = [contact.preferredName, contact.firstName, contact.lastName].filter(\n (part) => part && part.trim().length\n ) as string[]\n if (parts.length) return parts.join(' ')\n }\n return fallback ?? null\n}\n\nfunction resolveCustomerEmail(snapshot: CustomerSnapshot | null | undefined) {\n if (!snapshot) return null\n if (snapshot.customer?.primaryEmail) return snapshot.customer.primaryEmail\n return null\n}\n\nfunction toNumber(value: unknown): number | null {\n if (typeof value === 'number') return Number.isNaN(value) ? null : value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n return Number.isNaN(parsed) ? null : parsed\n }\n return null\n}\n\nfunction formatCurrency(amount: number | null | undefined, currency: string | null | undefined, fallback = '\u2014') {\n if (amount == null || Number.isNaN(amount)) return fallback\n try {\n if (currency && currency.trim().length) {\n const formatter = new Intl.NumberFormat(undefined, { style: 'currency', currency })\n return formatter.format(amount)\n }\n return new Intl.NumberFormat(undefined, { style: 'decimal', maximumFractionDigits: 2 }).format(amount)\n } catch {\n return String(amount)\n }\n}\n\nfunction mergeOptions(existing: FilterOption[], next: FilterOption[]): FilterOption[] {\n const map = new Map<string, FilterOption>()\n existing.forEach((opt) => map.set(opt.value, opt))\n next.forEach((opt) => map.set(opt.value, opt))\n return Array.from(map.values()).sort((a, b) => a.label.localeCompare(b.label))\n}\n\nfunction normalizeNumberInput(value: unknown): number | null {\n if (typeof value === 'number') return Number.isNaN(value) ? null : value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n return Number.isNaN(parsed) ? null : parsed\n }\n return null\n}\n\nexport function SalesDocumentsTable({ kind }: { kind: SalesDocumentKind }) {\n const t = useT()\n const { enabled: channelsEnabled } = useSalesChannelsEnabled()\n const router = useRouter()\n const scopeVersion = useOrganizationScopeVersion()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [rows, setRows] = React.useState<SalesDocumentRow[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [totalIsCapped, setTotalIsCapped] = React.useState(false)\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'createdAt', desc: true }])\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [isLoading, setLoading] = React.useState(false)\n const [cacheStatus, setCacheStatus] = React.useState<'hit' | 'miss' | null>(null)\n const [reloadToken, setReloadToken] = React.useState(0)\n const [channelOptions, setChannelOptions] = React.useState<FilterOption[]>([])\n const [tagOptions, setTagOptions] = React.useState<FilterOption[]>([])\n const [customerOptions, setCustomerOptions] = React.useState<FilterOption[]>([])\n const [statusMap, setStatusMap] = React.useState<DictionaryMap>({})\n\n const resource = kind === 'order' ? 'orders' : 'quotes'\n const entityId = kind === 'order' ? E.sales.sales_order : E.sales.sales_quote\n const title = kind === 'order'\n ? t('sales.documents.list.ordersTitle', 'Sales orders')\n : t('sales.documents.list.quotesTitle', 'Sales quotes')\n const subtitle = t(\n 'sales.documents.list.subtitle',\n 'Review documents with customer context, totals, and channels.'\n )\n\n const fetchChannelOptionsBy = React.useCallback(async (params: URLSearchParams): Promise<FilterOption[]> => {\n try {\n const call = await apiCall<{ items?: unknown[] }>(`/api/sales/channels?${params.toString()}`)\n if (!call.ok) return []\n const items = Array.isArray(call.result?.items) ? call.result!.items : []\n return items\n .map((item: any): FilterOption | null => {\n const id = typeof item?.id === 'string' ? item.id : null\n const name = typeof item?.name === 'string' ? item.name : null\n if (!id || !name) return null\n return { value: id, label: name }\n })\n .filter((opt): opt is FilterOption => opt !== null)\n } catch {\n return []\n }\n }, [])\n\n const fetchChannelOptions = React.useCallback(\n (query?: string) => {\n const params = new URLSearchParams({ page: '1', pageSize: '50' })\n if (query && query.trim()) params.set('search', query.trim())\n return fetchChannelOptionsBy(params)\n },\n [fetchChannelOptionsBy]\n )\n\n // A selected channel outside the first options page \u2014 restored from a perspective, or simply the\n // 51st channel \u2014 would otherwise render as a bare uuid in the filter chip and the selected tag,\n // which is the same defect this change removes from the column. The route supports `ids=`, so\n // fetch exactly the selected ones and merge them in. See\n // `.ai/lessons/async-edit-selects-must-be-hydrated-as-value-plus.md`.\n const seedChannelOptionsForIds = React.useCallback(\n async (ids: string[]) => {\n const missing = ids.filter((id) => id !== UNASSIGNED_CHANNEL_VALUE)\n if (!missing.length) return\n const params = new URLSearchParams({ page: '1', pageSize: String(missing.length) })\n params.set('ids', missing.join(','))\n const opts = await fetchChannelOptionsBy(params)\n if (opts.length) setChannelOptions((prev) => mergeOptions(prev, opts))\n },\n [fetchChannelOptionsBy]\n )\n\n const fetchTagOptions = React.useCallback(async (query?: string): Promise<FilterOption[]> => {\n const params = new URLSearchParams({ page: '1', pageSize: '50' })\n if (query && query.trim()) params.set('search', query.trim())\n try {\n const call = await apiCall<{ items?: unknown[] }>(`/api/sales/tags?${params.toString()}`)\n if (!call.ok) return []\n const items = Array.isArray(call.result?.items) ? call.result!.items : []\n return items\n .map((item: any): FilterOption | null => {\n const id = typeof item?.id === 'string' ? item.id : null\n const label = typeof item?.label === 'string' ? item.label : null\n if (!id || !label) return null\n const description = typeof item?.description === 'string' ? item.description : null\n return { value: id, label, description }\n })\n .filter((opt): opt is FilterOption => opt !== null)\n } catch {\n return []\n }\n }, [])\n\n const fetchCustomerOptions = React.useCallback(async (query?: string): Promise<FilterOption[]> => {\n const params = new URLSearchParams({ page: '1', pageSize: '20' })\n if (query && query.trim().length) params.set('search', query.trim())\n try {\n const [people, companies] = await Promise.all([\n apiCall<{ items?: unknown[] }>(`/api/customers/people?${params.toString()}`),\n apiCall<{ items?: unknown[] }>(`/api/customers/companies?${params.toString()}`),\n ])\n const peopleItems = Array.isArray(people.result?.items) ? people.result?.items ?? [] : []\n const companyItems = Array.isArray(companies.result?.items) ? companies.result?.items ?? [] : []\n const parseOption = (item: any, kind: 'person' | 'company'): FilterOption | null => {\n const id = typeof item?.id === 'string' ? item.id : null\n if (!id) return null\n const name =\n typeof item?.display_name === 'string' && item.display_name.trim().length\n ? item.display_name\n : typeof item?.name === 'string' && item.name.trim().length\n ? item.name\n : id\n const email =\n typeof item?.primary_email === 'string' && item.primary_email.trim().length\n ? item.primary_email.trim()\n : null\n const label = email ? `${name} (${email})` : name\n return { value: id, label }\n }\n const options = [...peopleItems.map((i) => parseOption(i, 'person')), ...companyItems.map((i) => parseOption(i, 'company'))]\n .filter((opt): opt is FilterOption => !!opt)\n return options\n } catch {\n return []\n }\n }, [])\n\n const loadStatusMap = React.useCallback(async () => {\n try {\n const params = new URLSearchParams({ page: '1', pageSize: '100' })\n const response = await apiCall<{ items?: Array<Record<string, unknown>> }>(\n `/api/sales/order-statuses?${params.toString()}`,\n undefined,\n { fallback: { items: [] } }\n )\n const entries = normalizeDictionaryEntries(response.result?.items ?? [], { sort: false })\n setStatusMap(createDictionaryMap(entries))\n } catch (err) {\n logger.error('sales.documents.statuses.load', { err })\n setStatusMap({})\n }\n }, [])\n\n const loadChannelOptions = React.useCallback(\n async (query?: string) => {\n const opts = await fetchChannelOptions(query)\n if (opts.length) setChannelOptions((prev) => mergeOptions(prev, opts))\n return opts\n },\n [fetchChannelOptions]\n )\n\n const unassignedChannelOption = React.useMemo<FilterOption>(\n () => ({\n value: UNASSIGNED_CHANNEL_VALUE,\n label: t('sales.documents.list.filters.channelUnassigned', '(No channel)'),\n }),\n [t]\n )\n\n // The sentinel is offered alongside the real channels so \"these channels or unassigned\" is one\n // selection. It is stripped back out when the query is built \u2014 the API takes it as a separate\n // channelIdsEmpty flag, not as an id.\n const channelFilterOptions = React.useMemo<FilterOption[]>(\n () => [unassignedChannelOption, ...channelOptions],\n [channelOptions, unassignedChannelOption]\n )\n\n const loadChannelFilterOptions = React.useCallback(\n async (query?: string) => {\n const opts = await loadChannelOptions(query)\n // Only offer the sentinel when the user is browsing rather than searching \u2014 typing \"web\"\n // should not suggest \"(No channel)\".\n return query && query.trim() ? opts : [unassignedChannelOption, ...opts]\n },\n [loadChannelOptions, unassignedChannelOption]\n )\n\n const loadTagOptions = React.useCallback(\n async (query?: string) => {\n const opts = await fetchTagOptions(query)\n if (opts.length) setTagOptions((prev) => mergeOptions(prev, opts))\n return opts\n },\n [fetchTagOptions]\n )\n\n const loadCustomerOptions = React.useCallback(\n async (query?: string) => {\n const opts = await fetchCustomerOptions(query)\n if (opts.length) setCustomerOptions((prev) => mergeOptions(prev, opts))\n return opts\n },\n [fetchCustomerOptions]\n )\n\n React.useEffect(() => {\n if (channelsEnabled) loadChannelOptions().catch(() => {})\n loadTagOptions().catch(() => {})\n loadCustomerOptions().catch(() => {})\n loadStatusMap().catch(() => setStatusMap({}))\n }, [channelsEnabled, loadChannelOptions, loadCustomerOptions, loadStatusMap, loadTagOptions, scopeVersion])\n\n const filters = React.useMemo<FilterDef[]>(() => [\n ...(channelsEnabled ? [{\n id: 'channelId',\n label: t('sales.documents.list.filters.channel', 'Channel'),\n type: 'tags',\n options: channelFilterOptions,\n loadOptions: loadChannelFilterOptions,\n placeholder: t('sales.documents.list.filters.channelPlaceholder', 'Search channels'),\n formatValue: (val: string) =>\n val === UNASSIGNED_CHANNEL_VALUE\n ? t('sales.documents.list.filters.channelUnassigned', '(No channel)')\n : channelOptions.find((opt) => opt.value === val)?.label ?? val,\n } satisfies FilterDef] : []),\n {\n id: 'date',\n label: t('sales.documents.list.filters.date', 'Date'),\n type: 'dateRange',\n },\n {\n id: 'lineItemCountMin',\n label: t('sales.documents.list.filters.itemsMin', 'Min items'),\n type: 'text',\n },\n {\n id: 'lineItemCountMax',\n label: t('sales.documents.list.filters.itemsMax', 'Max items'),\n type: 'text',\n },\n {\n id: 'totalNetMin',\n label: t('sales.documents.list.filters.totalNetMin', 'Min total (net)'),\n type: 'text',\n },\n {\n id: 'totalNetMax',\n label: t('sales.documents.list.filters.totalNetMax', 'Max total (net)'),\n type: 'text',\n },\n {\n id: 'totalGrossMin',\n label: t('sales.documents.list.filters.totalGrossMin', 'Min total (gross)'),\n type: 'text',\n },\n {\n id: 'totalGrossMax',\n label: t('sales.documents.list.filters.totalGrossMax', 'Max total (gross)'),\n type: 'text',\n },\n {\n id: 'customerId',\n label: t('sales.documents.list.filters.customer', 'Customer'),\n type: 'tags',\n options: customerOptions,\n loadOptions: loadCustomerOptions,\n placeholder: t('sales.documents.list.filters.customerPlaceholder', 'Search customers'),\n formatValue: (val: string) => {\n const match = customerOptions.find((opt) => opt.value === val)\n return match?.label ?? val\n },\n },\n {\n id: 'tagIds',\n label: t('sales.documents.list.filters.tags', 'Tags'),\n type: 'tags',\n options: tagOptions,\n loadOptions: loadTagOptions,\n formatValue: (val: string) => tagOptions.find((o) => o.value === val)?.label ?? val,\n formatDescription: (val: string) => tagOptions.find((o) => o.value === val)?.description ?? null,\n },\n ], [channelsEnabled, channelFilterOptions, channelOptions, loadChannelFilterOptions, customerOptions, loadCustomerOptions, loadTagOptions, tagOptions, t])\n\n const queryParams = React.useMemo(() => {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', String(PAGE_SIZE))\n if (search.trim()) params.set('search', search.trim())\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 const channelSelection = normalizeChannelSelection(filterValues.channelId)\n const channelIds = channelSelection.filter((value) => value !== UNASSIGNED_CHANNEL_VALUE)\n if (channelIds.length > 0) params.set('channelIds', channelIds.join(','))\n if (channelSelection.includes(UNASSIGNED_CHANNEL_VALUE)) params.set('channelIdsEmpty', 'true')\n const customerIds = Array.isArray(filterValues.customerId)\n ? filterValues.customerId\n .map((value) => (typeof value === 'string' ? value.trim() : String(value || '').trim()))\n .filter((value) => value.length > 0)\n : []\n if (customerIds.length > 0) {\n params.set('customerId', customerIds[0])\n }\n const date = filterValues.date\n if (date && typeof date === 'object') {\n if (date.from) params.set('dateFrom', date.from)\n if (date.to) params.set('dateTo', date.to)\n }\n const numberFilters: Array<[keyof FilterValues, string]> = [\n ['lineItemCountMin', 'lineItemCountMin'],\n ['lineItemCountMax', 'lineItemCountMax'],\n ['totalNetMin', 'totalNetMin'],\n ['totalNetMax', 'totalNetMax'],\n ['totalGrossMin', 'totalGrossMin'],\n ['totalGrossMax', 'totalGrossMax'],\n ]\n numberFilters.forEach(([key, queryKey]) => {\n const value = normalizeNumberInput((filterValues as any)[key])\n if (value != null) params.set(queryKey, String(value))\n })\n const tagIds = Array.isArray(filterValues.tagIds)\n ? filterValues.tagIds.map((value) => (typeof value === 'string' ? value.trim() : String(value || '').trim())).filter((v) => v.length > 0)\n : []\n if (tagIds.length > 0) {\n params.set('tagIds', tagIds.join(','))\n }\n Object.entries(filterValues).forEach(([key, value]) => {\n if (!key.startsWith('cf_') || value == null) return\n if (Array.isArray(value)) {\n const normalized = value\n .map((item) => {\n if (item == null) return ''\n if (typeof item === 'string') return item.trim()\n return String(item).trim()\n })\n .filter((item) => item.length > 0)\n if (normalized.length) params.set(key, normalized.join(','))\n } else if (typeof value === 'object') {\n return\n } else if (value !== '') {\n const stringValue = typeof value === 'string' ? value.trim() : String(value)\n if (stringValue) params.set(key, stringValue)\n }\n })\n return params.toString()\n }, [filterValues, page, search, sorting])\n\n const currentParams = React.useMemo(() => Object.fromEntries(new URLSearchParams(queryParams)), [queryParams])\n\n const exportConfig = React.useMemo(() => ({\n view: {\n getUrl: (format: DataTableExportFormat) =>\n buildCrudExportUrl(`sales/${resource}`, { ...currentParams, exportScope: 'view' }, format),\n },\n full: {\n getUrl: (format: DataTableExportFormat) =>\n buildCrudExportUrl(`sales/${resource}`, { ...currentParams, exportScope: 'full', all: 'true' }, format),\n },\n }), [currentParams, resource])\n\n const mapApiDocument = React.useCallback(\n (item: Record<string, unknown>): SalesDocumentRow => {\n const doc = item as ApiDocument\n const id = typeof doc.id === 'string' ? doc.id : ''\n const number = kind === 'order'\n ? doc.orderNumber ?? (item as any)?.order_number ?? id\n : doc.quoteNumber ?? (item as any)?.quote_number ?? id\n const customerSnapshot = (doc.customerSnapshot ?? null) as CustomerSnapshot | null\n const customerName = resolveCustomerName(customerSnapshot, doc.customerEntityId ?? null)\n const customerEmail = resolveCustomerEmail(customerSnapshot)\n const totalNet = toNumber(doc.grandTotalNetAmount)\n const totalGross = toNumber(doc.grandTotalGrossAmount)\n const placedAt = doc.placedAt ?? null\n const validUntil = doc.validUntil ?? null\n const createdAt = doc.createdAt ?? null\n const date = placedAt ?? validUntil ?? createdAt ?? null\n return withDataTableNamespaces({\n id,\n number,\n status: doc.status ?? null,\n customerName,\n customerEmail,\n channelId: doc.channelId ?? null,\n channelName: doc.channelName ?? null,\n lineItemCount: doc.lineItemCount ?? null,\n totalNet,\n totalGross,\n currency: doc.currencyCode ?? null,\n date,\n updatedAt: doc.updatedAt ?? null,\n }, item)\n },\n [kind]\n )\n\n const loadDocuments = React.useCallback(async () => {\n setLoading(true)\n setCacheStatus(null)\n try {\n const call = await apiCall<DocumentsResponse>(`/api/sales/${resource}?${queryParams}`)\n if (!call.ok) {\n flash(t('sales.documents.list.errors.load', 'Failed to load documents.'), 'error')\n setRows([])\n setTotal(0)\n setTotalPages(1)\n setTotalIsCapped(false)\n return\n }\n const payload = call.result ?? {}\n const items = Array.isArray(payload.items) ? payload.items : []\n setRows(items.map((item) => mapApiDocument(item)))\n const count = typeof payload.total === 'number' ? payload.total : items.length\n setTotal(count)\n const pages = typeof payload.totalPages === 'number'\n ? payload.totalPages\n : Math.max(1, Math.ceil(count / PAGE_SIZE))\n setTotalPages(pages)\n setTotalIsCapped(payload.totalIsCapped === true)\n setCacheStatus(call.cacheStatus ?? null)\n } catch (err) {\n logger.error('sales.documents.list', { err })\n flash(t('sales.documents.list.errors.load', 'Failed to load documents.'), 'error')\n } finally {\n setLoading(false)\n }\n }, [mapApiDocument, queryParams, resource, t])\n\n React.useEffect(() => {\n void loadDocuments()\n }, [loadDocuments, reloadToken, scopeVersion])\n\n // Normalize at the hydration boundary, not only where the query string is built: a perspective\n // saved while this filter was a single-value select restores `channelId` as a bare string, and\n // the `tags` control renders nothing for a non-array value. Storing the normalized shape keeps\n // the control, the active-filter chip and the query builder all reading one shape \u2014 otherwise the\n // list is filtered while the field looks empty, and the next channel the user picks replaces the\n // restored one instead of joining it.\n const handleFiltersApply = React.useCallback((values: FilterValues) => {\n if (values.channelId === undefined) {\n setFilterValues(values)\n } else {\n const channelSelection = normalizeChannelSelection(values.channelId)\n setFilterValues({ ...values, channelId: channelSelection })\n void seedChannelOptionsForIds(channelSelection)\n }\n setPage(1)\n }, [seedChannelOptionsForIds])\n\n const handleFiltersClear = React.useCallback(() => {\n setFilterValues({})\n setPage(1)\n }, [])\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(\n async (row: SalesDocumentRow) => {\n const confirmMessage =\n kind === 'order'\n ? t(\n 'sales.documents.list.table.deleteOrderConfirm',\n 'Delete this sales order? Related shipments, payments, addresses, and items will be removed.'\n )\n : t(\n 'sales.documents.list.table.deleteQuoteConfirm',\n 'Delete this sales quote? Related addresses, comments, and items will be removed.'\n )\n const confirmed = await confirm({\n title: confirmMessage,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const result = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(row.updatedAt),\n () =>\n deleteCrud(`sales/${resource}`, row.id, {\n errorMessage: t('sales.documents.list.table.deleteError', 'Failed to delete document.'),\n }),\n )\n if (result.ok) {\n flash(\n kind === 'order'\n ? t('sales.documents.list.table.orderDeleted', 'Sales order deleted.')\n : t('sales.documents.list.table.quoteDeleted', 'Sales quote deleted.'),\n 'success'\n )\n handleRefresh()\n }\n } catch (err) {\n logger.error('sales.documents.delete', { err })\n flash(t('sales.documents.list.table.deleteError', 'Failed to delete document.'), 'error')\n }\n },\n [confirm, handleRefresh, kind, resource, t]\n )\n\n const handleRowClick = React.useCallback((row: SalesDocumentRow) => {\n router.push(`/backend/sales/${resource}/${row.id}?kind=${kind}`)\n }, [kind, resource, router])\n\n const columns = React.useMemo<ColumnDef<SalesDocumentRow>[]>(() => [\n {\n id: 'number',\n accessorKey: 'number',\n header: kind === 'order'\n ? t('sales.documents.list.table.order', 'Order')\n : t('sales.documents.list.table.quote', 'Quote'),\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"font-semibold\">{row.original.number}</span>\n {row.original.status ? (\n <DictionaryValue\n value={row.original.status}\n map={statusMap}\n fallback={<span className=\"text-xs text-muted-foreground\">{row.original.status}</span>}\n className=\"text-xs text-muted-foreground font-medium\"\n iconWrapperClassName=\"inline-flex h-5 w-5 items-center justify-center rounded bg-muted text-muted-foreground\"\n iconClassName=\"h-3.5 w-3.5\"\n colorClassName=\"h-3 w-3 rounded-full border border-border/70\"\n />\n ) : null}\n </div>\n ),\n meta: SALES_DOCUMENT_NUMBER_COLUMN_META,\n },\n {\n accessorKey: 'customerName',\n header: t('sales.documents.list.table.customer', 'Customer'),\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"text-sm font-medium\">\n {row.original.customerName ?? t('sales.documents.list.table.noCustomer', 'No customer')}\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {row.original.customerEmail ?? t('sales.documents.list.table.noEmail', 'No email')}\n </span>\n </div>\n ),\n enableSorting: false,\n },\n ...(channelsEnabled ? [{\n accessorKey: 'channelId',\n header: t('sales.documents.list.table.channel', 'Channel'),\n cell: ({ row }) => {\n const channelId = row.original.channelId\n if (!channelId) return <span className=\"text-xs text-muted-foreground\">{t('sales.documents.list.table.unassigned', 'Unassigned')}</span>\n // Server-resolved, so this no longer depends on the channel being inside the options page.\n // The uuid remains only for a channel the list response could not resolve.\n return (\n <span className=\"text-sm\">{row.original.channelName ?? channelId}</span>\n )\n },\n enableSorting: false,\n } satisfies ColumnDef<SalesDocumentRow>] : []),\n {\n id: 'lineItemCount',\n accessorKey: 'lineItemCount',\n header: t('sales.documents.list.table.items', 'Items'),\n cell: ({ row }) => (\n <span className=\"text-sm font-semibold\">{typeof row.original.lineItemCount === 'number' ? row.original.lineItemCount : '\u2014'}</span>\n ),\n },\n {\n id: 'grandTotalNetAmount',\n accessorKey: 'totalNet',\n header: t('sales.documents.list.table.totalNet', 'Total (net)'),\n cell: ({ row }) => (\n <span className=\"text-sm\">{formatCurrency(row.original.totalNet ?? null, row.original.currency)}</span>\n ),\n },\n {\n id: 'grandTotalGrossAmount',\n accessorKey: 'totalGross',\n header: t('sales.documents.list.table.totalGross', 'Total (gross)'),\n cell: ({ row }) => (\n <span className=\"text-sm\">{formatCurrency(row.original.totalGross ?? null, row.original.currency)}</span>\n ),\n },\n {\n id: 'createdAt',\n accessorKey: 'date',\n header: t('sales.documents.list.table.date', 'Date'),\n cell: ({ row }) =>\n row.original.date\n ? <span className=\"text-xs text-muted-foreground\">{new Date(row.original.date).toLocaleString()}</span>\n : <span className=\"text-xs text-muted-foreground\">\u2014</span>,\n },\n ], [channelsEnabled, kind, statusMap, t])\n\n const emptyLabel = kind === 'order'\n ? t('sales.documents.list.table.emptyOrders', 'No orders yet.')\n : t('sales.documents.list.table.emptyQuotes', 'No quotes yet.')\n\n return (\n <Page>\n <PageBody>\n <DataTable<SalesDocumentRow>\n stickyFirstColumn\n stickyActionsColumn\n title={(\n <span className=\"flex flex-col\">\n <span>{title}</span>\n <span className=\"text-sm font-normal text-muted-foreground\">{subtitle}</span>\n </span>\n )}\n titleHeadingLevel={1}\n actions={(\n <Button asChild>\n <Link href={`/backend/sales/documents/create?kind=${kind}`}>\n {t('sales.documents.create.title', 'Create sales document')}\n </Link>\n </Button>\n )}\n columns={columns}\n data={rows}\n sorting={sorting}\n onSortingChange={setSorting}\n isLoading={isLoading}\n searchValue={search}\n onSearchChange={handleSearchChange}\n searchPlaceholder={\n kind === 'order'\n ? t('sales.documents.list.search.orders', 'Search orders\u2026')\n : t('sales.documents.list.search.quotes', 'Search quotes\u2026')\n }\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={handleFiltersApply}\n onFiltersClear={handleFiltersClear}\n entityId={entityId}\n exporter={exportConfig}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalPages,\n totalIsCapped,\n onPageChange: setPage,\n cacheStatus,\n }}\n refreshButton={{\n label: t('sales.documents.list.table.refresh', 'Refresh'),\n onRefresh: handleRefresh,\n isRefreshing: isLoading,\n }}\n rowActions={(row) => (\n <RowActions\n items={[\n {\n id: 'open',\n label: t('sales.documents.list.table.open', 'Open'),\n href: `/backend/sales/${resource}/${row.id}?kind=${kind}`,\n },\n {\n id: 'delete',\n label:\n kind === 'order'\n ? t('sales.documents.list.table.deleteOrder', 'Delete order')\n : t('sales.documents.list.table.deleteQuote', 'Delete quote'),\n onSelect: () => handleDelete(row),\n },\n ]}\n />\n )}\n perspective={{\n tableId: kind === 'order'\n ? extensionPoints.hosts.ordersTable.tableId\n : extensionPoints.hosts.quotesTable.tableId,\n }}\n onRowClick={handleRowClick}\n emptyState={\n <div className=\"py-10 text-center text-sm text-muted-foreground\">\n {emptyLabel}\n </div>\n }\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n\nexport default SalesDocumentsTable\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/sales/extension-points'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\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 { DataTable, type DataTableExportFormat, withDataTableNamespaces } 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 { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { buildCrudExportUrl, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\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 { E } from '#generated/entities.ids.generated'\nimport {\n DictionaryValue,\n type DictionaryMap,\n createDictionaryMap,\n normalizeDictionaryEntries,\n} from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { SALES_DOCUMENT_NUMBER_COLUMN_META } from './salesDocumentsColumns'\nimport { useSalesChannelsEnabled } from '../useSalesChannelsEnabled'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('sales')\n\ntype SalesDocumentKind = 'order' | 'quote'\n\ntype FilterOption = { value: string; label: string; description?: string | null }\n\ntype CustomerSnapshot = {\n customer?: {\n displayName?: string | null\n primaryEmail?: string | null\n } | null\n contact?: {\n firstName?: string | null\n lastName?: string | null\n preferredName?: string | null\n } | null\n}\n\ntype ApiDocument = {\n id: string\n orderNumber?: string | null\n quoteNumber?: string | null\n status?: string | null\n customerEntityId?: string | null\n customerSnapshot?: Record<string, unknown> | null\n channelId?: string | null\n channelName?: string | null\n lineItemCount?: number | null\n grandTotalNetAmount?: number | null\n grandTotalGrossAmount?: number | null\n currencyCode?: string | null\n placedAt?: string | null\n validUntil?: string | null\n validFrom?: string | null\n createdAt?: string | null\n updatedAt?: string | null\n}\n\ntype DocumentsResponse = {\n items?: Array<Record<string, unknown>>\n total?: number\n totalPages?: number\n totalIsCapped?: boolean\n}\n\ntype SalesDocumentRow = {\n id: string\n number: string\n status?: string | null\n customerName?: string | null\n customerEmail?: string | null\n channelId?: string | null\n channelName?: string | null\n lineItemCount?: number | null\n totalNet?: number | null\n totalGross?: number | null\n currency?: string | null\n date?: string | null\n updatedAt?: string | null\n}\n\nconst PAGE_SIZE = 20\n\n// Not a uuid on purpose: the API drops non-uuid entries from channelIds, so a leak of this sentinel\n// into the id list narrows the filter instead of erroring.\nconst UNASSIGNED_CHANNEL_VALUE = '__unassigned__'\n\n// Perspectives persist filterValues, so snapshots saved while this filter was a single-value select\n// hold a bare string. Without this the multi-select would read it as an unknown shape and silently\n// clear the user's saved filter.\nfunction normalizeChannelSelection(value: unknown): string[] {\n const raw = Array.isArray(value) ? value : typeof value === 'string' ? [value] : []\n return raw\n .map((entry) => (typeof entry === 'string' ? entry.trim() : String(entry || '').trim()))\n .filter((entry) => entry.length > 0)\n}\n\nfunction resolveCustomerName(snapshot: CustomerSnapshot | null | undefined, fallback?: string | null) {\n if (!snapshot) return fallback ?? null\n const base = snapshot.customer?.displayName ?? null\n if (base) return base\n const contact = snapshot.contact\n if (contact) {\n const parts = [contact.preferredName, contact.firstName, contact.lastName].filter(\n (part) => part && part.trim().length\n ) as string[]\n if (parts.length) return parts.join(' ')\n }\n return fallback ?? null\n}\n\nfunction resolveCustomerEmail(snapshot: CustomerSnapshot | null | undefined) {\n if (!snapshot) return null\n if (snapshot.customer?.primaryEmail) return snapshot.customer.primaryEmail\n return null\n}\n\nfunction toNumber(value: unknown): number | null {\n if (typeof value === 'number') return Number.isNaN(value) ? null : value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n return Number.isNaN(parsed) ? null : parsed\n }\n return null\n}\n\nfunction formatCurrency(amount: number | null | undefined, currency: string | null | undefined, fallback = '\u2014') {\n if (amount == null || Number.isNaN(amount)) return fallback\n try {\n if (currency && currency.trim().length) {\n const formatter = new Intl.NumberFormat(undefined, { style: 'currency', currency })\n return formatter.format(amount)\n }\n return new Intl.NumberFormat(undefined, { style: 'decimal', maximumFractionDigits: 2 }).format(amount)\n } catch {\n return String(amount)\n }\n}\n\nfunction mergeOptions(existing: FilterOption[], next: FilterOption[]): FilterOption[] {\n const map = new Map<string, FilterOption>()\n existing.forEach((opt) => map.set(opt.value, opt))\n next.forEach((opt) => map.set(opt.value, opt))\n return Array.from(map.values()).sort((a, b) => a.label.localeCompare(b.label))\n}\n\nfunction normalizeNumberInput(value: unknown): number | null {\n if (typeof value === 'number') return Number.isNaN(value) ? null : value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n return Number.isNaN(parsed) ? null : parsed\n }\n return null\n}\n\nexport function SalesDocumentsTable({ kind }: { kind: SalesDocumentKind }) {\n const t = useT()\n const { enabled: channelsEnabled } = useSalesChannelsEnabled()\n const router = useRouter()\n const scopeVersion = useOrganizationScopeVersion()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [rows, setRows] = React.useState<SalesDocumentRow[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [totalIsCapped, setTotalIsCapped] = React.useState(false)\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'createdAt', desc: true }])\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [isLoading, setLoading] = React.useState(false)\n const [cacheStatus, setCacheStatus] = React.useState<'hit' | 'miss' | null>(null)\n const [reloadToken, setReloadToken] = React.useState(0)\n const [channelOptions, setChannelOptions] = React.useState<FilterOption[]>([])\n const [tagOptions, setTagOptions] = React.useState<FilterOption[]>([])\n const [customerOptions, setCustomerOptions] = React.useState<FilterOption[]>([])\n const [statusMap, setStatusMap] = React.useState<DictionaryMap>({})\n\n const resource = kind === 'order' ? 'orders' : 'quotes'\n const entityId = kind === 'order' ? E.sales.sales_order : E.sales.sales_quote\n const title = kind === 'order'\n ? t('sales.documents.list.ordersTitle', 'Sales orders')\n : t('sales.documents.list.quotesTitle', 'Sales quotes')\n const subtitle = t(\n 'sales.documents.list.subtitle',\n 'Review documents with customer context, totals, and channels.'\n )\n\n const fetchChannelOptionsBy = React.useCallback(async (params: URLSearchParams): Promise<FilterOption[]> => {\n try {\n const call = await apiCall<{ items?: unknown[] }>(`/api/sales/channels?${params.toString()}`)\n if (!call.ok) return []\n const items = Array.isArray(call.result?.items) ? call.result!.items : []\n return items\n .map((item: any): FilterOption | null => {\n const id = typeof item?.id === 'string' ? item.id : null\n const name = typeof item?.name === 'string' ? item.name : null\n if (!id || !name) return null\n return { value: id, label: name }\n })\n .filter((opt): opt is FilterOption => opt !== null)\n } catch {\n return []\n }\n }, [])\n\n const fetchChannelOptions = React.useCallback(\n (query?: string) => {\n const params = new URLSearchParams({ page: '1', pageSize: '50' })\n if (query && query.trim()) params.set('search', query.trim())\n return fetchChannelOptionsBy(params)\n },\n [fetchChannelOptionsBy]\n )\n\n // A selected channel outside the first options page \u2014 restored from a perspective, or simply the\n // 51st channel \u2014 would otherwise render as a bare uuid in the filter chip and the selected tag,\n // which is the same defect this change removes from the column. The route supports `ids=`, so\n // fetch exactly the selected ones and merge them in. See\n // `.ai/lessons/async-edit-selects-must-be-hydrated-as-value-plus.md`.\n const seedChannelOptionsForIds = React.useCallback(\n async (ids: string[]) => {\n const missing = ids.filter((id) => id !== UNASSIGNED_CHANNEL_VALUE)\n if (!missing.length) return\n const params = new URLSearchParams({ page: '1', pageSize: String(missing.length) })\n params.set('ids', missing.join(','))\n const opts = await fetchChannelOptionsBy(params)\n if (opts.length) setChannelOptions((prev) => mergeOptions(prev, opts))\n },\n [fetchChannelOptionsBy]\n )\n\n const fetchTagOptions = React.useCallback(async (query?: string): Promise<FilterOption[]> => {\n const params = new URLSearchParams({ page: '1', pageSize: '50' })\n if (query && query.trim()) params.set('search', query.trim())\n try {\n const call = await apiCall<{ items?: unknown[] }>(`/api/sales/tags?${params.toString()}`)\n if (!call.ok) return []\n const items = Array.isArray(call.result?.items) ? call.result!.items : []\n return items\n .map((item: any): FilterOption | null => {\n const id = typeof item?.id === 'string' ? item.id : null\n const label = typeof item?.label === 'string' ? item.label : null\n if (!id || !label) return null\n const description = typeof item?.description === 'string' ? item.description : null\n return { value: id, label, description }\n })\n .filter((opt): opt is FilterOption => opt !== null)\n } catch {\n return []\n }\n }, [])\n\n const fetchCustomerOptions = React.useCallback(async (query?: string): Promise<FilterOption[]> => {\n const params = new URLSearchParams({ page: '1', pageSize: '20' })\n if (query && query.trim().length) params.set('search', query.trim())\n try {\n const [people, companies] = await Promise.all([\n apiCall<{ items?: unknown[] }>(`/api/customers/people?${params.toString()}`),\n apiCall<{ items?: unknown[] }>(`/api/customers/companies?${params.toString()}`),\n ])\n const peopleItems = Array.isArray(people.result?.items) ? people.result?.items ?? [] : []\n const companyItems = Array.isArray(companies.result?.items) ? companies.result?.items ?? [] : []\n const parseOption = (item: any, kind: 'person' | 'company'): FilterOption | null => {\n const id = typeof item?.id === 'string' ? item.id : null\n if (!id) return null\n const name =\n typeof item?.display_name === 'string' && item.display_name.trim().length\n ? item.display_name\n : typeof item?.name === 'string' && item.name.trim().length\n ? item.name\n : id\n const email =\n typeof item?.primary_email === 'string' && item.primary_email.trim().length\n ? item.primary_email.trim()\n : null\n const label = email ? `${name} (${email})` : name\n return { value: id, label }\n }\n const options = [...peopleItems.map((i) => parseOption(i, 'person')), ...companyItems.map((i) => parseOption(i, 'company'))]\n .filter((opt): opt is FilterOption => !!opt)\n return options\n } catch {\n return []\n }\n }, [])\n\n const loadStatusMap = React.useCallback(async () => {\n try {\n const params = new URLSearchParams({ page: '1', pageSize: '100' })\n const response = await apiCall<{ items?: Array<Record<string, unknown>> }>(\n `/api/sales/order-statuses?${params.toString()}`,\n undefined,\n { fallback: { items: [] } }\n )\n const entries = normalizeDictionaryEntries(response.result?.items ?? [], { sort: false })\n setStatusMap(createDictionaryMap(entries))\n } catch (err) {\n logger.error('sales.documents.statuses.load', { err })\n setStatusMap({})\n }\n }, [])\n\n const loadChannelOptions = React.useCallback(\n async (query?: string) => {\n const opts = await fetchChannelOptions(query)\n if (opts.length) setChannelOptions((prev) => mergeOptions(prev, opts))\n return opts\n },\n [fetchChannelOptions]\n )\n\n const unassignedChannelOption = React.useMemo<FilterOption>(\n () => ({\n value: UNASSIGNED_CHANNEL_VALUE,\n label: t('sales.documents.list.filters.channelUnassigned', '(No channel)'),\n }),\n [t]\n )\n\n // The sentinel is offered alongside the real channels so \"these channels or unassigned\" is one\n // selection. It is stripped back out when the query is built \u2014 the API takes it as a separate\n // channelIdsEmpty flag, not as an id.\n const channelFilterOptions = React.useMemo<FilterOption[]>(\n () => [unassignedChannelOption, ...channelOptions],\n [channelOptions, unassignedChannelOption]\n )\n\n const loadChannelFilterOptions = React.useCallback(\n async (query?: string) => {\n const opts = await loadChannelOptions(query)\n // Only offer the sentinel when the user is browsing rather than searching \u2014 typing \"web\"\n // should not suggest \"(No channel)\".\n return query && query.trim() ? opts : [unassignedChannelOption, ...opts]\n },\n [loadChannelOptions, unassignedChannelOption]\n )\n\n const loadTagOptions = React.useCallback(\n async (query?: string) => {\n const opts = await fetchTagOptions(query)\n if (opts.length) setTagOptions((prev) => mergeOptions(prev, opts))\n return opts\n },\n [fetchTagOptions]\n )\n\n const loadCustomerOptions = React.useCallback(\n async (query?: string) => {\n const opts = await fetchCustomerOptions(query)\n if (opts.length) setCustomerOptions((prev) => mergeOptions(prev, opts))\n return opts\n },\n [fetchCustomerOptions]\n )\n\n React.useEffect(() => {\n if (channelsEnabled) loadChannelOptions().catch(() => {})\n loadTagOptions().catch(() => {})\n loadCustomerOptions().catch(() => {})\n loadStatusMap().catch(() => setStatusMap({}))\n }, [channelsEnabled, loadChannelOptions, loadCustomerOptions, loadStatusMap, loadTagOptions, scopeVersion])\n\n const filters = React.useMemo<FilterDef[]>(() => [\n ...(channelsEnabled ? [{\n id: 'channelId',\n label: t('sales.documents.list.filters.channel', 'Channel'),\n type: 'tags',\n options: channelFilterOptions,\n loadOptions: loadChannelFilterOptions,\n placeholder: t('sales.documents.list.filters.channelPlaceholder', 'Search channels'),\n formatValue: (val: string) =>\n val === UNASSIGNED_CHANNEL_VALUE\n ? t('sales.documents.list.filters.channelUnassigned', '(No channel)')\n : channelOptions.find((opt) => opt.value === val)?.label ?? val,\n } satisfies FilterDef] : []),\n {\n id: 'date',\n label: t('sales.documents.list.filters.date', 'Date'),\n type: 'dateRange',\n },\n {\n id: 'lineItemCountMin',\n label: t('sales.documents.list.filters.itemsMin', 'Min items'),\n type: 'text',\n },\n {\n id: 'lineItemCountMax',\n label: t('sales.documents.list.filters.itemsMax', 'Max items'),\n type: 'text',\n },\n {\n id: 'totalNetMin',\n label: t('sales.documents.list.filters.totalNetMin', 'Min total (net)'),\n type: 'text',\n },\n {\n id: 'totalNetMax',\n label: t('sales.documents.list.filters.totalNetMax', 'Max total (net)'),\n type: 'text',\n },\n {\n id: 'totalGrossMin',\n label: t('sales.documents.list.filters.totalGrossMin', 'Min total (gross)'),\n type: 'text',\n },\n {\n id: 'totalGrossMax',\n label: t('sales.documents.list.filters.totalGrossMax', 'Max total (gross)'),\n type: 'text',\n },\n {\n id: 'customerId',\n label: t('sales.documents.list.filters.customer', 'Customer'),\n type: 'tags',\n options: customerOptions,\n loadOptions: loadCustomerOptions,\n placeholder: t('sales.documents.list.filters.customerPlaceholder', 'Search customers'),\n formatValue: (val: string) => {\n const match = customerOptions.find((opt) => opt.value === val)\n return match?.label ?? val\n },\n },\n {\n id: 'tagIds',\n label: t('sales.documents.list.filters.tags', 'Tags'),\n type: 'tags',\n options: tagOptions,\n loadOptions: loadTagOptions,\n formatValue: (val: string) => tagOptions.find((o) => o.value === val)?.label ?? val,\n formatDescription: (val: string) => tagOptions.find((o) => o.value === val)?.description ?? null,\n },\n ], [channelsEnabled, channelFilterOptions, channelOptions, loadChannelFilterOptions, customerOptions, loadCustomerOptions, loadTagOptions, tagOptions, t])\n\n const queryParams = React.useMemo(() => {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', String(PAGE_SIZE))\n if (search.trim()) params.set('search', search.trim())\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 const channelSelection = normalizeChannelSelection(filterValues.channelId)\n const channelIds = channelSelection.filter((value) => value !== UNASSIGNED_CHANNEL_VALUE)\n if (channelIds.length > 0) params.set('channelIds', channelIds.join(','))\n if (channelSelection.includes(UNASSIGNED_CHANNEL_VALUE)) params.set('channelIdsEmpty', 'true')\n const customerIds = Array.isArray(filterValues.customerId)\n ? filterValues.customerId\n .map((value) => (typeof value === 'string' ? value.trim() : String(value || '').trim()))\n .filter((value) => value.length > 0)\n : []\n if (customerIds.length > 0) {\n params.set('customerId', customerIds[0])\n }\n const date = filterValues.date\n if (date && typeof date === 'object') {\n if (date.from) params.set('dateFrom', date.from)\n if (date.to) params.set('dateTo', date.to)\n }\n const numberFilters: Array<[keyof FilterValues, string]> = [\n ['lineItemCountMin', 'lineItemCountMin'],\n ['lineItemCountMax', 'lineItemCountMax'],\n ['totalNetMin', 'totalNetMin'],\n ['totalNetMax', 'totalNetMax'],\n ['totalGrossMin', 'totalGrossMin'],\n ['totalGrossMax', 'totalGrossMax'],\n ]\n numberFilters.forEach(([key, queryKey]) => {\n const value = normalizeNumberInput((filterValues as any)[key])\n if (value != null) params.set(queryKey, String(value))\n })\n const tagIds = Array.isArray(filterValues.tagIds)\n ? filterValues.tagIds.map((value) => (typeof value === 'string' ? value.trim() : String(value || '').trim())).filter((v) => v.length > 0)\n : []\n if (tagIds.length > 0) {\n params.set('tagIds', tagIds.join(','))\n }\n Object.entries(filterValues).forEach(([key, value]) => {\n if (!key.startsWith('cf_') || value == null) return\n if (Array.isArray(value)) {\n const normalized = value\n .map((item) => {\n if (item == null) return ''\n if (typeof item === 'string') return item.trim()\n return String(item).trim()\n })\n .filter((item) => item.length > 0)\n if (normalized.length) params.set(key, normalized.join(','))\n } else if (typeof value === 'object') {\n return\n } else if (value !== '') {\n const stringValue = typeof value === 'string' ? value.trim() : String(value)\n if (stringValue) params.set(key, stringValue)\n }\n })\n return params.toString()\n }, [filterValues, page, search, sorting])\n\n const currentParams = React.useMemo(() => Object.fromEntries(new URLSearchParams(queryParams)), [queryParams])\n\n const exportConfig = React.useMemo(() => ({\n view: {\n getUrl: (format: DataTableExportFormat) =>\n buildCrudExportUrl(`sales/${resource}`, { ...currentParams, exportScope: 'view' }, format),\n },\n full: {\n getUrl: (format: DataTableExportFormat) =>\n buildCrudExportUrl(`sales/${resource}`, { ...currentParams, exportScope: 'full', all: 'true' }, format),\n },\n }), [currentParams, resource])\n\n const mapApiDocument = React.useCallback(\n (item: Record<string, unknown>): SalesDocumentRow => {\n const doc = item as ApiDocument\n const id = typeof doc.id === 'string' ? doc.id : ''\n const number = kind === 'order'\n ? doc.orderNumber ?? (item as any)?.order_number ?? id\n : doc.quoteNumber ?? (item as any)?.quote_number ?? id\n const customerSnapshot = (doc.customerSnapshot ?? null) as CustomerSnapshot | null\n const customerName = resolveCustomerName(customerSnapshot, doc.customerEntityId ?? null)\n const customerEmail = resolveCustomerEmail(customerSnapshot)\n const totalNet = toNumber(doc.grandTotalNetAmount)\n const totalGross = toNumber(doc.grandTotalGrossAmount)\n const placedAt = doc.placedAt ?? null\n const validUntil = doc.validUntil ?? null\n const createdAt = doc.createdAt ?? null\n const date = placedAt ?? validUntil ?? createdAt ?? null\n return withDataTableNamespaces({\n id,\n number,\n status: doc.status ?? null,\n customerName,\n customerEmail,\n channelId: doc.channelId ?? null,\n channelName: doc.channelName ?? null,\n lineItemCount: doc.lineItemCount ?? null,\n totalNet,\n totalGross,\n currency: doc.currencyCode ?? null,\n date,\n updatedAt: doc.updatedAt ?? null,\n }, item)\n },\n [kind]\n )\n\n const loadDocuments = React.useCallback(async () => {\n setLoading(true)\n setCacheStatus(null)\n try {\n const call = await apiCall<DocumentsResponse>(`/api/sales/${resource}?${queryParams}`)\n if (!call.ok) {\n flash(t('sales.documents.list.errors.load', 'Failed to load documents.'), 'error')\n setRows([])\n setTotal(0)\n setTotalPages(1)\n setTotalIsCapped(false)\n return\n }\n const payload = call.result ?? {}\n const items = Array.isArray(payload.items) ? payload.items : []\n setRows(items.map((item) => mapApiDocument(item)))\n const count = typeof payload.total === 'number' ? payload.total : items.length\n setTotal(count)\n const pages = typeof payload.totalPages === 'number'\n ? payload.totalPages\n : Math.max(1, Math.ceil(count / PAGE_SIZE))\n setTotalPages(pages)\n setTotalIsCapped(payload.totalIsCapped === true)\n setCacheStatus(call.cacheStatus ?? null)\n } catch (err) {\n logger.error('sales.documents.list', { err })\n flash(t('sales.documents.list.errors.load', 'Failed to load documents.'), 'error')\n } finally {\n setLoading(false)\n }\n }, [mapApiDocument, queryParams, resource, t])\n\n React.useEffect(() => {\n void loadDocuments()\n }, [loadDocuments, reloadToken, scopeVersion])\n\n // Normalize at the hydration boundary, not only where the query string is built: a perspective\n // saved while this filter was a single-value select restores `channelId` as a bare string, and\n // the `tags` control renders nothing for a non-array value. Storing the normalized shape keeps\n // the control, the active-filter chip and the query builder all reading one shape \u2014 otherwise the\n // list is filtered while the field looks empty, and the next channel the user picks replaces the\n // restored one instead of joining it.\n const handleFiltersApply = React.useCallback((values: FilterValues) => {\n if (values.channelId === undefined) {\n setFilterValues(values)\n } else {\n const channelSelection = normalizeChannelSelection(values.channelId)\n setFilterValues({ ...values, channelId: channelSelection })\n void seedChannelOptionsForIds(channelSelection)\n }\n setPage(1)\n }, [seedChannelOptionsForIds])\n\n const handleFiltersClear = React.useCallback(() => {\n setFilterValues({})\n setPage(1)\n }, [])\n\n const handleSearchChange = React.useCallback((value: string) => {\n setSearch(value)\n setPage(1)\n }, [])\n\n const handleSortingChange = React.useCallback((nextSorting: SortingState) => {\n setSorting(nextSorting)\n setPage(1)\n }, [])\n\n const handleRefresh = React.useCallback(() => {\n setReloadToken((token) => token + 1)\n }, [])\n\n const handleDelete = React.useCallback(\n async (row: SalesDocumentRow) => {\n const confirmMessage =\n kind === 'order'\n ? t(\n 'sales.documents.list.table.deleteOrderConfirm',\n 'Delete this sales order? Related shipments, payments, addresses, and items will be removed.'\n )\n : t(\n 'sales.documents.list.table.deleteQuoteConfirm',\n 'Delete this sales quote? Related addresses, comments, and items will be removed.'\n )\n const confirmed = await confirm({\n title: confirmMessage,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const result = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(row.updatedAt),\n () =>\n deleteCrud(`sales/${resource}`, row.id, {\n errorMessage: t('sales.documents.list.table.deleteError', 'Failed to delete document.'),\n }),\n )\n if (result.ok) {\n flash(\n kind === 'order'\n ? t('sales.documents.list.table.orderDeleted', 'Sales order deleted.')\n : t('sales.documents.list.table.quoteDeleted', 'Sales quote deleted.'),\n 'success'\n )\n handleRefresh()\n }\n } catch (err) {\n logger.error('sales.documents.delete', { err })\n flash(t('sales.documents.list.table.deleteError', 'Failed to delete document.'), 'error')\n }\n },\n [confirm, handleRefresh, kind, resource, t]\n )\n\n const handleRowClick = React.useCallback((row: SalesDocumentRow) => {\n router.push(`/backend/sales/${resource}/${row.id}?kind=${kind}`)\n }, [kind, resource, router])\n\n const columns = React.useMemo<ColumnDef<SalesDocumentRow>[]>(() => [\n {\n id: 'number',\n accessorKey: 'number',\n header: kind === 'order'\n ? t('sales.documents.list.table.order', 'Order')\n : t('sales.documents.list.table.quote', 'Quote'),\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"font-semibold\">{row.original.number}</span>\n {row.original.status ? (\n <DictionaryValue\n value={row.original.status}\n map={statusMap}\n fallback={<span className=\"text-xs text-muted-foreground\">{row.original.status}</span>}\n className=\"text-xs text-muted-foreground font-medium\"\n iconWrapperClassName=\"inline-flex h-5 w-5 items-center justify-center rounded bg-muted text-muted-foreground\"\n iconClassName=\"h-3.5 w-3.5\"\n colorClassName=\"h-3 w-3 rounded-full border border-border/70\"\n />\n ) : null}\n </div>\n ),\n meta: SALES_DOCUMENT_NUMBER_COLUMN_META,\n },\n {\n accessorKey: 'customerName',\n header: t('sales.documents.list.table.customer', 'Customer'),\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"text-sm font-medium\">\n {row.original.customerName ?? t('sales.documents.list.table.noCustomer', 'No customer')}\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {row.original.customerEmail ?? t('sales.documents.list.table.noEmail', 'No email')}\n </span>\n </div>\n ),\n enableSorting: false,\n },\n ...(channelsEnabled ? [{\n accessorKey: 'channelId',\n header: t('sales.documents.list.table.channel', 'Channel'),\n cell: ({ row }) => {\n const channelId = row.original.channelId\n if (!channelId) return <span className=\"text-xs text-muted-foreground\">{t('sales.documents.list.table.unassigned', 'Unassigned')}</span>\n // Server-resolved, so this no longer depends on the channel being inside the options page.\n // The uuid remains only for a channel the list response could not resolve.\n return (\n <span className=\"text-sm\">{row.original.channelName ?? channelId}</span>\n )\n },\n enableSorting: false,\n } satisfies ColumnDef<SalesDocumentRow>] : []),\n {\n id: 'lineItemCount',\n accessorKey: 'lineItemCount',\n header: t('sales.documents.list.table.items', 'Items'),\n cell: ({ row }) => (\n <span className=\"text-sm font-semibold\">{typeof row.original.lineItemCount === 'number' ? row.original.lineItemCount : '\u2014'}</span>\n ),\n },\n {\n id: 'grandTotalNetAmount',\n accessorKey: 'totalNet',\n header: t('sales.documents.list.table.totalNet', 'Total (net)'),\n cell: ({ row }) => (\n <span className=\"text-sm\">{formatCurrency(row.original.totalNet ?? null, row.original.currency)}</span>\n ),\n },\n {\n id: 'grandTotalGrossAmount',\n accessorKey: 'totalGross',\n header: t('sales.documents.list.table.totalGross', 'Total (gross)'),\n cell: ({ row }) => (\n <span className=\"text-sm\">{formatCurrency(row.original.totalGross ?? null, row.original.currency)}</span>\n ),\n },\n {\n id: 'createdAt',\n accessorKey: 'date',\n header: t('sales.documents.list.table.date', 'Date'),\n cell: ({ row }) =>\n row.original.date\n ? <span className=\"text-xs text-muted-foreground\">{new Date(row.original.date).toLocaleString()}</span>\n : <span className=\"text-xs text-muted-foreground\">\u2014</span>,\n },\n ], [channelsEnabled, kind, statusMap, t])\n\n const emptyLabel = kind === 'order'\n ? t('sales.documents.list.table.emptyOrders', 'No orders yet.')\n : t('sales.documents.list.table.emptyQuotes', 'No quotes yet.')\n\n return (\n <Page>\n <PageBody>\n <DataTable<SalesDocumentRow>\n stickyFirstColumn\n stickyActionsColumn\n title={(\n <span className=\"flex flex-col\">\n <span>{title}</span>\n <span className=\"text-sm font-normal text-muted-foreground\">{subtitle}</span>\n </span>\n )}\n titleHeadingLevel={1}\n actions={(\n <Button asChild>\n <Link href={`/backend/sales/documents/create?kind=${kind}`}>\n {t('sales.documents.create.title', 'Create sales document')}\n </Link>\n </Button>\n )}\n columns={columns}\n data={rows}\n sortable\n manualSorting\n sorting={sorting}\n onSortingChange={handleSortingChange}\n isLoading={isLoading}\n searchValue={search}\n onSearchChange={handleSearchChange}\n searchPlaceholder={\n kind === 'order'\n ? t('sales.documents.list.search.orders', 'Search orders\u2026')\n : t('sales.documents.list.search.quotes', 'Search quotes\u2026')\n }\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={handleFiltersApply}\n onFiltersClear={handleFiltersClear}\n entityId={entityId}\n exporter={exportConfig}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalPages,\n totalIsCapped,\n onPageChange: setPage,\n cacheStatus,\n }}\n refreshButton={{\n label: t('sales.documents.list.table.refresh', 'Refresh'),\n onRefresh: handleRefresh,\n isRefreshing: isLoading,\n }}\n rowActions={(row) => (\n <RowActions\n items={[\n {\n id: 'open',\n label: t('sales.documents.list.table.open', 'Open'),\n href: `/backend/sales/${resource}/${row.id}?kind=${kind}`,\n },\n {\n id: 'delete',\n label:\n kind === 'order'\n ? t('sales.documents.list.table.deleteOrder', 'Delete order')\n : t('sales.documents.list.table.deleteQuote', 'Delete quote'),\n onSelect: () => handleDelete(row),\n },\n ]}\n />\n )}\n perspective={{\n tableId: kind === 'order'\n ? extensionPoints.hosts.ordersTable.tableId\n : extensionPoints.hosts.quotesTable.tableId,\n }}\n onRowClick={handleRowClick}\n emptyState={\n <div className=\"py-10 text-center text-sm text-muted-foreground\">\n {emptyLabel}\n </div>\n }\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n\nexport default SalesDocumentsTable\n"],
5
+ "mappings": "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",
6
6
  "names": ["kind"]
7
7
  }
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { cn } from "@open-mercato/shared/lib/utils";
3
4
  import { Tabs, TabsList, TabsTrigger } from "@open-mercato/ui/primitives/tabs";
4
5
  function WarrantyWorkspace({
5
6
  title,
@@ -32,7 +33,13 @@ function WarrantyWorkspace({
32
33
  },
33
34
  tab.id
34
35
  )) }) }) : null,
35
- /* @__PURE__ */ jsx("div", { className: `min-w-0 [&>[data-component-handle]>div:first-child_[data-slot=button]]:h-8 [&>[data-component-handle]>div:first-child_[data-slot=search-input-wrapper]]:h-8 [&_[data-slot=table-header]_[data-slot=table-row]]:h-9 [&_[data-slot=table-body]_[data-slot=table-row]]:h-16 ${contentClassName ?? ""}`, children })
36
+ /* @__PURE__ */ jsx("div", { className: cn(
37
+ "min-w-0 [&>[data-component-handle]>div:first-child_[data-slot=button]]:h-8 [&>[data-component-handle]>div:first-child_[data-slot=search-input-wrapper]]:h-8 [&_[data-slot=table-header]_[data-slot=table-row]]:h-9 [&_[data-slot=table-body]_[data-slot=table-row]]:h-16",
38
+ "md:[&_[data-slot=table-head][class*=sticky]]:bg-card md:[&_[data-slot=table-cell][class*=sticky]]:bg-card",
39
+ "md:[&_[data-slot=table-head][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-head][class*=sticky]]:from-muted/40 md:[&_[data-slot=table-head][class*=sticky]]:to-muted/40",
40
+ "md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:from-muted/30 md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:to-muted/30",
41
+ contentClassName
42
+ ), children })
36
43
  ] });
37
44
  }
38
45
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/warranty_claims/backend/components/WarrantyWorkspace.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\n\nexport type WarrantyWorkspaceTab = {\n id: string\n label: string\n count?: number\n disabled?: boolean\n}\n\ntype WarrantyWorkspaceProps = {\n title: string\n description?: string\n action?: React.ReactNode\n tabs?: WarrantyWorkspaceTab[]\n activeTab?: string\n onTabChange?: (value: string) => void\n summary?: React.ReactNode\n contentClassName?: string\n children: React.ReactNode\n}\n\nexport function WarrantyWorkspace({\n title,\n description,\n action,\n tabs,\n activeTab,\n onTabChange,\n summary,\n contentClassName,\n children,\n}: WarrantyWorkspaceProps) {\n return (\n <section className=\"overflow-hidden rounded-xl border border-border bg-card\">\n <div className=\"flex flex-col gap-4 px-8 pb-6 pt-7 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"min-w-0\">\n <h1 className=\"text-2xl font-bold tracking-tight text-foreground\">{title}</h1>\n {description ? <p className=\"mt-1 text-sm text-muted-foreground\">{description}</p> : null}\n </div>\n {action ? <div className=\"shrink-0\">{action}</div> : null}\n </div>\n {summary ?? null}\n {tabs?.length && activeTab && onTabChange ? (\n <Tabs value={activeTab} onValueChange={onTabChange} variant=\"underline\">\n <TabsList className=\"flex h-auto w-full gap-1 overflow-x-auto px-7 pb-2\" aria-label={title}>\n {tabs.map((tab) => (\n <TabsTrigger\n key={tab.id}\n value={tab.id}\n count={tab.count}\n disabled={tab.disabled}\n className=\"shrink-0 gap-2 px-4 pb-2 pt-3 [&_[data-slot=tabs-trigger-count]]:h-auto [&_[data-slot=tabs-trigger-count]]:min-w-0 [&_[data-slot=tabs-trigger-count]]:rounded-md [&_[data-slot=tabs-trigger-count]]:bg-muted [&_[data-slot=tabs-trigger-count]]:px-1.5 [&_[data-slot=tabs-trigger-count]]:py-0.5 [&_[data-slot=tabs-trigger-count]]:text-overline [&_[data-slot=tabs-trigger-count]]:text-muted-foreground\"\n >\n {tab.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n ) : null}\n <div className={`min-w-0 [&>[data-component-handle]>div:first-child_[data-slot=button]]:h-8 [&>[data-component-handle]>div:first-child_[data-slot=search-input-wrapper]]:h-8 [&_[data-slot=table-header]_[data-slot=table-row]]:h-9 [&_[data-slot=table-body]_[data-slot=table-row]]:h-16 ${contentClassName ?? ''}`}>{children}</div>\n </section>\n )\n}\n"],
5
- "mappings": ";AAsCQ,SACE,KADF;AAnCR,SAAS,MAAM,UAAU,mBAAmB;AAqBrC,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,SACE,qBAAC,aAAQ,WAAU,2DACjB;AAAA,yBAAC,SAAI,WAAU,qFACb;AAAA,2BAAC,SAAI,WAAU,WACb;AAAA,4BAAC,QAAG,WAAU,qDAAqD,iBAAM;AAAA,QACxE,cAAc,oBAAC,OAAE,WAAU,sCAAsC,uBAAY,IAAO;AAAA,SACvF;AAAA,MACC,SAAS,oBAAC,SAAI,WAAU,YAAY,kBAAO,IAAS;AAAA,OACvD;AAAA,IACC,WAAW;AAAA,IACX,MAAM,UAAU,aAAa,cAC5B,oBAAC,QAAK,OAAO,WAAW,eAAe,aAAa,SAAQ,aAC1D,8BAAC,YAAS,WAAU,sDAAqD,cAAY,OAClF,eAAK,IAAI,CAAC,QACT;AAAA,MAAC;AAAA;AAAA,QAEC,OAAO,IAAI;AAAA,QACX,OAAO,IAAI;AAAA,QACX,UAAU,IAAI;AAAA,QACd,WAAU;AAAA,QAET,cAAI;AAAA;AAAA,MANA,IAAI;AAAA,IAOX,CACD,GACH,GACF,IACE;AAAA,IACJ,oBAAC,SAAI,WAAW,4QAA4Q,oBAAoB,EAAE,IAAK,UAAS;AAAA,KAClU;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\n\nexport type WarrantyWorkspaceTab = {\n id: string\n label: string\n count?: number\n disabled?: boolean\n}\n\ntype WarrantyWorkspaceProps = {\n title: string\n description?: string\n action?: React.ReactNode\n tabs?: WarrantyWorkspaceTab[]\n activeTab?: string\n onTabChange?: (value: string) => void\n summary?: React.ReactNode\n contentClassName?: string\n children: React.ReactNode\n}\n\nexport function WarrantyWorkspace({\n title,\n description,\n action,\n tabs,\n activeTab,\n onTabChange,\n summary,\n contentClassName,\n children,\n}: WarrantyWorkspaceProps) {\n return (\n <section className=\"overflow-hidden rounded-xl border border-border bg-card\">\n <div className=\"flex flex-col gap-4 px-8 pb-6 pt-7 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"min-w-0\">\n <h1 className=\"text-2xl font-bold tracking-tight text-foreground\">{title}</h1>\n {description ? <p className=\"mt-1 text-sm text-muted-foreground\">{description}</p> : null}\n </div>\n {action ? <div className=\"shrink-0\">{action}</div> : null}\n </div>\n {summary ?? null}\n {tabs?.length && activeTab && onTabChange ? (\n <Tabs value={activeTab} onValueChange={onTabChange} variant=\"underline\">\n <TabsList className=\"flex h-auto w-full gap-1 overflow-x-auto px-7 pb-2\" aria-label={title}>\n {tabs.map((tab) => (\n <TabsTrigger\n key={tab.id}\n value={tab.id}\n count={tab.count}\n disabled={tab.disabled}\n className=\"shrink-0 gap-2 px-4 pb-2 pt-3 [&_[data-slot=tabs-trigger-count]]:h-auto [&_[data-slot=tabs-trigger-count]]:min-w-0 [&_[data-slot=tabs-trigger-count]]:rounded-md [&_[data-slot=tabs-trigger-count]]:bg-muted [&_[data-slot=tabs-trigger-count]]:px-1.5 [&_[data-slot=tabs-trigger-count]]:py-0.5 [&_[data-slot=tabs-trigger-count]]:text-overline [&_[data-slot=tabs-trigger-count]]:text-muted-foreground\"\n >\n {tab.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n ) : null}\n <div className={cn(\n 'min-w-0 [&>[data-component-handle]>div:first-child_[data-slot=button]]:h-8 [&>[data-component-handle]>div:first-child_[data-slot=search-input-wrapper]]:h-8 [&_[data-slot=table-header]_[data-slot=table-row]]:h-9 [&_[data-slot=table-body]_[data-slot=table-row]]:h-16',\n 'md:[&_[data-slot=table-head][class*=sticky]]:bg-card md:[&_[data-slot=table-cell][class*=sticky]]:bg-card',\n 'md:[&_[data-slot=table-head][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-head][class*=sticky]]:from-muted/40 md:[&_[data-slot=table-head][class*=sticky]]:to-muted/40',\n 'md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:from-muted/30 md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:to-muted/30',\n contentClassName,\n )}>{children}</div>\n </section>\n )\n}\n"],
5
+ "mappings": ";AAuCQ,SACE,KADF;AApCR,SAAS,UAAU;AACnB,SAAS,MAAM,UAAU,mBAAmB;AAqBrC,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,SACE,qBAAC,aAAQ,WAAU,2DACjB;AAAA,yBAAC,SAAI,WAAU,qFACb;AAAA,2BAAC,SAAI,WAAU,WACb;AAAA,4BAAC,QAAG,WAAU,qDAAqD,iBAAM;AAAA,QACxE,cAAc,oBAAC,OAAE,WAAU,sCAAsC,uBAAY,IAAO;AAAA,SACvF;AAAA,MACC,SAAS,oBAAC,SAAI,WAAU,YAAY,kBAAO,IAAS;AAAA,OACvD;AAAA,IACC,WAAW;AAAA,IACX,MAAM,UAAU,aAAa,cAC5B,oBAAC,QAAK,OAAO,WAAW,eAAe,aAAa,SAAQ,aAC1D,8BAAC,YAAS,WAAU,sDAAqD,cAAY,OAClF,eAAK,IAAI,CAAC,QACT;AAAA,MAAC;AAAA;AAAA,QAEC,OAAO,IAAI;AAAA,QACX,OAAO,IAAI;AAAA,QACX,UAAU,IAAI;AAAA,QACd,WAAU;AAAA,QAET,cAAI;AAAA;AAAA,MANA,IAAI;AAAA,IAOX,CACD,GACH,GACF,IACE;AAAA,IACJ,oBAAC,SAAI,WAAW;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,GAAI,UAAS;AAAA,KACf;AAEJ;",
6
6
  "names": []
7
7
  }