@open-mercato/core 0.10.1-develop.7941.1.2d89db94ca → 0.10.1-develop.7954.1.48da4a7d13

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.
@@ -1,4 +1,4 @@
1
- [build:core] found 6276 entry points
1
+ [build:core] found 6277 entry points
2
2
  [build:core] built successfully
3
3
  [build:core:generated] found 271 entry points
4
4
  [build:core:generated] built successfully
@@ -3,7 +3,9 @@ import { z } from "zod";
3
3
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
4
  import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
5
5
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
6
+ import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
6
7
  import { WarrantyClaimLine } from "../../../../data/entities.js";
8
+ import { loadWarrantyClaimDictionaryLabel } from "../../../../lib/dictionaries.js";
7
9
  import { loadPortalOwnedClaim } from "../../../../lib/portalClaimAccess.js";
8
10
  import { resolveLinkedCustomerAuth } from "../../../../lib/portalAuthGuard.js";
9
11
  const metadata = {
@@ -81,6 +83,23 @@ function serializeClaim(claim, lines) {
81
83
  lines: lines.map(serializeLine)
82
84
  };
83
85
  }
86
+ async function loadRejectionReasonLabel(context, rejectionReasonCode) {
87
+ if (!rejectionReasonCode) return null;
88
+ try {
89
+ return await loadWarrantyClaimDictionaryLabel(
90
+ context.em.fork(),
91
+ { tenantId: context.tenantId, organizationId: context.organizationId },
92
+ "warranty-claim-rejection-reason",
93
+ rejectionReasonCode
94
+ );
95
+ } catch (error) {
96
+ getTelemetryRuntime()?.reportError(error, {
97
+ module: "warranty_claims",
98
+ code: "warranty_claims.portal_rejection_label_failed"
99
+ });
100
+ return null;
101
+ }
102
+ }
84
103
  async function GET(req, ctx) {
85
104
  const contextOrResponse = await resolvePortalContext(req);
86
105
  if (contextOrResponse instanceof Response) return contextOrResponse;
@@ -110,7 +129,8 @@ async function GET(req, ctx) {
110
129
  { orderBy: { lineNo: "ASC" } },
111
130
  scope
112
131
  );
113
- return NextResponse.json({ item: serializeClaim(claim, lines) });
132
+ const rejectionReasonLabel = await loadRejectionReasonLabel(context, claim.rejectionReasonCode);
133
+ return NextResponse.json({ item: { ...serializeClaim(claim, lines), rejectionReasonLabel } });
114
134
  }
115
135
  const lineSchema = z.object({
116
136
  id: z.string().uuid(),
@@ -143,6 +163,7 @@ const claimSchema = z.object({
143
163
  orderNumber: z.string().nullable(),
144
164
  reasonCode: z.string().nullable(),
145
165
  rejectionReasonCode: z.string().nullable(),
166
+ rejectionReasonLabel: z.string().nullable().optional(),
146
167
  resolutionSummary: z.string().nullable(),
147
168
  slaDueAt: z.string().nullable(),
148
169
  submittedAt: z.string().nullable(),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/warranty_claims/api/portal/claims/%5Bid%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaim, WarrantyClaimLine } from '../../../../data/entities'\nimport { loadPortalOwnedClaim } from '../../../../lib/portalClaimAccess'\nimport { resolveLinkedCustomerAuth } from '../../../../lib/portalAuthGuard'\n\nexport const metadata = {\n GET: { requireAuth: false },\n}\n\ntype RouteParams = { id: string }\ntype RouteContext = { params: Promise<RouteParams> }\n\ntype PortalContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n}\n\nfunction toIso(value: Date | string | null | undefined): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\n}\n\nasync function resolveClaimId(ctx: RouteContext): Promise<string | null> {\n try {\n const params = await ctx.params\n return typeof params.id === 'string' && params.id.trim().length ? params.id.trim() : null\n } catch {\n return null\n }\n}\n\nasync function resolvePortalContext(req: Request): Promise<PortalContext | Response> {\n const authOrResponse = await resolveLinkedCustomerAuth(req)\n if (authOrResponse instanceof Response) return authOrResponse\n const auth = authOrResponse\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n }\n}\n\nfunction serializeLine(line: WarrantyClaimLine) {\n return {\n id: line.id,\n lineNo: line.lineNo,\n productId: line.productId ?? null,\n sku: line.sku ?? null,\n productName: line.productName ?? null,\n orderLineId: line.orderLineId ?? null,\n serialNumber: line.serialNumber ?? null,\n faultCode: line.faultCode ?? null,\n faultDescription: line.faultDescription ?? null,\n qtyClaimed: line.qtyClaimed,\n qtyApproved: line.qtyApproved ?? null,\n disposition: line.disposition ?? null,\n lineStatus: line.lineStatus,\n creditAmount: line.creditAmount ?? null,\n createdAt: toIso(line.createdAt),\n updatedAt: toIso(line.updatedAt),\n }\n}\n\nfunction serializeClaim(claim: WarrantyClaim, lines: WarrantyClaimLine[]) {\n return {\n id: claim.id,\n claimNumber: claim.claimNumber,\n claimType: claim.claimType,\n status: claim.status,\n channel: claim.channel,\n priority: claim.priority,\n customerId: claim.customerId ?? null,\n customerName: claim.customerName ?? null,\n orderId: claim.orderId ?? null,\n orderNumber: claim.orderNumber ?? null,\n reasonCode: claim.reasonCode ?? null,\n rejectionReasonCode: claim.rejectionReasonCode ?? null,\n resolutionSummary: claim.resolutionSummary ?? null,\n slaDueAt: toIso(claim.slaDueAt),\n submittedAt: toIso(claim.submittedAt),\n resolvedAt: toIso(claim.resolvedAt),\n closedAt: toIso(claim.closedAt),\n createdAt: toIso(claim.createdAt),\n updatedAt: toIso(claim.updatedAt),\n lines: lines.map(serializeLine),\n }\n}\n\nexport async function GET(req: Request, ctx: RouteContext) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n const claimId = await resolveClaimId(ctx)\n if (!claimId) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const scope = { tenantId: context.tenantId, organizationId: context.organizationId }\n const claim = await loadPortalOwnedClaim(\n context.em,\n {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n customerId: context.customerId,\n },\n claimId,\n )\n if (!claim) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const lines = await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n { claim: claim.id, tenantId: context.tenantId, organizationId: context.organizationId, deletedAt: null },\n { orderBy: { lineNo: 'ASC' } },\n scope,\n )\n return NextResponse.json({ item: serializeClaim(claim, lines) })\n}\n\nconst lineSchema = z.object({\n id: z.string().uuid(),\n lineNo: z.number().int(),\n productId: z.string().uuid().nullable(),\n sku: z.string().nullable(),\n productName: z.string().nullable(),\n orderLineId: z.string().uuid().nullable(),\n serialNumber: z.string().nullable(),\n faultCode: z.string().nullable(),\n faultDescription: z.string().nullable(),\n qtyClaimed: z.string(),\n qtyApproved: z.string().nullable(),\n disposition: z.string().nullable(),\n lineStatus: z.string(),\n creditAmount: z.string().nullable(),\n createdAt: z.string().nullable(),\n updatedAt: z.string().nullable(),\n})\n\nconst claimSchema = z.object({\n id: z.string().uuid(),\n claimNumber: z.string(),\n claimType: z.string(),\n status: z.string(),\n channel: z.string(),\n priority: z.string(),\n customerId: z.string().uuid().nullable(),\n customerName: z.string().nullable(),\n orderId: z.string().uuid().nullable(),\n orderNumber: z.string().nullable(),\n reasonCode: z.string().nullable(),\n rejectionReasonCode: z.string().nullable(),\n resolutionSummary: z.string().nullable(),\n slaDueAt: z.string().nullable(),\n submittedAt: z.string().nullable(),\n resolvedAt: z.string().nullable(),\n closedAt: z.string().nullable(),\n createdAt: z.string().nullable(),\n updatedAt: z.string().nullable(),\n lines: z.array(lineSchema),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim detail',\n methods: {\n GET: {\n summary: 'Read one authenticated customer claim',\n responses: [\n {\n status: 200,\n description: 'Customer claim detail',\n schema: z.object({ item: claimSchema }),\n },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaim, WarrantyClaimLine } from '../../../../data/entities'\nimport { loadWarrantyClaimDictionaryLabel } from '../../../../lib/dictionaries'\nimport { loadPortalOwnedClaim } from '../../../../lib/portalClaimAccess'\nimport { resolveLinkedCustomerAuth } from '../../../../lib/portalAuthGuard'\n\nexport const metadata = {\n GET: { requireAuth: false },\n}\n\ntype RouteParams = { id: string }\ntype RouteContext = { params: Promise<RouteParams> }\n\ntype PortalContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n}\n\nfunction toIso(value: Date | string | null | undefined): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\n}\n\nasync function resolveClaimId(ctx: RouteContext): Promise<string | null> {\n try {\n const params = await ctx.params\n return typeof params.id === 'string' && params.id.trim().length ? params.id.trim() : null\n } catch {\n return null\n }\n}\n\nasync function resolvePortalContext(req: Request): Promise<PortalContext | Response> {\n const authOrResponse = await resolveLinkedCustomerAuth(req)\n if (authOrResponse instanceof Response) return authOrResponse\n const auth = authOrResponse\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n }\n}\n\nfunction serializeLine(line: WarrantyClaimLine) {\n return {\n id: line.id,\n lineNo: line.lineNo,\n productId: line.productId ?? null,\n sku: line.sku ?? null,\n productName: line.productName ?? null,\n orderLineId: line.orderLineId ?? null,\n serialNumber: line.serialNumber ?? null,\n faultCode: line.faultCode ?? null,\n faultDescription: line.faultDescription ?? null,\n qtyClaimed: line.qtyClaimed,\n qtyApproved: line.qtyApproved ?? null,\n disposition: line.disposition ?? null,\n lineStatus: line.lineStatus,\n creditAmount: line.creditAmount ?? null,\n createdAt: toIso(line.createdAt),\n updatedAt: toIso(line.updatedAt),\n }\n}\n\nfunction serializeClaim(claim: WarrantyClaim, lines: WarrantyClaimLine[]) {\n return {\n id: claim.id,\n claimNumber: claim.claimNumber,\n claimType: claim.claimType,\n status: claim.status,\n channel: claim.channel,\n priority: claim.priority,\n customerId: claim.customerId ?? null,\n customerName: claim.customerName ?? null,\n orderId: claim.orderId ?? null,\n orderNumber: claim.orderNumber ?? null,\n reasonCode: claim.reasonCode ?? null,\n rejectionReasonCode: claim.rejectionReasonCode ?? null,\n resolutionSummary: claim.resolutionSummary ?? null,\n slaDueAt: toIso(claim.slaDueAt),\n submittedAt: toIso(claim.submittedAt),\n resolvedAt: toIso(claim.resolvedAt),\n closedAt: toIso(claim.closedAt),\n createdAt: toIso(claim.createdAt),\n updatedAt: toIso(claim.updatedAt),\n lines: lines.map(serializeLine),\n }\n}\n\nasync function loadRejectionReasonLabel(\n context: PortalContext,\n rejectionReasonCode: string | null | undefined,\n): Promise<string | null> {\n if (!rejectionReasonCode) return null\n try {\n return await loadWarrantyClaimDictionaryLabel(\n context.em.fork(),\n { tenantId: context.tenantId, organizationId: context.organizationId },\n 'warranty-claim-rejection-reason',\n rejectionReasonCode,\n )\n } catch (error) {\n getTelemetryRuntime()?.reportError(error, {\n module: 'warranty_claims',\n code: 'warranty_claims.portal_rejection_label_failed',\n })\n return null\n }\n}\n\nexport async function GET(req: Request, ctx: RouteContext) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n const claimId = await resolveClaimId(ctx)\n if (!claimId) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const scope = { tenantId: context.tenantId, organizationId: context.organizationId }\n const claim = await loadPortalOwnedClaim(\n context.em,\n {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n customerId: context.customerId,\n },\n claimId,\n )\n if (!claim) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.notFound', 'Claim not found.') }, { status: 404 })\n }\n const lines = await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n { claim: claim.id, tenantId: context.tenantId, organizationId: context.organizationId, deletedAt: null },\n { orderBy: { lineNo: 'ASC' } },\n scope,\n )\n const rejectionReasonLabel = await loadRejectionReasonLabel(context, claim.rejectionReasonCode)\n return NextResponse.json({ item: { ...serializeClaim(claim, lines), rejectionReasonLabel } })\n}\n\nconst lineSchema = z.object({\n id: z.string().uuid(),\n lineNo: z.number().int(),\n productId: z.string().uuid().nullable(),\n sku: z.string().nullable(),\n productName: z.string().nullable(),\n orderLineId: z.string().uuid().nullable(),\n serialNumber: z.string().nullable(),\n faultCode: z.string().nullable(),\n faultDescription: z.string().nullable(),\n qtyClaimed: z.string(),\n qtyApproved: z.string().nullable(),\n disposition: z.string().nullable(),\n lineStatus: z.string(),\n creditAmount: z.string().nullable(),\n createdAt: z.string().nullable(),\n updatedAt: z.string().nullable(),\n})\n\nconst claimSchema = z.object({\n id: z.string().uuid(),\n claimNumber: z.string(),\n claimType: z.string(),\n status: z.string(),\n channel: z.string(),\n priority: z.string(),\n customerId: z.string().uuid().nullable(),\n customerName: z.string().nullable(),\n orderId: z.string().uuid().nullable(),\n orderNumber: z.string().nullable(),\n reasonCode: z.string().nullable(),\n rejectionReasonCode: z.string().nullable(),\n rejectionReasonLabel: z.string().nullable().optional(),\n resolutionSummary: z.string().nullable(),\n slaDueAt: z.string().nullable(),\n submittedAt: z.string().nullable(),\n resolvedAt: z.string().nullable(),\n closedAt: z.string().nullable(),\n createdAt: z.string().nullable(),\n updatedAt: z.string().nullable(),\n lines: z.array(lineSchema),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claim detail',\n methods: {\n GET: {\n summary: 'Read one authenticated customer claim',\n responses: [\n {\n status: 200,\n description: 'Customer claim detail',\n schema: z.object({ item: claimSchema }),\n },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -100,7 +100,7 @@ function bannerMessage(claim, t) {
100
100
  case "closed":
101
101
  return claim.resolutionSummary ?? t("warranty_claims.portal.tracker.banner.resolved");
102
102
  case "rejected": {
103
- const reason = claim.rejectionReasonCode ? localizeDictionaryLabel(t, "rejection", claim.rejectionReasonCode, claim.rejectionReasonCode) : null;
103
+ const reason = claim.rejectionReasonCode ? localizeDictionaryLabel(t, "rejection", claim.rejectionReasonCode, claim.rejectionReasonLabel || claim.rejectionReasonCode) : null;
104
104
  const base = t("warranty_claims.portal.detail.terminal.rejected");
105
105
  const parts = [base, reason, claim.resolutionSummary].filter((part) => Boolean(part));
106
106
  return parts.join(" ");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/warranty_claims/frontend/%5BorgSlug%5D/portal/claims/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { ArrowLeft, Check, CircleAlert, Info, Paperclip, RefreshCw, ShieldCheck, Trash2, TriangleAlert } from 'lucide-react'\nimport { replaceWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'\nimport { useLocale, useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { localizeDictionaryLabel } from '@open-mercato/core/modules/warranty_claims/lib/dictionaryLabels'\nimport { formatQuantity } from '@open-mercato/core/modules/warranty_claims/lib/quantity'\nimport {\n ATTACHMENT_ACCEPT_TYPES,\n validateAttachmentFile,\n} from '@open-mercato/core/modules/warranty_claims/lib/portalAttachmentValidation'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { PortalEmptyState } from '@open-mercato/ui/portal/components/PortalEmptyState'\nimport { usePortalAppEvent } from '@open-mercato/ui/portal/hooks/usePortalAppEvent'\n\ntype Props = { params: { orgSlug: string; id: string } }\n\ntype PortalClaimLine = {\n id: string\n lineNo: number\n sku: string | null\n productName: string | null\n serialNumber: string | null\n faultCode: string | null\n faultDescription: string | null\n qtyClaimed: string\n qtyApproved: string | null\n lineStatus: string\n disposition: string | null\n creditAmount: string | null\n}\n\ntype PortalClaim = {\n id: string\n claimNumber: string\n claimType: string\n status: string\n priority: string\n orderId: string | null\n orderNumber: string | null\n reasonCode: string | null\n rejectionReasonCode: string | null\n resolutionSummary: string | null\n submittedAt: string | null\n resolvedAt: string | null\n closedAt: string | null\n createdAt: string | null\n updatedAt: string | null\n lines: PortalClaimLine[]\n}\n\ntype PortalClaimEvent = {\n id: string\n kind: string\n body: string | null\n payload: Record<string, unknown> | null\n actorCustomerId: string | null\n createdAt: string | null\n}\n\ntype PortalAttachment = {\n id: string\n url: string\n downloadUrl: string\n fileName: string\n fileSize: number\n mimeType: string | null\n thumbnailUrl: string\n createdAt: string | null\n}\n\ntype ClaimResponse = { item: PortalClaim }\ntype EventResponse = { items: PortalClaimEvent[] }\ntype AttachmentResponse = { items: PortalAttachment[] }\ntype MutationOkResponse = { ok: boolean; error?: string }\ntype UploadResponse = MutationOkResponse & { item?: PortalAttachment }\ntype ClaimActionResponse = MutationOkResponse & { claimId?: string; status?: string }\ntype PortalOption = { value: string; label: string }\ntype PortalOptionsResponse = { ok: boolean; result?: { reasons: PortalOption[] } }\n\ntype PortalClaimAction = 'submit' | 'withdraw'\n\ntype TrackerStepState = 'complete' | 'current' | 'pending'\n\ntype TrackerStep = {\n id: string\n title: string\n description: string\n state: TrackerStepState\n dateLabel: string | null\n}\n\ntype BannerTone = 'info' | 'success' | 'warning' | 'error'\n\ntype ActivityEntry = {\n id: string\n title: string\n description: string\n createdAt: string | null\n href?: string\n attachment?: PortalAttachment\n}\n\nconst TRACKER_STEP_KEYS = ['received', 'review', 'decision', 'return', 'resolved'] as const\n\nconst STATUS_STEP_INDEX: Record<string, number> = {\n draft: 0,\n submitted: 1,\n in_review: 1,\n info_requested: 1,\n rejected: 2,\n cancelled: 2,\n approved: 3,\n awaiting_return: 3,\n received: 4,\n inspecting: 4,\n resolved: 5,\n closed: 5,\n}\n\nconst LINE_STATUS_DOT_CLASSES: Record<string, string> = {\n pending: 'bg-status-neutral-icon',\n approved: 'bg-status-success-icon',\n rejected: 'bg-status-error-icon',\n received: 'bg-status-info-icon',\n inspected: 'bg-status-info-icon',\n resolved: 'bg-status-success-icon',\n}\n\nconst BANNER_TONE_CLASSES: Record<BannerTone, { wrapper: string; text: string; icon: string }> = {\n info: {\n wrapper: 'bg-status-info-bg',\n text: 'text-status-info-text',\n icon: 'text-status-info-icon',\n },\n success: {\n wrapper: 'bg-status-success-bg',\n text: 'text-status-success-text',\n icon: 'text-status-success-icon',\n },\n warning: {\n wrapper: 'bg-status-warning-bg',\n text: 'text-status-warning-text',\n icon: 'text-status-warning-icon',\n },\n error: {\n wrapper: 'bg-status-error-bg',\n text: 'text-status-error-text',\n icon: 'text-status-error-icon',\n },\n}\n\nfunction bannerTone(status: string): BannerTone {\n if (status === 'rejected' || status === 'cancelled') return 'error'\n if (status === 'info_requested') return 'warning'\n if (status === 'approved' || status === 'resolved' || status === 'closed') return 'success'\n return 'info'\n}\n\nfunction bannerMessage(claim: PortalClaim, t: TranslateFn): string {\n switch (claim.status) {\n case 'draft':\n return t('warranty_claims.portal.tracker.banner.draft')\n case 'info_requested':\n return t('warranty_claims.portal.tracker.banner.infoRequested')\n case 'approved':\n return t('warranty_claims.portal.tracker.banner.approved')\n case 'awaiting_return':\n return t('warranty_claims.portal.tracker.banner.awaitingReturn')\n case 'received':\n case 'inspecting':\n return t('warranty_claims.portal.tracker.banner.goodsFlow')\n case 'resolved':\n case 'closed':\n return claim.resolutionSummary ?? t('warranty_claims.portal.tracker.banner.resolved')\n case 'rejected': {\n const reason = claim.rejectionReasonCode\n ? localizeDictionaryLabel(t, 'rejection', claim.rejectionReasonCode, claim.rejectionReasonCode)\n : null\n const base = t('warranty_claims.portal.detail.terminal.rejected')\n const parts = [base, reason, claim.resolutionSummary].filter((part): part is string => Boolean(part))\n return parts.join(' ')\n }\n case 'cancelled':\n return t('warranty_claims.portal.detail.terminal.cancelled')\n default:\n return t('warranty_claims.portal.tracker.banner.review')\n }\n}\n\nfunction formatShortDate(value: string | null, locale: string): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return date.toLocaleDateString(locale || undefined, { month: 'short', day: 'numeric' })\n}\n\nfunction formatLongDate(value: string | null, locale: string): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return date.toLocaleDateString(locale || undefined, { year: 'numeric', month: 'short', day: 'numeric' })\n}\n\nfunction buildTrackerSteps(claim: PortalClaim, t: TranslateFn, locale: string): TrackerStep[] {\n const currentIndex = STATUS_STEP_INDEX[claim.status] ?? 1\n const terminalComplete = claim.status === 'resolved' || claim.status === 'closed'\n return TRACKER_STEP_KEYS.map((key, index) => {\n const state: TrackerStepState = index < currentIndex || (terminalComplete && index === currentIndex)\n ? 'complete'\n : index === currentIndex\n ? 'current'\n : 'pending'\n let dateLabel: string | null = null\n if (index === 0 && state === 'complete') dateLabel = formatShortDate(claim.submittedAt, locale)\n if (index === TRACKER_STEP_KEYS.length - 1 && state === 'complete') {\n dateLabel = formatShortDate(\n claim.status === 'closed'\n ? claim.closedAt ?? claim.resolvedAt\n : claim.resolvedAt ?? claim.closedAt,\n locale,\n )\n }\n if (state === 'current') dateLabel = t('warranty_claims.portal.tracker.step.now')\n return {\n id: key,\n title: t(`warranty_claims.portal.tracker.step.${key}.title`),\n description: t(`warranty_claims.portal.tracker.step.${key}.description`),\n state,\n dateLabel,\n }\n })\n}\n\nfunction buildClaimTitle(claim: PortalClaim, reasonOptions: PortalOption[], t: TranslateFn): string {\n const typeLabel = t(`warranty_claims.claimType.${claim.claimType}`)\n if (claim.reasonCode) {\n const reasonCode = claim.reasonCode\n const dictionaryLabel = reasonOptions.find((option) => option.value === reasonCode)?.label ?? reasonCode\n const reasonLabel = localizeDictionaryLabel(t, 'reason', reasonCode, dictionaryLabel)\n return t('warranty_claims.portal.tracker.titleWithReason', { reason: reasonLabel, type: typeLabel })\n }\n return t('warranty_claims.portal.tracker.titleFallback', { type: typeLabel })\n}\n\nfunction buildClaimSubtitle(claim: PortalClaim, t: TranslateFn, locale: string): string {\n if (claim.status === 'draft') {\n const created = formatLongDate(claim.createdAt, locale)\n return created\n ? t('warranty_claims.portal.tracker.draftCreated', { date: created })\n : t('warranty_claims.portal.detail.notSubmitted')\n }\n const submitted = formatLongDate(claim.submittedAt, locale) ?? formatLongDate(claim.createdAt, locale)\n if (!submitted) return t('warranty_claims.portal.value.notAvailable')\n if (claim.orderNumber) {\n return t('warranty_claims.portal.tracker.submittedFromOrder', { date: submitted, order: claim.orderNumber })\n }\n return t('warranty_claims.portal.tracker.submittedOn', { date: submitted })\n}\n\nfunction formatEventBody(event: PortalClaimEvent, t: TranslateFn): string {\n if (event.body) return event.body\n const payload = event.payload\n const from = typeof payload?.from === 'string' ? payload.from : null\n const to = typeof payload?.to === 'string' ? payload.to : null\n if (from && to) {\n return t('warranty_claims.portal.event.transition', {\n from: t(`warranty_claims.status.${from}`),\n to: t(`warranty_claims.status.${to}`),\n })\n }\n return t('warranty_claims.portal.event.noDetails')\n}\n\nfunction eventTitle(event: PortalClaimEvent, t: TranslateFn): string {\n if (event.kind === 'comment') {\n return event.actorCustomerId\n ? t('warranty_claims.portal.tracker.event.youReplied')\n : t('warranty_claims.portal.tracker.event.teamReplied')\n }\n return t(`warranty_claims.portal.event.${event.kind}`)\n}\n\nfunction buildActivityEntries(\n events: PortalClaimEvent[],\n attachments: PortalAttachment[],\n t: TranslateFn,\n): ActivityEntry[] {\n const eventEntries: ActivityEntry[] = events.map((event) => ({\n id: `event-${event.id}`,\n title: eventTitle(event, t),\n description: formatEventBody(event, t),\n createdAt: event.createdAt,\n }))\n const attachmentEntries: ActivityEntry[] = attachments.map((attachment) => ({\n id: `attachment-${attachment.id}`,\n title: t('warranty_claims.portal.tracker.event.attachmentAdded'),\n description: attachment.fileName,\n createdAt: attachment.createdAt,\n href: attachment.downloadUrl,\n attachment,\n }))\n return [...eventEntries, ...attachmentEntries].sort((left, right) => {\n const leftTime = left.createdAt ? new Date(left.createdAt).getTime() : 0\n const rightTime = right.createdAt ? new Date(right.createdAt).getTime() : 0\n return rightTime - leftTime\n })\n}\n\nconst DETAIL_MUTATION_CONTEXT_ID = 'warranty_claims.portal.claim.detail'\n\nconst SECTION_CLASS = 'flex w-full flex-col gap-3 border-t border-border px-6 pb-6 pt-5'\nconst SECTION_HEADING_CLASS = 'text-sm font-semibold text-foreground'\nconst DARK_BUTTON_CLASS = 'rounded-md px-3 py-2 text-sm font-medium'\n\nexport default function WarrantyClaimPortalDetailPage({ params }: Props) {\n const t = useT()\n const locale = useLocale()\n const { auth } = usePortalContext()\n const { user, loading } = auth\n const guardedMutation = useGuardedMutation<Record<string, unknown>>({\n contextId: DETAIL_MUTATION_CONTEXT_ID,\n blockedMessage: t('warranty_claims.portal.detail.blocked'),\n })\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [claim, setClaim] = React.useState<PortalClaim | null>(null)\n const [events, setEvents] = React.useState<PortalClaimEvent[]>([])\n const [attachments, setAttachments] = React.useState<PortalAttachment[]>([])\n const [reasonOptions, setReasonOptions] = React.useState<PortalOption[]>([])\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [notFound, setNotFound] = React.useState(false)\n const [comment, setComment] = React.useState('')\n const [commentSubmitting, setCommentSubmitting] = React.useState(false)\n const [fileInputKey, setFileInputKey] = React.useState(0)\n const [uploading, setUploading] = React.useState(false)\n const [attachmentMutationId, setAttachmentMutationId] = React.useState<string | null>(null)\n const [claimAction, setClaimAction] = React.useState<PortalClaimAction | null>(null)\n const [actionSubmitting, setActionSubmitting] = React.useState(false)\n const replacementInputRef = React.useRef<HTMLInputElement | null>(null)\n const replacementTargetRef = React.useRef<PortalAttachment | null>(null)\n const claimRequestSequenceRef = React.useRef(0)\n const eventsRequestSequenceRef = React.useRef(0)\n\n // Leaving an authenticated page for the login page crosses the public/authenticated\n // boundary, so it must be a full page load \u2014 see `replaceWithPageReload`.\n React.useEffect(() => {\n if (!loading && !user) {\n replaceWithPageReload(`/${params.orgSlug}/portal/login`)\n }\n }, [loading, user, params.orgSlug])\n\n const refreshEvents = React.useCallback(async (claimId: string) => {\n const requestSequence = ++eventsRequestSequenceRef.current\n const result = await apiCall<EventResponse>(`/api/warranty_claims/portal/events?claimId=${encodeURIComponent(claimId)}`)\n if (requestSequence === eventsRequestSequenceRef.current && result.ok && result.result) {\n setEvents(result.result.items)\n }\n }, [])\n\n const refreshAttachments = React.useCallback(async (claimId: string) => {\n const result = await apiCall<AttachmentResponse>(`/api/warranty_claims/portal/attachments?claimId=${encodeURIComponent(claimId)}`)\n if (result.ok && result.result) setAttachments(result.result.items)\n }, [])\n\n const loadClaim = React.useCallback(async (showLoading = true) => {\n const requestSequence = ++claimRequestSequenceRef.current\n if (showLoading) setIsLoading(true)\n setError(null)\n setNotFound(false)\n try {\n const detail = await apiCall<ClaimResponse>(`/api/warranty_claims/portal/claims/${encodeURIComponent(params.id)}`)\n if (requestSequence !== claimRequestSequenceRef.current) return\n if (detail.status === 404) {\n setClaim(null)\n setNotFound(true)\n return\n }\n if (!detail.ok || !detail.result) {\n setError(t('warranty_claims.portal.detail.loadError'))\n return\n }\n setClaim(detail.result.item)\n await Promise.all([\n refreshEvents(detail.result.item.id),\n refreshAttachments(detail.result.item.id),\n ])\n } catch {\n if (requestSequence === claimRequestSequenceRef.current) {\n setError(t('warranty_claims.portal.detail.loadError'))\n }\n } finally {\n if (requestSequence === claimRequestSequenceRef.current) setIsLoading(false)\n }\n }, [params.id, refreshAttachments, refreshEvents, t])\n\n React.useEffect(() => {\n if (user) void loadClaim()\n }, [loadClaim, user])\n\n usePortalAppEvent('warranty_claims.claim.portal_status_changed', (event) => {\n if (!user || event.payload.claimId !== params.id) return\n void loadClaim(false)\n })\n\n usePortalAppEvent('warranty_claims.claim.comment_added', (event) => {\n if (!user || event.payload.claimId !== params.id) return\n void refreshEvents(params.id).catch((error) => {\n getTelemetryRuntime()?.reportError(error, {\n module: 'warranty_claims',\n code: 'warranty_claims.portal_timeline_refresh_failed',\n attributes: { claimId: params.id },\n })\n setError(t('warranty_claims.portal.detail.loadError'))\n })\n })\n\n React.useEffect(() => {\n if (!user) return\n let cancelled = false\n apiCall<PortalOptionsResponse>('/api/warranty_claims/portal/options')\n .then((result) => {\n if (cancelled) return\n setReasonOptions(result.result?.ok ? result.result.result?.reasons ?? [] : [])\n })\n .catch(() => {})\n return () => {\n cancelled = true\n }\n }, [user])\n\n const submitComment = React.useCallback(async () => {\n if (!claim || commentSubmitting) return\n const body = comment.trim()\n if (!body) return\n setCommentSubmitting(true)\n setError(null)\n try {\n const result = await guardedMutation.runMutation({\n operation: () => apiCall<MutationOkResponse>('/api/warranty_claims/portal/events', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({ claimId: claim.id, body }),\n }),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'warranty_claims.claim_event',\n operation: 'portal_comment',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'warranty_claims.claim_event',\n resourceId: claim.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: { claimId: claim.id, body },\n })\n if (!result.ok || !result.result?.ok) {\n setError(t('warranty_claims.portal.detail.commentError'))\n return\n }\n setComment('')\n flash(t('warranty_claims.portal.detail.commentSuccess'), 'success')\n await refreshEvents(claim.id)\n } catch {\n setError(t('warranty_claims.portal.detail.commentError'))\n } finally {\n setCommentSubmitting(false)\n }\n }, [claim, comment, commentSubmitting, guardedMutation, refreshEvents, t])\n\n const uploadAttachment = React.useCallback(async (file: File, replacementTarget?: PortalAttachment) => {\n if (!claim || uploading) return\n const validationError = await validateAttachmentFile(file, t)\n if (validationError) {\n setError(validationError)\n flash(validationError, 'error')\n return\n }\n setUploading(true)\n setAttachmentMutationId(replacementTarget?.id ?? 'new')\n setError(null)\n try {\n const form = new FormData()\n form.set('claimId', claim.id)\n form.set('file', file)\n if (replacementTarget) form.set('attachmentId', replacementTarget.id)\n const result = await guardedMutation.runMutation({\n operation: () => apiCall<UploadResponse>('/api/warranty_claims/portal/attachments', {\n method: replacementTarget ? 'PUT' : 'POST',\n credentials: 'include',\n body: form,\n }),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'attachments.attachment',\n operation: replacementTarget ? 'portal_attachment_replace' : 'portal_attachment_upload',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'attachments.attachment',\n resourceId: replacementTarget?.id ?? claim.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: {\n claimId: claim.id,\n attachmentId: replacementTarget?.id,\n fileName: file.name,\n fileSize: file.size,\n },\n })\n if (!result.ok || !result.result?.ok) {\n setError(replacementTarget\n ? t('warranty_claims.portal.detail.attachmentReplaceError', 'Attachment replacement failed.')\n : t('warranty_claims.portal.detail.attachmentError'))\n return\n }\n flash(\n replacementTarget\n ? t('warranty_claims.portal.detail.attachmentReplaceSuccess', 'Attachment replaced.')\n : t('warranty_claims.portal.detail.attachmentSuccess'),\n 'success',\n )\n await refreshAttachments(claim.id)\n } catch {\n setError(replacementTarget\n ? t('warranty_claims.portal.detail.attachmentReplaceError', 'Attachment replacement failed.')\n : t('warranty_claims.portal.detail.attachmentError'))\n } finally {\n setUploading(false)\n setAttachmentMutationId(null)\n setFileInputKey((current) => current + 1)\n }\n }, [claim, guardedMutation, refreshAttachments, t, uploading])\n\n const deleteAttachment = React.useCallback(async (attachment: PortalAttachment) => {\n if (!claim || uploading) return\n const confirmed = await confirm({\n title: t('warranty_claims.portal.detail.attachmentDelete', 'Delete attachment'),\n text: t(\n 'warranty_claims.portal.detail.attachmentDeleteConfirm',\n 'Delete \"{name}\"? This action cannot be undone.',\n { name: attachment.fileName },\n ),\n confirmText: t('warranty_claims.portal.detail.attachmentDelete', 'Delete attachment'),\n cancelText: t('warranty_claims.portal.detail.dialogCancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!confirmed) return\n setUploading(true)\n setAttachmentMutationId(attachment.id)\n setError(null)\n try {\n const result = await guardedMutation.runMutation({\n operation: () => apiCall<MutationOkResponse>(\n `/api/warranty_claims/portal/attachments?attachmentId=${encodeURIComponent(attachment.id)}`,\n { method: 'DELETE', credentials: 'include' },\n ),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'attachments.attachment',\n operation: 'portal_attachment_delete',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'attachments.attachment',\n resourceId: attachment.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: { claimId: claim.id, attachmentId: attachment.id },\n })\n if (!result.ok || !result.result?.ok) {\n setError(t('warranty_claims.portal.detail.attachmentDeleteError', 'Attachment deletion failed.'))\n return\n }\n flash(t('warranty_claims.portal.detail.attachmentDeleteSuccess', 'Attachment deleted.'), 'success')\n await refreshAttachments(claim.id)\n } catch {\n setError(t('warranty_claims.portal.detail.attachmentDeleteError', 'Attachment deletion failed.'))\n } finally {\n setUploading(false)\n setAttachmentMutationId(null)\n }\n }, [claim, confirm, guardedMutation, refreshAttachments, t, uploading])\n\n const requestAttachmentReplacement = React.useCallback((attachment: PortalAttachment) => {\n if (uploading) return\n replacementTargetRef.current = attachment\n replacementInputRef.current?.click()\n }, [uploading])\n\n const runClaimAction = React.useCallback(async (action: PortalClaimAction) => {\n if (!claim || actionSubmitting) return\n setActionSubmitting(true)\n setError(null)\n try {\n const endpoint = `/api/warranty_claims/portal/claims/${encodeURIComponent(claim.id)}/${action}`\n const result = await guardedMutation.runMutation({\n operation: () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader(claim.updatedAt),\n () => apiCall<ClaimActionResponse>(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({}),\n }),\n ),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'warranty_claims.claim',\n operation: action === 'submit' ? 'portal_submit' : 'portal_withdraw',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'warranty_claims.claim',\n resourceId: claim.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: { claimId: claim.id, action },\n })\n if (!result.ok || !result.result?.ok) {\n const message = result.status === 409\n ? t('warranty_claims.portal.detail.actionConflict', 'This claim changed in the meantime. It has been reloaded \u2014 please try again.')\n : action === 'submit'\n ? t('warranty_claims.portal.detail.submitError', 'The claim could not be submitted.')\n : t('warranty_claims.portal.detail.withdrawError', 'The claim could not be withdrawn.')\n setError(message)\n flash(message, 'error')\n if (result.status === 409) {\n setClaimAction(null)\n await loadClaim()\n }\n return\n }\n setClaimAction(null)\n flash(\n action === 'submit'\n ? t('warranty_claims.portal.detail.submitSuccess', 'Your claim has been submitted.')\n : t('warranty_claims.portal.detail.withdrawSuccess', 'Your claim has been withdrawn.'),\n 'success',\n )\n await loadClaim()\n } catch {\n const message = action === 'submit'\n ? t('warranty_claims.portal.detail.submitError', 'The claim could not be submitted.')\n : t('warranty_claims.portal.detail.withdrawError', 'The claim could not be withdrawn.')\n setError(message)\n flash(message, 'error')\n } finally {\n setActionSubmitting(false)\n }\n }, [actionSubmitting, claim, guardedMutation, loadClaim, t])\n\n const handleSelectedFileChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0] ?? null\n if (!file) return\n void uploadAttachment(file)\n }, [uploadAttachment])\n\n const handleReplacementFileChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n const replacementTarget = replacementTargetRef.current\n replacementTargetRef.current = null\n const file = event.target.files?.[0] ?? null\n event.target.value = ''\n if (!replacementTarget || !file) return\n void uploadAttachment(file, replacementTarget)\n }, [uploadAttachment])\n\n const handleCommentSubmit = React.useCallback((event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n void submitComment()\n }, [submitComment])\n\n const handleCommentKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void submitComment()\n return\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n setComment('')\n }\n }, [submitComment])\n\n if (loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (!user) return null\n\n if (isLoading) {\n return <LoadingMessage label={t('warranty_claims.portal.detail.loading')} />\n }\n\n if (notFound) {\n return (\n <PortalEmptyState\n icon={<ShieldCheck className=\"size-5\" />}\n title={t('warranty_claims.portal.detail.notFoundTitle')}\n description={t('warranty_claims.portal.detail.notFoundDescription')}\n action={\n <Button asChild>\n <Link href={`/${params.orgSlug}/portal/claims`}>\n <ArrowLeft className=\"size-4\" aria-hidden=\"true\" />\n {t('warranty_claims.portal.detail.back')}\n </Link>\n </Button>\n }\n />\n )\n }\n\n if (!claim) {\n return <ErrorMessage label={error ?? t('warranty_claims.portal.detail.loadError')} />\n }\n\n const trackerSteps = buildTrackerSteps(claim, t, locale)\n const isDraft = claim.status === 'draft'\n const canWithdraw = claim.status === 'draft' || claim.status === 'submitted'\n const tone = bannerTone(claim.status)\n const toneClasses = BANNER_TONE_CLASSES[tone]\n const BannerIcon = tone === 'error' ? CircleAlert : tone === 'warning' ? TriangleAlert : Info\n const activityEntries = buildActivityEntries(events, attachments, t)\n\n return (\n <div className=\"mx-auto flex w-full max-w-3xl flex-col gap-4\">\n <Link\n href={`/${params.orgSlug}/portal/claims`}\n className=\"text-sm font-medium text-muted-foreground transition-colors hover:text-foreground\"\n >\n {'\u2190'} {t('warranty_claims.portal.listTitle')}\n </Link>\n\n {error ? <ErrorMessage label={error} /> : null}\n\n <div className=\"w-full overflow-hidden rounded-lg border border-border bg-card\">\n <div className=\"flex w-full flex-col gap-3 px-6 pb-5 pt-6\">\n <div className=\"flex w-full items-center gap-2\">\n <div className=\"flex items-center gap-2 rounded-sm border border-border bg-background px-3 py-1\">\n <span className=\"text-xs font-medium text-muted-foreground\">\n {t(`warranty_claims.claimType.${claim.claimType}`)}\n </span>\n <span className=\"text-sm font-semibold text-foreground\">\n {claim.claimNumber}\n </span>\n </div>\n <div className=\"min-w-px flex-1\" />\n {isDraft ? (\n <Button\n type=\"button\"\n size=\"sm\"\n className={DARK_BUTTON_CLASS}\n onClick={() => setClaimAction('submit')}\n disabled={actionSubmitting}\n >\n {t('warranty_claims.portal.submit')}\n </Button>\n ) : null}\n {canWithdraw ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"destructive\"\n className=\"rounded-md px-3 py-2 text-sm font-medium\"\n onClick={() => setClaimAction('withdraw')}\n disabled={actionSubmitting}\n >\n {t('warranty_claims.portal.detail.withdraw', 'Withdraw claim')}\n </Button>\n ) : null}\n </div>\n <h1 className=\"text-xl font-bold text-foreground\">\n {buildClaimTitle(claim, reasonOptions, t)}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {buildClaimSubtitle(claim, t, locale)}\n </p>\n </div>\n\n <div className=\"w-full px-6 pb-6\">\n <div className={`flex w-full items-start gap-3 rounded-lg px-4 py-3 ${toneClasses.wrapper}`}>\n <BannerIcon className={`mt-px size-4 shrink-0 ${toneClasses.icon}`} aria-hidden=\"true\" />\n <p className={`min-w-px flex-1 text-sm ${toneClasses.text}`}>\n {bannerMessage(claim, t)}\n </p>\n </div>\n </div>\n\n <div className=\"flex w-full flex-col px-6 pb-6\">\n {trackerSteps.map((step, index) => (\n <div key={step.id} className=\"flex w-full items-stretch gap-4\">\n <div className=\"flex flex-col items-center\">\n {step.state === 'complete' ? (\n <span className=\"flex size-6 shrink-0 items-center justify-center rounded-full bg-status-success-icon\">\n <Check className=\"size-3 text-status-success-bg\" strokeWidth={2.5} aria-hidden=\"true\" />\n </span>\n ) : step.state === 'current' ? (\n <span className=\"flex size-6 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-semibold text-primary-foreground\">\n {index + 1}\n </span>\n ) : (\n <span className=\"flex size-6 shrink-0 items-center justify-center rounded-full border border-border text-xs font-semibold text-muted-foreground\">\n {index + 1}\n </span>\n )}\n {index < trackerSteps.length - 1 ? (\n <span\n className={`min-h-2 w-px flex-1 ${step.state === 'complete' ? 'bg-status-success-icon' : 'bg-border'}`}\n />\n ) : null}\n </div>\n <div className=\"flex min-w-px flex-1 flex-col gap-1 pb-4\">\n <div className=\"flex w-full items-center gap-2\">\n <p\n className={\n step.state === 'pending'\n ? 'text-sm font-medium text-muted-foreground'\n : 'text-sm font-semibold text-foreground'\n }\n >\n {step.title}\n </p>\n <div className=\"min-w-px flex-1\" />\n {step.dateLabel ? (\n <p className=\"text-xs text-muted-foreground\">{step.dateLabel}</p>\n ) : null}\n </div>\n <p className=\"w-full text-xs text-muted-foreground\">{step.description}</p>\n </div>\n </div>\n ))}\n </div>\n\n <div className={SECTION_CLASS}>\n <p className={SECTION_HEADING_CLASS}>{t('warranty_claims.portal.tracker.items')}</p>\n {claim.lines.length > 0 ? (\n claim.lines.map((line) => (\n <div\n key={line.id}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border px-4 py-3\"\n >\n <div className=\"flex min-w-px flex-1 flex-col gap-1\">\n <p className=\"text-sm font-medium text-foreground\">\n {line.productName ?? line.sku ?? t('warranty_claims.portal.value.unnamedLine')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {line.sku\n ? t('warranty_claims.portal.tracker.itemMeta', {\n sku: line.sku,\n qty: formatQuantity(line.qtyClaimed, t('warranty_claims.portal.value.notAvailable')),\n })\n : t('warranty_claims.portal.tracker.itemQty', {\n qty: formatQuantity(line.qtyClaimed, t('warranty_claims.portal.value.notAvailable')),\n })}\n </p>\n </div>\n <div className=\"flex shrink-0 items-center gap-2 rounded-sm border border-border bg-background px-2 py-1\">\n <span\n className={`size-2 shrink-0 rounded-full ${LINE_STATUS_DOT_CLASSES[line.lineStatus] ?? 'bg-status-neutral-icon'}`}\n aria-hidden=\"true\"\n />\n <span className=\"text-xs font-medium text-foreground\">\n {t(`warranty_claims.lineStatus.${line.lineStatus}`)}\n </span>\n </div>\n </div>\n ))\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t('warranty_claims.portal.detail.linesEmpty.description')}\n </p>\n )}\n </div>\n\n <div className={SECTION_CLASS}>\n <p className={SECTION_HEADING_CLASS}>{t('warranty_claims.portal.tracker.activity')}</p>\n {activityEntries.length > 0 ? (\n activityEntries.map((entry) => (\n <div key={entry.id} className=\"flex w-full items-start gap-3\">\n <span className=\"mt-1 size-2 shrink-0 rounded-full bg-status-neutral-icon\" aria-hidden=\"true\" />\n <div className=\"flex min-w-px flex-1 flex-col gap-px\">\n <div className=\"flex w-full items-start gap-2\">\n <p className=\"text-sm font-medium text-foreground\">{entry.title}</p>\n <div className=\"min-w-px flex-1\" />\n {entry.createdAt ? (\n <p className=\"text-xs text-muted-foreground\">{formatShortDate(entry.createdAt, locale)}</p>\n ) : null}\n </div>\n {entry.href ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <a\n href={entry.href}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"min-w-0 whitespace-pre-wrap text-xs text-muted-foreground underline-offset-2 hover:underline\"\n >\n {entry.description}\n </a>\n {entry.attachment ? (\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"px-2 text-xs\"\n aria-label={t(\n 'warranty_claims.portal.detail.attachmentReplaceNamed',\n 'Replace {name}',\n { name: entry.attachment.fileName },\n )}\n onClick={() => requestAttachmentReplacement(entry.attachment!)}\n disabled={uploading}\n >\n {attachmentMutationId === entry.attachment.id ? (\n <Spinner className=\"size-3\" />\n ) : (\n <RefreshCw className=\"size-3\" aria-hidden=\"true\" />\n )}\n {t('warranty_claims.portal.detail.attachmentReplace', 'Replace')}\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"px-2 text-xs text-status-error-text\"\n aria-label={t(\n 'warranty_claims.portal.detail.attachmentDeleteNamed',\n 'Delete {name}',\n { name: entry.attachment.fileName },\n )}\n onClick={() => { void deleteAttachment(entry.attachment!) }}\n disabled={uploading}\n >\n <Trash2 className=\"size-3\" aria-hidden=\"true\" />\n {t('warranty_claims.portal.detail.attachmentDelete', 'Delete attachment')}\n </Button>\n </div>\n ) : null}\n </div>\n ) : (\n <p className=\"whitespace-pre-wrap text-xs text-muted-foreground\">{entry.description}</p>\n )}\n </div>\n </div>\n ))\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t('warranty_claims.portal.detail.timelineEmpty.description')}\n </p>\n )}\n <input\n ref={replacementInputRef}\n type=\"file\"\n accept={ATTACHMENT_ACCEPT_TYPES}\n onChange={handleReplacementFileChange}\n disabled={uploading}\n className=\"sr-only\"\n aria-label={t('warranty_claims.portal.detail.attachmentChooseReplacement', 'Choose replacement file')}\n />\n </div>\n\n <div className={SECTION_CLASS}>\n <p className={SECTION_HEADING_CLASS}>{t('warranty_claims.portal.tracker.message')}</p>\n <form id=\"warranty-claim-comment-box\" className=\"flex w-full flex-col gap-3\" onSubmit={handleCommentSubmit}>\n <Textarea\n value={comment}\n onChange={(event) => setComment(event.target.value)}\n onKeyDown={handleCommentKeyDown}\n placeholder={t('warranty_claims.portal.tracker.messagePlaceholder')}\n disabled={commentSubmitting}\n maxLength={8000}\n className=\"h-40 resize-none rounded-lg border-input bg-background px-3 py-2 text-sm shadow-xs placeholder:text-muted-foreground\"\n />\n <div className=\"flex w-full items-center gap-2\">\n <label className={`flex items-center gap-2 ${uploading ? 'cursor-default opacity-70' : 'cursor-pointer'}`}>\n {uploading ? (\n <Spinner className=\"size-4\" />\n ) : (\n <Paperclip className=\"size-4 text-muted-foreground\" aria-hidden=\"true\" />\n )}\n <span className=\"text-xs font-medium text-muted-foreground transition-colors hover:text-foreground\">\n {uploading\n ? t('warranty_claims.portal.detail.uploading')\n : t('warranty_claims.portal.tracker.attachFiles')}\n </span>\n <input\n key={fileInputKey}\n type=\"file\"\n accept={ATTACHMENT_ACCEPT_TYPES}\n onChange={handleSelectedFileChange}\n disabled={uploading}\n className=\"sr-only\"\n />\n </label>\n <div className=\"min-w-px flex-1\" />\n <Button type=\"submit\" size=\"sm\" className={DARK_BUTTON_CLASS} disabled={commentSubmitting || !comment.trim()}>\n {commentSubmitting\n ? t('warranty_claims.portal.tracker.sending')\n : t('warranty_claims.portal.tracker.send')}\n </Button>\n </div>\n </form>\n </div>\n </div>\n\n <Dialog\n open={claimAction !== null}\n onOpenChange={(open) => {\n if (!open && !actionSubmitting) setClaimAction(null)\n }}\n >\n <DialogContent\n className=\"max-w-lg\"\n onKeyDown={(event) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n if (claimAction && !actionSubmitting) void runClaimAction(claimAction)\n }\n if (event.key === 'Escape' && !actionSubmitting) {\n setClaimAction(null)\n }\n }}\n >\n <DialogHeader>\n <DialogTitle>\n {claimAction === 'withdraw'\n ? t('warranty_claims.portal.detail.withdrawDialog.title', 'Withdraw this claim?')\n : t('warranty_claims.portal.detail.submitDialog.title', 'Submit this claim?')}\n </DialogTitle>\n <DialogDescription>\n {claimAction === 'withdraw'\n ? t('warranty_claims.portal.detail.withdrawDialog.description', 'The claim will be cancelled and cannot be reopened. This cannot be undone.')\n : t('warranty_claims.portal.detail.submitDialog.description', 'Your claim will be sent to our support team for review. You can still add comments and photos afterwards.')}\n </DialogDescription>\n </DialogHeader>\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setClaimAction(null)}\n disabled={actionSubmitting}\n >\n {t('warranty_claims.portal.detail.dialogCancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n variant={claimAction === 'withdraw' ? 'destructive' : 'default'}\n onClick={() => {\n if (claimAction) void runClaimAction(claimAction)\n }}\n disabled={actionSubmitting}\n >\n {claimAction === 'withdraw'\n ? (actionSubmitting\n ? t('warranty_claims.portal.detail.withdrawing', 'Withdrawing...')\n : t('warranty_claims.portal.detail.withdrawConfirm', 'Withdraw claim'))\n : (actionSubmitting\n ? t('warranty_claims.portal.detail.submitting', 'Submitting...')\n : t('warranty_claims.portal.detail.submitConfirm', 'Submit claim'))}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { ArrowLeft, Check, CircleAlert, Info, Paperclip, RefreshCw, ShieldCheck, Trash2, TriangleAlert } from 'lucide-react'\nimport { replaceWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'\nimport { useLocale, useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { localizeDictionaryLabel } from '@open-mercato/core/modules/warranty_claims/lib/dictionaryLabels'\nimport { formatQuantity } from '@open-mercato/core/modules/warranty_claims/lib/quantity'\nimport {\n ATTACHMENT_ACCEPT_TYPES,\n validateAttachmentFile,\n} from '@open-mercato/core/modules/warranty_claims/lib/portalAttachmentValidation'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { PortalEmptyState } from '@open-mercato/ui/portal/components/PortalEmptyState'\nimport { usePortalAppEvent } from '@open-mercato/ui/portal/hooks/usePortalAppEvent'\n\ntype Props = { params: { orgSlug: string; id: string } }\n\ntype PortalClaimLine = {\n id: string\n lineNo: number\n sku: string | null\n productName: string | null\n serialNumber: string | null\n faultCode: string | null\n faultDescription: string | null\n qtyClaimed: string\n qtyApproved: string | null\n lineStatus: string\n disposition: string | null\n creditAmount: string | null\n}\n\ntype PortalClaim = {\n id: string\n claimNumber: string\n claimType: string\n status: string\n priority: string\n orderId: string | null\n orderNumber: string | null\n reasonCode: string | null\n rejectionReasonCode: string | null\n rejectionReasonLabel?: string | null\n resolutionSummary: string | null\n submittedAt: string | null\n resolvedAt: string | null\n closedAt: string | null\n createdAt: string | null\n updatedAt: string | null\n lines: PortalClaimLine[]\n}\n\ntype PortalClaimEvent = {\n id: string\n kind: string\n body: string | null\n payload: Record<string, unknown> | null\n actorCustomerId: string | null\n createdAt: string | null\n}\n\ntype PortalAttachment = {\n id: string\n url: string\n downloadUrl: string\n fileName: string\n fileSize: number\n mimeType: string | null\n thumbnailUrl: string\n createdAt: string | null\n}\n\ntype ClaimResponse = { item: PortalClaim }\ntype EventResponse = { items: PortalClaimEvent[] }\ntype AttachmentResponse = { items: PortalAttachment[] }\ntype MutationOkResponse = { ok: boolean; error?: string }\ntype UploadResponse = MutationOkResponse & { item?: PortalAttachment }\ntype ClaimActionResponse = MutationOkResponse & { claimId?: string; status?: string }\ntype PortalOption = { value: string; label: string }\ntype PortalOptionsResponse = { ok: boolean; result?: { reasons: PortalOption[] } }\n\ntype PortalClaimAction = 'submit' | 'withdraw'\n\ntype TrackerStepState = 'complete' | 'current' | 'pending'\n\ntype TrackerStep = {\n id: string\n title: string\n description: string\n state: TrackerStepState\n dateLabel: string | null\n}\n\ntype BannerTone = 'info' | 'success' | 'warning' | 'error'\n\ntype ActivityEntry = {\n id: string\n title: string\n description: string\n createdAt: string | null\n href?: string\n attachment?: PortalAttachment\n}\n\nconst TRACKER_STEP_KEYS = ['received', 'review', 'decision', 'return', 'resolved'] as const\n\nconst STATUS_STEP_INDEX: Record<string, number> = {\n draft: 0,\n submitted: 1,\n in_review: 1,\n info_requested: 1,\n rejected: 2,\n cancelled: 2,\n approved: 3,\n awaiting_return: 3,\n received: 4,\n inspecting: 4,\n resolved: 5,\n closed: 5,\n}\n\nconst LINE_STATUS_DOT_CLASSES: Record<string, string> = {\n pending: 'bg-status-neutral-icon',\n approved: 'bg-status-success-icon',\n rejected: 'bg-status-error-icon',\n received: 'bg-status-info-icon',\n inspected: 'bg-status-info-icon',\n resolved: 'bg-status-success-icon',\n}\n\nconst BANNER_TONE_CLASSES: Record<BannerTone, { wrapper: string; text: string; icon: string }> = {\n info: {\n wrapper: 'bg-status-info-bg',\n text: 'text-status-info-text',\n icon: 'text-status-info-icon',\n },\n success: {\n wrapper: 'bg-status-success-bg',\n text: 'text-status-success-text',\n icon: 'text-status-success-icon',\n },\n warning: {\n wrapper: 'bg-status-warning-bg',\n text: 'text-status-warning-text',\n icon: 'text-status-warning-icon',\n },\n error: {\n wrapper: 'bg-status-error-bg',\n text: 'text-status-error-text',\n icon: 'text-status-error-icon',\n },\n}\n\nfunction bannerTone(status: string): BannerTone {\n if (status === 'rejected' || status === 'cancelled') return 'error'\n if (status === 'info_requested') return 'warning'\n if (status === 'approved' || status === 'resolved' || status === 'closed') return 'success'\n return 'info'\n}\n\nfunction bannerMessage(claim: PortalClaim, t: TranslateFn): string {\n switch (claim.status) {\n case 'draft':\n return t('warranty_claims.portal.tracker.banner.draft')\n case 'info_requested':\n return t('warranty_claims.portal.tracker.banner.infoRequested')\n case 'approved':\n return t('warranty_claims.portal.tracker.banner.approved')\n case 'awaiting_return':\n return t('warranty_claims.portal.tracker.banner.awaitingReturn')\n case 'received':\n case 'inspecting':\n return t('warranty_claims.portal.tracker.banner.goodsFlow')\n case 'resolved':\n case 'closed':\n return claim.resolutionSummary ?? t('warranty_claims.portal.tracker.banner.resolved')\n case 'rejected': {\n const reason = claim.rejectionReasonCode\n ? localizeDictionaryLabel(t, 'rejection', claim.rejectionReasonCode, claim.rejectionReasonLabel || claim.rejectionReasonCode)\n : null\n const base = t('warranty_claims.portal.detail.terminal.rejected')\n const parts = [base, reason, claim.resolutionSummary].filter((part): part is string => Boolean(part))\n return parts.join(' ')\n }\n case 'cancelled':\n return t('warranty_claims.portal.detail.terminal.cancelled')\n default:\n return t('warranty_claims.portal.tracker.banner.review')\n }\n}\n\nfunction formatShortDate(value: string | null, locale: string): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return date.toLocaleDateString(locale || undefined, { month: 'short', day: 'numeric' })\n}\n\nfunction formatLongDate(value: string | null, locale: string): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return date.toLocaleDateString(locale || undefined, { year: 'numeric', month: 'short', day: 'numeric' })\n}\n\nfunction buildTrackerSteps(claim: PortalClaim, t: TranslateFn, locale: string): TrackerStep[] {\n const currentIndex = STATUS_STEP_INDEX[claim.status] ?? 1\n const terminalComplete = claim.status === 'resolved' || claim.status === 'closed'\n return TRACKER_STEP_KEYS.map((key, index) => {\n const state: TrackerStepState = index < currentIndex || (terminalComplete && index === currentIndex)\n ? 'complete'\n : index === currentIndex\n ? 'current'\n : 'pending'\n let dateLabel: string | null = null\n if (index === 0 && state === 'complete') dateLabel = formatShortDate(claim.submittedAt, locale)\n if (index === TRACKER_STEP_KEYS.length - 1 && state === 'complete') {\n dateLabel = formatShortDate(\n claim.status === 'closed'\n ? claim.closedAt ?? claim.resolvedAt\n : claim.resolvedAt ?? claim.closedAt,\n locale,\n )\n }\n if (state === 'current') dateLabel = t('warranty_claims.portal.tracker.step.now')\n return {\n id: key,\n title: t(`warranty_claims.portal.tracker.step.${key}.title`),\n description: t(`warranty_claims.portal.tracker.step.${key}.description`),\n state,\n dateLabel,\n }\n })\n}\n\nfunction buildClaimTitle(claim: PortalClaim, reasonOptions: PortalOption[], t: TranslateFn): string {\n const typeLabel = t(`warranty_claims.claimType.${claim.claimType}`)\n if (claim.reasonCode) {\n const reasonCode = claim.reasonCode\n const dictionaryLabel = reasonOptions.find((option) => option.value === reasonCode)?.label ?? reasonCode\n const reasonLabel = localizeDictionaryLabel(t, 'reason', reasonCode, dictionaryLabel)\n return t('warranty_claims.portal.tracker.titleWithReason', { reason: reasonLabel, type: typeLabel })\n }\n return t('warranty_claims.portal.tracker.titleFallback', { type: typeLabel })\n}\n\nfunction buildClaimSubtitle(claim: PortalClaim, t: TranslateFn, locale: string): string {\n if (claim.status === 'draft') {\n const created = formatLongDate(claim.createdAt, locale)\n return created\n ? t('warranty_claims.portal.tracker.draftCreated', { date: created })\n : t('warranty_claims.portal.detail.notSubmitted')\n }\n const submitted = formatLongDate(claim.submittedAt, locale) ?? formatLongDate(claim.createdAt, locale)\n if (!submitted) return t('warranty_claims.portal.value.notAvailable')\n if (claim.orderNumber) {\n return t('warranty_claims.portal.tracker.submittedFromOrder', { date: submitted, order: claim.orderNumber })\n }\n return t('warranty_claims.portal.tracker.submittedOn', { date: submitted })\n}\n\nfunction formatEventBody(event: PortalClaimEvent, t: TranslateFn): string {\n if (event.body) return event.body\n const payload = event.payload\n const from = typeof payload?.from === 'string' ? payload.from : null\n const to = typeof payload?.to === 'string' ? payload.to : null\n if (from && to) {\n return t('warranty_claims.portal.event.transition', {\n from: t(`warranty_claims.status.${from}`),\n to: t(`warranty_claims.status.${to}`),\n })\n }\n return t('warranty_claims.portal.event.noDetails')\n}\n\nfunction eventTitle(event: PortalClaimEvent, t: TranslateFn): string {\n if (event.kind === 'comment') {\n return event.actorCustomerId\n ? t('warranty_claims.portal.tracker.event.youReplied')\n : t('warranty_claims.portal.tracker.event.teamReplied')\n }\n return t(`warranty_claims.portal.event.${event.kind}`)\n}\n\nfunction buildActivityEntries(\n events: PortalClaimEvent[],\n attachments: PortalAttachment[],\n t: TranslateFn,\n): ActivityEntry[] {\n const eventEntries: ActivityEntry[] = events.map((event) => ({\n id: `event-${event.id}`,\n title: eventTitle(event, t),\n description: formatEventBody(event, t),\n createdAt: event.createdAt,\n }))\n const attachmentEntries: ActivityEntry[] = attachments.map((attachment) => ({\n id: `attachment-${attachment.id}`,\n title: t('warranty_claims.portal.tracker.event.attachmentAdded'),\n description: attachment.fileName,\n createdAt: attachment.createdAt,\n href: attachment.downloadUrl,\n attachment,\n }))\n return [...eventEntries, ...attachmentEntries].sort((left, right) => {\n const leftTime = left.createdAt ? new Date(left.createdAt).getTime() : 0\n const rightTime = right.createdAt ? new Date(right.createdAt).getTime() : 0\n return rightTime - leftTime\n })\n}\n\nconst DETAIL_MUTATION_CONTEXT_ID = 'warranty_claims.portal.claim.detail'\n\nconst SECTION_CLASS = 'flex w-full flex-col gap-3 border-t border-border px-6 pb-6 pt-5'\nconst SECTION_HEADING_CLASS = 'text-sm font-semibold text-foreground'\nconst DARK_BUTTON_CLASS = 'rounded-md px-3 py-2 text-sm font-medium'\n\nexport default function WarrantyClaimPortalDetailPage({ params }: Props) {\n const t = useT()\n const locale = useLocale()\n const { auth } = usePortalContext()\n const { user, loading } = auth\n const guardedMutation = useGuardedMutation<Record<string, unknown>>({\n contextId: DETAIL_MUTATION_CONTEXT_ID,\n blockedMessage: t('warranty_claims.portal.detail.blocked'),\n })\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [claim, setClaim] = React.useState<PortalClaim | null>(null)\n const [events, setEvents] = React.useState<PortalClaimEvent[]>([])\n const [attachments, setAttachments] = React.useState<PortalAttachment[]>([])\n const [reasonOptions, setReasonOptions] = React.useState<PortalOption[]>([])\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [notFound, setNotFound] = React.useState(false)\n const [comment, setComment] = React.useState('')\n const [commentSubmitting, setCommentSubmitting] = React.useState(false)\n const [fileInputKey, setFileInputKey] = React.useState(0)\n const [uploading, setUploading] = React.useState(false)\n const [attachmentMutationId, setAttachmentMutationId] = React.useState<string | null>(null)\n const [claimAction, setClaimAction] = React.useState<PortalClaimAction | null>(null)\n const [actionSubmitting, setActionSubmitting] = React.useState(false)\n const replacementInputRef = React.useRef<HTMLInputElement | null>(null)\n const replacementTargetRef = React.useRef<PortalAttachment | null>(null)\n const claimRequestSequenceRef = React.useRef(0)\n const eventsRequestSequenceRef = React.useRef(0)\n\n // Leaving an authenticated page for the login page crosses the public/authenticated\n // boundary, so it must be a full page load \u2014 see `replaceWithPageReload`.\n React.useEffect(() => {\n if (!loading && !user) {\n replaceWithPageReload(`/${params.orgSlug}/portal/login`)\n }\n }, [loading, user, params.orgSlug])\n\n const refreshEvents = React.useCallback(async (claimId: string) => {\n const requestSequence = ++eventsRequestSequenceRef.current\n const result = await apiCall<EventResponse>(`/api/warranty_claims/portal/events?claimId=${encodeURIComponent(claimId)}`)\n if (requestSequence === eventsRequestSequenceRef.current && result.ok && result.result) {\n setEvents(result.result.items)\n }\n }, [])\n\n const refreshAttachments = React.useCallback(async (claimId: string) => {\n const result = await apiCall<AttachmentResponse>(`/api/warranty_claims/portal/attachments?claimId=${encodeURIComponent(claimId)}`)\n if (result.ok && result.result) setAttachments(result.result.items)\n }, [])\n\n const loadClaim = React.useCallback(async (showLoading = true) => {\n const requestSequence = ++claimRequestSequenceRef.current\n if (showLoading) setIsLoading(true)\n setError(null)\n setNotFound(false)\n try {\n const detail = await apiCall<ClaimResponse>(`/api/warranty_claims/portal/claims/${encodeURIComponent(params.id)}`)\n if (requestSequence !== claimRequestSequenceRef.current) return\n if (detail.status === 404) {\n setClaim(null)\n setNotFound(true)\n return\n }\n if (!detail.ok || !detail.result) {\n setError(t('warranty_claims.portal.detail.loadError'))\n return\n }\n setClaim(detail.result.item)\n await Promise.all([\n refreshEvents(detail.result.item.id),\n refreshAttachments(detail.result.item.id),\n ])\n } catch {\n if (requestSequence === claimRequestSequenceRef.current) {\n setError(t('warranty_claims.portal.detail.loadError'))\n }\n } finally {\n if (requestSequence === claimRequestSequenceRef.current) setIsLoading(false)\n }\n }, [params.id, refreshAttachments, refreshEvents, t])\n\n React.useEffect(() => {\n if (user) void loadClaim()\n }, [loadClaim, user])\n\n usePortalAppEvent('warranty_claims.claim.portal_status_changed', (event) => {\n if (!user || event.payload.claimId !== params.id) return\n void loadClaim(false)\n })\n\n usePortalAppEvent('warranty_claims.claim.comment_added', (event) => {\n if (!user || event.payload.claimId !== params.id) return\n void refreshEvents(params.id).catch((error) => {\n getTelemetryRuntime()?.reportError(error, {\n module: 'warranty_claims',\n code: 'warranty_claims.portal_timeline_refresh_failed',\n attributes: { claimId: params.id },\n })\n setError(t('warranty_claims.portal.detail.loadError'))\n })\n })\n\n React.useEffect(() => {\n if (!user) return\n let cancelled = false\n apiCall<PortalOptionsResponse>('/api/warranty_claims/portal/options')\n .then((result) => {\n if (cancelled) return\n setReasonOptions(result.result?.ok ? result.result.result?.reasons ?? [] : [])\n })\n .catch(() => {})\n return () => {\n cancelled = true\n }\n }, [user])\n\n const submitComment = React.useCallback(async () => {\n if (!claim || commentSubmitting) return\n const body = comment.trim()\n if (!body) return\n setCommentSubmitting(true)\n setError(null)\n try {\n const result = await guardedMutation.runMutation({\n operation: () => apiCall<MutationOkResponse>('/api/warranty_claims/portal/events', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({ claimId: claim.id, body }),\n }),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'warranty_claims.claim_event',\n operation: 'portal_comment',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'warranty_claims.claim_event',\n resourceId: claim.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: { claimId: claim.id, body },\n })\n if (!result.ok || !result.result?.ok) {\n setError(t('warranty_claims.portal.detail.commentError'))\n return\n }\n setComment('')\n flash(t('warranty_claims.portal.detail.commentSuccess'), 'success')\n await refreshEvents(claim.id)\n } catch {\n setError(t('warranty_claims.portal.detail.commentError'))\n } finally {\n setCommentSubmitting(false)\n }\n }, [claim, comment, commentSubmitting, guardedMutation, refreshEvents, t])\n\n const uploadAttachment = React.useCallback(async (file: File, replacementTarget?: PortalAttachment) => {\n if (!claim || uploading) return\n const validationError = await validateAttachmentFile(file, t)\n if (validationError) {\n setError(validationError)\n flash(validationError, 'error')\n return\n }\n setUploading(true)\n setAttachmentMutationId(replacementTarget?.id ?? 'new')\n setError(null)\n try {\n const form = new FormData()\n form.set('claimId', claim.id)\n form.set('file', file)\n if (replacementTarget) form.set('attachmentId', replacementTarget.id)\n const result = await guardedMutation.runMutation({\n operation: () => apiCall<UploadResponse>('/api/warranty_claims/portal/attachments', {\n method: replacementTarget ? 'PUT' : 'POST',\n credentials: 'include',\n body: form,\n }),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'attachments.attachment',\n operation: replacementTarget ? 'portal_attachment_replace' : 'portal_attachment_upload',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'attachments.attachment',\n resourceId: replacementTarget?.id ?? claim.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: {\n claimId: claim.id,\n attachmentId: replacementTarget?.id,\n fileName: file.name,\n fileSize: file.size,\n },\n })\n if (!result.ok || !result.result?.ok) {\n setError(replacementTarget\n ? t('warranty_claims.portal.detail.attachmentReplaceError', 'Attachment replacement failed.')\n : t('warranty_claims.portal.detail.attachmentError'))\n return\n }\n flash(\n replacementTarget\n ? t('warranty_claims.portal.detail.attachmentReplaceSuccess', 'Attachment replaced.')\n : t('warranty_claims.portal.detail.attachmentSuccess'),\n 'success',\n )\n await refreshAttachments(claim.id)\n } catch {\n setError(replacementTarget\n ? t('warranty_claims.portal.detail.attachmentReplaceError', 'Attachment replacement failed.')\n : t('warranty_claims.portal.detail.attachmentError'))\n } finally {\n setUploading(false)\n setAttachmentMutationId(null)\n setFileInputKey((current) => current + 1)\n }\n }, [claim, guardedMutation, refreshAttachments, t, uploading])\n\n const deleteAttachment = React.useCallback(async (attachment: PortalAttachment) => {\n if (!claim || uploading) return\n const confirmed = await confirm({\n title: t('warranty_claims.portal.detail.attachmentDelete', 'Delete attachment'),\n text: t(\n 'warranty_claims.portal.detail.attachmentDeleteConfirm',\n 'Delete \"{name}\"? This action cannot be undone.',\n { name: attachment.fileName },\n ),\n confirmText: t('warranty_claims.portal.detail.attachmentDelete', 'Delete attachment'),\n cancelText: t('warranty_claims.portal.detail.dialogCancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!confirmed) return\n setUploading(true)\n setAttachmentMutationId(attachment.id)\n setError(null)\n try {\n const result = await guardedMutation.runMutation({\n operation: () => apiCall<MutationOkResponse>(\n `/api/warranty_claims/portal/attachments?attachmentId=${encodeURIComponent(attachment.id)}`,\n { method: 'DELETE', credentials: 'include' },\n ),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'attachments.attachment',\n operation: 'portal_attachment_delete',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'attachments.attachment',\n resourceId: attachment.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: { claimId: claim.id, attachmentId: attachment.id },\n })\n if (!result.ok || !result.result?.ok) {\n setError(t('warranty_claims.portal.detail.attachmentDeleteError', 'Attachment deletion failed.'))\n return\n }\n flash(t('warranty_claims.portal.detail.attachmentDeleteSuccess', 'Attachment deleted.'), 'success')\n await refreshAttachments(claim.id)\n } catch {\n setError(t('warranty_claims.portal.detail.attachmentDeleteError', 'Attachment deletion failed.'))\n } finally {\n setUploading(false)\n setAttachmentMutationId(null)\n }\n }, [claim, confirm, guardedMutation, refreshAttachments, t, uploading])\n\n const requestAttachmentReplacement = React.useCallback((attachment: PortalAttachment) => {\n if (uploading) return\n replacementTargetRef.current = attachment\n replacementInputRef.current?.click()\n }, [uploading])\n\n const runClaimAction = React.useCallback(async (action: PortalClaimAction) => {\n if (!claim || actionSubmitting) return\n setActionSubmitting(true)\n setError(null)\n try {\n const endpoint = `/api/warranty_claims/portal/claims/${encodeURIComponent(claim.id)}/${action}`\n const result = await guardedMutation.runMutation({\n operation: () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader(claim.updatedAt),\n () => apiCall<ClaimActionResponse>(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({}),\n }),\n ),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'warranty_claims.claim',\n operation: action === 'submit' ? 'portal_submit' : 'portal_withdraw',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'warranty_claims.claim',\n resourceId: claim.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: { claimId: claim.id, action },\n })\n if (!result.ok || !result.result?.ok) {\n const message = result.status === 409\n ? t('warranty_claims.portal.detail.actionConflict', 'This claim changed in the meantime. It has been reloaded \u2014 please try again.')\n : action === 'submit'\n ? t('warranty_claims.portal.detail.submitError', 'The claim could not be submitted.')\n : t('warranty_claims.portal.detail.withdrawError', 'The claim could not be withdrawn.')\n setError(message)\n flash(message, 'error')\n if (result.status === 409) {\n setClaimAction(null)\n await loadClaim()\n }\n return\n }\n setClaimAction(null)\n flash(\n action === 'submit'\n ? t('warranty_claims.portal.detail.submitSuccess', 'Your claim has been submitted.')\n : t('warranty_claims.portal.detail.withdrawSuccess', 'Your claim has been withdrawn.'),\n 'success',\n )\n await loadClaim()\n } catch {\n const message = action === 'submit'\n ? t('warranty_claims.portal.detail.submitError', 'The claim could not be submitted.')\n : t('warranty_claims.portal.detail.withdrawError', 'The claim could not be withdrawn.')\n setError(message)\n flash(message, 'error')\n } finally {\n setActionSubmitting(false)\n }\n }, [actionSubmitting, claim, guardedMutation, loadClaim, t])\n\n const handleSelectedFileChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0] ?? null\n if (!file) return\n void uploadAttachment(file)\n }, [uploadAttachment])\n\n const handleReplacementFileChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n const replacementTarget = replacementTargetRef.current\n replacementTargetRef.current = null\n const file = event.target.files?.[0] ?? null\n event.target.value = ''\n if (!replacementTarget || !file) return\n void uploadAttachment(file, replacementTarget)\n }, [uploadAttachment])\n\n const handleCommentSubmit = React.useCallback((event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n void submitComment()\n }, [submitComment])\n\n const handleCommentKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void submitComment()\n return\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n setComment('')\n }\n }, [submitComment])\n\n if (loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (!user) return null\n\n if (isLoading) {\n return <LoadingMessage label={t('warranty_claims.portal.detail.loading')} />\n }\n\n if (notFound) {\n return (\n <PortalEmptyState\n icon={<ShieldCheck className=\"size-5\" />}\n title={t('warranty_claims.portal.detail.notFoundTitle')}\n description={t('warranty_claims.portal.detail.notFoundDescription')}\n action={\n <Button asChild>\n <Link href={`/${params.orgSlug}/portal/claims`}>\n <ArrowLeft className=\"size-4\" aria-hidden=\"true\" />\n {t('warranty_claims.portal.detail.back')}\n </Link>\n </Button>\n }\n />\n )\n }\n\n if (!claim) {\n return <ErrorMessage label={error ?? t('warranty_claims.portal.detail.loadError')} />\n }\n\n const trackerSteps = buildTrackerSteps(claim, t, locale)\n const isDraft = claim.status === 'draft'\n const canWithdraw = claim.status === 'draft' || claim.status === 'submitted'\n const tone = bannerTone(claim.status)\n const toneClasses = BANNER_TONE_CLASSES[tone]\n const BannerIcon = tone === 'error' ? CircleAlert : tone === 'warning' ? TriangleAlert : Info\n const activityEntries = buildActivityEntries(events, attachments, t)\n\n return (\n <div className=\"mx-auto flex w-full max-w-3xl flex-col gap-4\">\n <Link\n href={`/${params.orgSlug}/portal/claims`}\n className=\"text-sm font-medium text-muted-foreground transition-colors hover:text-foreground\"\n >\n {'\u2190'} {t('warranty_claims.portal.listTitle')}\n </Link>\n\n {error ? <ErrorMessage label={error} /> : null}\n\n <div className=\"w-full overflow-hidden rounded-lg border border-border bg-card\">\n <div className=\"flex w-full flex-col gap-3 px-6 pb-5 pt-6\">\n <div className=\"flex w-full items-center gap-2\">\n <div className=\"flex items-center gap-2 rounded-sm border border-border bg-background px-3 py-1\">\n <span className=\"text-xs font-medium text-muted-foreground\">\n {t(`warranty_claims.claimType.${claim.claimType}`)}\n </span>\n <span className=\"text-sm font-semibold text-foreground\">\n {claim.claimNumber}\n </span>\n </div>\n <div className=\"min-w-px flex-1\" />\n {isDraft ? (\n <Button\n type=\"button\"\n size=\"sm\"\n className={DARK_BUTTON_CLASS}\n onClick={() => setClaimAction('submit')}\n disabled={actionSubmitting}\n >\n {t('warranty_claims.portal.submit')}\n </Button>\n ) : null}\n {canWithdraw ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"destructive\"\n className=\"rounded-md px-3 py-2 text-sm font-medium\"\n onClick={() => setClaimAction('withdraw')}\n disabled={actionSubmitting}\n >\n {t('warranty_claims.portal.detail.withdraw', 'Withdraw claim')}\n </Button>\n ) : null}\n </div>\n <h1 className=\"text-xl font-bold text-foreground\">\n {buildClaimTitle(claim, reasonOptions, t)}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {buildClaimSubtitle(claim, t, locale)}\n </p>\n </div>\n\n <div className=\"w-full px-6 pb-6\">\n <div className={`flex w-full items-start gap-3 rounded-lg px-4 py-3 ${toneClasses.wrapper}`}>\n <BannerIcon className={`mt-px size-4 shrink-0 ${toneClasses.icon}`} aria-hidden=\"true\" />\n <p className={`min-w-px flex-1 text-sm ${toneClasses.text}`}>\n {bannerMessage(claim, t)}\n </p>\n </div>\n </div>\n\n <div className=\"flex w-full flex-col px-6 pb-6\">\n {trackerSteps.map((step, index) => (\n <div key={step.id} className=\"flex w-full items-stretch gap-4\">\n <div className=\"flex flex-col items-center\">\n {step.state === 'complete' ? (\n <span className=\"flex size-6 shrink-0 items-center justify-center rounded-full bg-status-success-icon\">\n <Check className=\"size-3 text-status-success-bg\" strokeWidth={2.5} aria-hidden=\"true\" />\n </span>\n ) : step.state === 'current' ? (\n <span className=\"flex size-6 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-semibold text-primary-foreground\">\n {index + 1}\n </span>\n ) : (\n <span className=\"flex size-6 shrink-0 items-center justify-center rounded-full border border-border text-xs font-semibold text-muted-foreground\">\n {index + 1}\n </span>\n )}\n {index < trackerSteps.length - 1 ? (\n <span\n className={`min-h-2 w-px flex-1 ${step.state === 'complete' ? 'bg-status-success-icon' : 'bg-border'}`}\n />\n ) : null}\n </div>\n <div className=\"flex min-w-px flex-1 flex-col gap-1 pb-4\">\n <div className=\"flex w-full items-center gap-2\">\n <p\n className={\n step.state === 'pending'\n ? 'text-sm font-medium text-muted-foreground'\n : 'text-sm font-semibold text-foreground'\n }\n >\n {step.title}\n </p>\n <div className=\"min-w-px flex-1\" />\n {step.dateLabel ? (\n <p className=\"text-xs text-muted-foreground\">{step.dateLabel}</p>\n ) : null}\n </div>\n <p className=\"w-full text-xs text-muted-foreground\">{step.description}</p>\n </div>\n </div>\n ))}\n </div>\n\n <div className={SECTION_CLASS}>\n <p className={SECTION_HEADING_CLASS}>{t('warranty_claims.portal.tracker.items')}</p>\n {claim.lines.length > 0 ? (\n claim.lines.map((line) => (\n <div\n key={line.id}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border px-4 py-3\"\n >\n <div className=\"flex min-w-px flex-1 flex-col gap-1\">\n <p className=\"text-sm font-medium text-foreground\">\n {line.productName ?? line.sku ?? t('warranty_claims.portal.value.unnamedLine')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {line.sku\n ? t('warranty_claims.portal.tracker.itemMeta', {\n sku: line.sku,\n qty: formatQuantity(line.qtyClaimed, t('warranty_claims.portal.value.notAvailable')),\n })\n : t('warranty_claims.portal.tracker.itemQty', {\n qty: formatQuantity(line.qtyClaimed, t('warranty_claims.portal.value.notAvailable')),\n })}\n </p>\n </div>\n <div className=\"flex shrink-0 items-center gap-2 rounded-sm border border-border bg-background px-2 py-1\">\n <span\n className={`size-2 shrink-0 rounded-full ${LINE_STATUS_DOT_CLASSES[line.lineStatus] ?? 'bg-status-neutral-icon'}`}\n aria-hidden=\"true\"\n />\n <span className=\"text-xs font-medium text-foreground\">\n {t(`warranty_claims.lineStatus.${line.lineStatus}`)}\n </span>\n </div>\n </div>\n ))\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t('warranty_claims.portal.detail.linesEmpty.description')}\n </p>\n )}\n </div>\n\n <div className={SECTION_CLASS}>\n <p className={SECTION_HEADING_CLASS}>{t('warranty_claims.portal.tracker.activity')}</p>\n {activityEntries.length > 0 ? (\n activityEntries.map((entry) => (\n <div key={entry.id} className=\"flex w-full items-start gap-3\">\n <span className=\"mt-1 size-2 shrink-0 rounded-full bg-status-neutral-icon\" aria-hidden=\"true\" />\n <div className=\"flex min-w-px flex-1 flex-col gap-px\">\n <div className=\"flex w-full items-start gap-2\">\n <p className=\"text-sm font-medium text-foreground\">{entry.title}</p>\n <div className=\"min-w-px flex-1\" />\n {entry.createdAt ? (\n <p className=\"text-xs text-muted-foreground\">{formatShortDate(entry.createdAt, locale)}</p>\n ) : null}\n </div>\n {entry.href ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <a\n href={entry.href}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"min-w-0 whitespace-pre-wrap text-xs text-muted-foreground underline-offset-2 hover:underline\"\n >\n {entry.description}\n </a>\n {entry.attachment ? (\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"px-2 text-xs\"\n aria-label={t(\n 'warranty_claims.portal.detail.attachmentReplaceNamed',\n 'Replace {name}',\n { name: entry.attachment.fileName },\n )}\n onClick={() => requestAttachmentReplacement(entry.attachment!)}\n disabled={uploading}\n >\n {attachmentMutationId === entry.attachment.id ? (\n <Spinner className=\"size-3\" />\n ) : (\n <RefreshCw className=\"size-3\" aria-hidden=\"true\" />\n )}\n {t('warranty_claims.portal.detail.attachmentReplace', 'Replace')}\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"px-2 text-xs text-status-error-text\"\n aria-label={t(\n 'warranty_claims.portal.detail.attachmentDeleteNamed',\n 'Delete {name}',\n { name: entry.attachment.fileName },\n )}\n onClick={() => { void deleteAttachment(entry.attachment!) }}\n disabled={uploading}\n >\n <Trash2 className=\"size-3\" aria-hidden=\"true\" />\n {t('warranty_claims.portal.detail.attachmentDelete', 'Delete attachment')}\n </Button>\n </div>\n ) : null}\n </div>\n ) : (\n <p className=\"whitespace-pre-wrap text-xs text-muted-foreground\">{entry.description}</p>\n )}\n </div>\n </div>\n ))\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t('warranty_claims.portal.detail.timelineEmpty.description')}\n </p>\n )}\n <input\n ref={replacementInputRef}\n type=\"file\"\n accept={ATTACHMENT_ACCEPT_TYPES}\n onChange={handleReplacementFileChange}\n disabled={uploading}\n className=\"sr-only\"\n aria-label={t('warranty_claims.portal.detail.attachmentChooseReplacement', 'Choose replacement file')}\n />\n </div>\n\n <div className={SECTION_CLASS}>\n <p className={SECTION_HEADING_CLASS}>{t('warranty_claims.portal.tracker.message')}</p>\n <form id=\"warranty-claim-comment-box\" className=\"flex w-full flex-col gap-3\" onSubmit={handleCommentSubmit}>\n <Textarea\n value={comment}\n onChange={(event) => setComment(event.target.value)}\n onKeyDown={handleCommentKeyDown}\n placeholder={t('warranty_claims.portal.tracker.messagePlaceholder')}\n disabled={commentSubmitting}\n maxLength={8000}\n className=\"h-40 resize-none rounded-lg border-input bg-background px-3 py-2 text-sm shadow-xs placeholder:text-muted-foreground\"\n />\n <div className=\"flex w-full items-center gap-2\">\n <label className={`flex items-center gap-2 ${uploading ? 'cursor-default opacity-70' : 'cursor-pointer'}`}>\n {uploading ? (\n <Spinner className=\"size-4\" />\n ) : (\n <Paperclip className=\"size-4 text-muted-foreground\" aria-hidden=\"true\" />\n )}\n <span className=\"text-xs font-medium text-muted-foreground transition-colors hover:text-foreground\">\n {uploading\n ? t('warranty_claims.portal.detail.uploading')\n : t('warranty_claims.portal.tracker.attachFiles')}\n </span>\n <input\n key={fileInputKey}\n type=\"file\"\n accept={ATTACHMENT_ACCEPT_TYPES}\n onChange={handleSelectedFileChange}\n disabled={uploading}\n className=\"sr-only\"\n />\n </label>\n <div className=\"min-w-px flex-1\" />\n <Button type=\"submit\" size=\"sm\" className={DARK_BUTTON_CLASS} disabled={commentSubmitting || !comment.trim()}>\n {commentSubmitting\n ? t('warranty_claims.portal.tracker.sending')\n : t('warranty_claims.portal.tracker.send')}\n </Button>\n </div>\n </form>\n </div>\n </div>\n\n <Dialog\n open={claimAction !== null}\n onOpenChange={(open) => {\n if (!open && !actionSubmitting) setClaimAction(null)\n }}\n >\n <DialogContent\n className=\"max-w-lg\"\n onKeyDown={(event) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n if (claimAction && !actionSubmitting) void runClaimAction(claimAction)\n }\n if (event.key === 'Escape' && !actionSubmitting) {\n setClaimAction(null)\n }\n }}\n >\n <DialogHeader>\n <DialogTitle>\n {claimAction === 'withdraw'\n ? t('warranty_claims.portal.detail.withdrawDialog.title', 'Withdraw this claim?')\n : t('warranty_claims.portal.detail.submitDialog.title', 'Submit this claim?')}\n </DialogTitle>\n <DialogDescription>\n {claimAction === 'withdraw'\n ? t('warranty_claims.portal.detail.withdrawDialog.description', 'The claim will be cancelled and cannot be reopened. This cannot be undone.')\n : t('warranty_claims.portal.detail.submitDialog.description', 'Your claim will be sent to our support team for review. You can still add comments and photos afterwards.')}\n </DialogDescription>\n </DialogHeader>\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setClaimAction(null)}\n disabled={actionSubmitting}\n >\n {t('warranty_claims.portal.detail.dialogCancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n variant={claimAction === 'withdraw' ? 'destructive' : 'default'}\n onClick={() => {\n if (claimAction) void runClaimAction(claimAction)\n }}\n disabled={actionSubmitting}\n >\n {claimAction === 'withdraw'\n ? (actionSubmitting\n ? t('warranty_claims.portal.detail.withdrawing', 'Withdrawing...')\n : t('warranty_claims.portal.detail.withdrawConfirm', 'Withdraw claim'))\n : (actionSubmitting\n ? t('warranty_claims.portal.detail.submitting', 'Submitting...')\n : t('warranty_claims.portal.detail.submitConfirm', 'Submit claim'))}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
+ "mappings": ";AAgsBmE,cAiBvD,YAjBuD;AA9rBnE,YAAY,WAAW;AACvB,OAAO,UAAU;AACjB,SAAS,WAAW,OAAO,aAAa,MAAM,WAAW,WAAW,aAAa,QAAQ,qBAAqB;AAC9G,SAAS,6BAA6B;AACtC,SAAS,WAAW,YAA8B;AAClD,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AACxC,SAAS,sBAAsB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,SAAS,cAAc,sBAAsB;AAC7C,SAAS,aAAa;AACtB,SAAS,wBAAwB;AACjC,SAAS,0BAA0B;AACnC,SAAS,SAAS,mCAAmC;AACrD,SAAS,iCAAiC;AAC1C,SAAS,wBAAwB;AACjC,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AA2FlC,MAAM,oBAAoB,CAAC,YAAY,UAAU,YAAY,UAAU,UAAU;AAEjF,MAAM,oBAA4C;AAAA,EAChD,OAAO;AAAA,EACP,WAAW;AAAA,EACX,WAAW;AAAA,EACX,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,WAAW;AAAA,EACX,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,QAAQ;AACV;AAEA,MAAM,0BAAkD;AAAA,EACtD,SAAS;AAAA,EACT,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,WAAW;AAAA,EACX,UAAU;AACZ;AAEA,MAAM,sBAA2F;AAAA,EAC/F,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AAAA,EACA,SAAS;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AAAA,EACA,SAAS;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AAAA,EACA,OAAO;AAAA,IACL,SAAS;AAAA,IACT,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AACF;AAEA,SAAS,WAAW,QAA4B;AAC9C,MAAI,WAAW,cAAc,WAAW,YAAa,QAAO;AAC5D,MAAI,WAAW,iBAAkB,QAAO;AACxC,MAAI,WAAW,cAAc,WAAW,cAAc,WAAW,SAAU,QAAO;AAClF,SAAO;AACT;AAEA,SAAS,cAAc,OAAoB,GAAwB;AACjE,UAAQ,MAAM,QAAQ;AAAA,IACpB,KAAK;AACH,aAAO,EAAE,6CAA6C;AAAA,IACxD,KAAK;AACH,aAAO,EAAE,qDAAqD;AAAA,IAChE,KAAK;AACH,aAAO,EAAE,gDAAgD;AAAA,IAC3D,KAAK;AACH,aAAO,EAAE,sDAAsD;AAAA,IACjE,KAAK;AAAA,IACL,KAAK;AACH,aAAO,EAAE,iDAAiD;AAAA,IAC5D,KAAK;AAAA,IACL,KAAK;AACH,aAAO,MAAM,qBAAqB,EAAE,gDAAgD;AAAA,IACtF,KAAK,YAAY;AACf,YAAM,SAAS,MAAM,sBACjB,wBAAwB,GAAG,aAAa,MAAM,qBAAqB,MAAM,wBAAwB,MAAM,mBAAmB,IAC1H;AACJ,YAAM,OAAO,EAAE,iDAAiD;AAChE,YAAM,QAAQ,CAAC,MAAM,QAAQ,MAAM,iBAAiB,EAAE,OAAO,CAAC,SAAyB,QAAQ,IAAI,CAAC;AACpG,aAAO,MAAM,KAAK,GAAG;AAAA,IACvB;AAAA,IACA,KAAK;AACH,aAAO,EAAE,kDAAkD;AAAA,IAC7D;AACE,aAAO,EAAE,8CAA8C;AAAA,EAC3D;AACF;AAEA,SAAS,gBAAgB,OAAsB,QAA+B;AAC5E,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,QAAO;AACzC,SAAO,KAAK,mBAAmB,UAAU,QAAW,EAAE,OAAO,SAAS,KAAK,UAAU,CAAC;AACxF;AAEA,SAAS,eAAe,OAAsB,QAA+B;AAC3E,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,QAAO;AACzC,SAAO,KAAK,mBAAmB,UAAU,QAAW,EAAE,MAAM,WAAW,OAAO,SAAS,KAAK,UAAU,CAAC;AACzG;AAEA,SAAS,kBAAkB,OAAoB,GAAgB,QAA+B;AAC5F,QAAM,eAAe,kBAAkB,MAAM,MAAM,KAAK;AACxD,QAAM,mBAAmB,MAAM,WAAW,cAAc,MAAM,WAAW;AACzE,SAAO,kBAAkB,IAAI,CAAC,KAAK,UAAU;AAC3C,UAAM,QAA0B,QAAQ,gBAAiB,oBAAoB,UAAU,eACnF,aACA,UAAU,eACR,YACA;AACN,QAAI,YAA2B;AAC/B,QAAI,UAAU,KAAK,UAAU,WAAY,aAAY,gBAAgB,MAAM,aAAa,MAAM;AAC9F,QAAI,UAAU,kBAAkB,SAAS,KAAK,UAAU,YAAY;AAClE,kBAAY;AAAA,QACV,MAAM,WAAW,WACb,MAAM,YAAY,MAAM,aACxB,MAAM,cAAc,MAAM;AAAA,QAC9B;AAAA,MACF;AAAA,IACF;AACA,QAAI,UAAU,UAAW,aAAY,EAAE,yCAAyC;AAChF,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO,EAAE,uCAAuC,GAAG,QAAQ;AAAA,MAC3D,aAAa,EAAE,uCAAuC,GAAG,cAAc;AAAA,MACvE;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,SAAS,gBAAgB,OAAoB,eAA+B,GAAwB;AAClG,QAAM,YAAY,EAAE,6BAA6B,MAAM,SAAS,EAAE;AAClE,MAAI,MAAM,YAAY;AACpB,UAAM,aAAa,MAAM;AACzB,UAAM,kBAAkB,cAAc,KAAK,CAAC,WAAW,OAAO,UAAU,UAAU,GAAG,SAAS;AAC9F,UAAM,cAAc,wBAAwB,GAAG,UAAU,YAAY,eAAe;AACpF,WAAO,EAAE,kDAAkD,EAAE,QAAQ,aAAa,MAAM,UAAU,CAAC;AAAA,EACrG;AACA,SAAO,EAAE,gDAAgD,EAAE,MAAM,UAAU,CAAC;AAC9E;AAEA,SAAS,mBAAmB,OAAoB,GAAgB,QAAwB;AACtF,MAAI,MAAM,WAAW,SAAS;AAC5B,UAAM,UAAU,eAAe,MAAM,WAAW,MAAM;AACtD,WAAO,UACH,EAAE,+CAA+C,EAAE,MAAM,QAAQ,CAAC,IAClE,EAAE,4CAA4C;AAAA,EACpD;AACA,QAAM,YAAY,eAAe,MAAM,aAAa,MAAM,KAAK,eAAe,MAAM,WAAW,MAAM;AACrG,MAAI,CAAC,UAAW,QAAO,EAAE,2CAA2C;AACpE,MAAI,MAAM,aAAa;AACrB,WAAO,EAAE,qDAAqD,EAAE,MAAM,WAAW,OAAO,MAAM,YAAY,CAAC;AAAA,EAC7G;AACA,SAAO,EAAE,8CAA8C,EAAE,MAAM,UAAU,CAAC;AAC5E;AAEA,SAAS,gBAAgB,OAAyB,GAAwB;AACxE,MAAI,MAAM,KAAM,QAAO,MAAM;AAC7B,QAAM,UAAU,MAAM;AACtB,QAAM,OAAO,OAAO,SAAS,SAAS,WAAW,QAAQ,OAAO;AAChE,QAAM,KAAK,OAAO,SAAS,OAAO,WAAW,QAAQ,KAAK;AAC1D,MAAI,QAAQ,IAAI;AACd,WAAO,EAAE,2CAA2C;AAAA,MAClD,MAAM,EAAE,0BAA0B,IAAI,EAAE;AAAA,MACxC,IAAI,EAAE,0BAA0B,EAAE,EAAE;AAAA,IACtC,CAAC;AAAA,EACH;AACA,SAAO,EAAE,wCAAwC;AACnD;AAEA,SAAS,WAAW,OAAyB,GAAwB;AACnE,MAAI,MAAM,SAAS,WAAW;AAC5B,WAAO,MAAM,kBACT,EAAE,iDAAiD,IACnD,EAAE,kDAAkD;AAAA,EAC1D;AACA,SAAO,EAAE,gCAAgC,MAAM,IAAI,EAAE;AACvD;AAEA,SAAS,qBACP,QACA,aACA,GACiB;AACjB,QAAM,eAAgC,OAAO,IAAI,CAAC,WAAW;AAAA,IAC3D,IAAI,SAAS,MAAM,EAAE;AAAA,IACrB,OAAO,WAAW,OAAO,CAAC;AAAA,IAC1B,aAAa,gBAAgB,OAAO,CAAC;AAAA,IACrC,WAAW,MAAM;AAAA,EACnB,EAAE;AACF,QAAM,oBAAqC,YAAY,IAAI,CAAC,gBAAgB;AAAA,IAC1E,IAAI,cAAc,WAAW,EAAE;AAAA,IAC/B,OAAO,EAAE,sDAAsD;AAAA,IAC/D,aAAa,WAAW;AAAA,IACxB,WAAW,WAAW;AAAA,IACtB,MAAM,WAAW;AAAA,IACjB;AAAA,EACF,EAAE;AACF,SAAO,CAAC,GAAG,cAAc,GAAG,iBAAiB,EAAE,KAAK,CAAC,MAAM,UAAU;AACnE,UAAM,WAAW,KAAK,YAAY,IAAI,KAAK,KAAK,SAAS,EAAE,QAAQ,IAAI;AACvE,UAAM,YAAY,MAAM,YAAY,IAAI,KAAK,MAAM,SAAS,EAAE,QAAQ,IAAI;AAC1E,WAAO,YAAY;AAAA,EACrB,CAAC;AACH;AAEA,MAAM,6BAA6B;AAEnC,MAAM,gBAAgB;AACtB,MAAM,wBAAwB;AAC9B,MAAM,oBAAoB;AAEX,SAAR,8BAA+C,EAAE,OAAO,GAAU;AACvE,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,UAAU;AACzB,QAAM,EAAE,KAAK,IAAI,iBAAiB;AAClC,QAAM,EAAE,MAAM,QAAQ,IAAI;AAC1B,QAAM,kBAAkB,mBAA4C;AAAA,IAClE,WAAW;AAAA,IACX,gBAAgB,EAAE,uCAAuC;AAAA,EAC3D,CAAC;AACD,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAC3D,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAA6B,IAAI;AACjE,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAA6B,CAAC,CAAC;AACjE,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAA6B,CAAC,CAAC;AAC3E,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAyB,CAAC,CAAC;AAC3E,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,IAAI;AACrD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AACpD,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,EAAE;AAC/C,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,KAAK;AACtE,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,CAAC;AACxD,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,KAAK;AACtD,QAAM,CAAC,sBAAsB,uBAAuB,IAAI,MAAM,SAAwB,IAAI;AAC1F,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAmC,IAAI;AACnF,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAS,KAAK;AACpE,QAAM,sBAAsB,MAAM,OAAgC,IAAI;AACtE,QAAM,uBAAuB,MAAM,OAAgC,IAAI;AACvE,QAAM,0BAA0B,MAAM,OAAO,CAAC;AAC9C,QAAM,2BAA2B,MAAM,OAAO,CAAC;AAI/C,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,WAAW,CAAC,MAAM;AACrB,4BAAsB,IAAI,OAAO,OAAO,eAAe;AAAA,IACzD;AAAA,EACF,GAAG,CAAC,SAAS,MAAM,OAAO,OAAO,CAAC;AAElC,QAAM,gBAAgB,MAAM,YAAY,OAAO,YAAoB;AACjE,UAAM,kBAAkB,EAAE,yBAAyB;AACnD,UAAM,SAAS,MAAM,QAAuB,8CAA8C,mBAAmB,OAAO,CAAC,EAAE;AACvH,QAAI,oBAAoB,yBAAyB,WAAW,OAAO,MAAM,OAAO,QAAQ;AACtF,gBAAU,OAAO,OAAO,KAAK;AAAA,IAC/B;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,qBAAqB,MAAM,YAAY,OAAO,YAAoB;AACtE,UAAM,SAAS,MAAM,QAA4B,mDAAmD,mBAAmB,OAAO,CAAC,EAAE;AACjI,QAAI,OAAO,MAAM,OAAO,OAAQ,gBAAe,OAAO,OAAO,KAAK;AAAA,EACpE,GAAG,CAAC,CAAC;AAEL,QAAM,YAAY,MAAM,YAAY,OAAO,cAAc,SAAS;AAChE,UAAM,kBAAkB,EAAE,wBAAwB;AAClD,QAAI,YAAa,cAAa,IAAI;AAClC,aAAS,IAAI;AACb,gBAAY,KAAK;AACjB,QAAI;AACF,YAAM,SAAS,MAAM,QAAuB,sCAAsC,mBAAmB,OAAO,EAAE,CAAC,EAAE;AACjH,UAAI,oBAAoB,wBAAwB,QAAS;AACzD,UAAI,OAAO,WAAW,KAAK;AACzB,iBAAS,IAAI;AACb,oBAAY,IAAI;AAChB;AAAA,MACF;AACA,UAAI,CAAC,OAAO,MAAM,CAAC,OAAO,QAAQ;AAChC,iBAAS,EAAE,yCAAyC,CAAC;AACrD;AAAA,MACF;AACA,eAAS,OAAO,OAAO,IAAI;AAC3B,YAAM,QAAQ,IAAI;AAAA,QAChB,cAAc,OAAO,OAAO,KAAK,EAAE;AAAA,QACnC,mBAAmB,OAAO,OAAO,KAAK,EAAE;AAAA,MAC1C,CAAC;AAAA,IACH,QAAQ;AACN,UAAI,oBAAoB,wBAAwB,SAAS;AACvD,iBAAS,EAAE,yCAAyC,CAAC;AAAA,MACvD;AAAA,IACF,UAAE;AACA,UAAI,oBAAoB,wBAAwB,QAAS,cAAa,KAAK;AAAA,IAC7E;AAAA,EACF,GAAG,CAAC,OAAO,IAAI,oBAAoB,eAAe,CAAC,CAAC;AAEpD,QAAM,UAAU,MAAM;AACpB,QAAI,KAAM,MAAK,UAAU;AAAA,EAC3B,GAAG,CAAC,WAAW,IAAI,CAAC;AAEpB,oBAAkB,+CAA+C,CAAC,UAAU;AAC1E,QAAI,CAAC,QAAQ,MAAM,QAAQ,YAAY,OAAO,GAAI;AAClD,SAAK,UAAU,KAAK;AAAA,EACtB,CAAC;AAED,oBAAkB,uCAAuC,CAAC,UAAU;AAClE,QAAI,CAAC,QAAQ,MAAM,QAAQ,YAAY,OAAO,GAAI;AAClD,SAAK,cAAc,OAAO,EAAE,EAAE,MAAM,CAACA,WAAU;AAC7C,0BAAoB,GAAG,YAAYA,QAAO;AAAA,QACxC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,YAAY,EAAE,SAAS,OAAO,GAAG;AAAA,MACnC,CAAC;AACD,eAAS,EAAE,yCAAyC,CAAC;AAAA,IACvD,CAAC;AAAA,EACH,CAAC;AAED,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,KAAM;AACX,QAAI,YAAY;AAChB,YAA+B,qCAAqC,EACjE,KAAK,CAAC,WAAW;AAChB,UAAI,UAAW;AACf,uBAAiB,OAAO,QAAQ,KAAK,OAAO,OAAO,QAAQ,WAAW,CAAC,IAAI,CAAC,CAAC;AAAA,IAC/E,CAAC,EACA,MAAM,MAAM;AAAA,IAAC,CAAC;AACjB,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,gBAAgB,MAAM,YAAY,YAAY;AAClD,QAAI,CAAC,SAAS,kBAAmB;AACjC,UAAM,OAAO,QAAQ,KAAK;AAC1B,QAAI,CAAC,KAAM;AACX,yBAAqB,IAAI;AACzB,aAAS,IAAI;AACb,QAAI;AACF,YAAM,SAAS,MAAM,gBAAgB,YAAY;AAAA,QAC/C,WAAW,MAAM,QAA4B,sCAAsC;AAAA,UACjF,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,aAAa;AAAA,UACb,MAAM,KAAK,UAAU,EAAE,SAAS,MAAM,IAAI,KAAK,CAAC;AAAA,QAClD,CAAC;AAAA,QACD,SAAS;AAAA,UACP,UAAU;AAAA,UACV,UAAU;AAAA,UACV,WAAW;AAAA,UACX,SAAS,MAAM;AAAA,UACf,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,YAAY,MAAM;AAAA,UAClB,mBAAmB,gBAAgB;AAAA,QACrC;AAAA,QACA,iBAAiB,EAAE,SAAS,MAAM,IAAI,KAAK;AAAA,MAC7C,CAAC;AACD,UAAI,CAAC,OAAO,MAAM,CAAC,OAAO,QAAQ,IAAI;AACpC,iBAAS,EAAE,4CAA4C,CAAC;AACxD;AAAA,MACF;AACA,iBAAW,EAAE;AACb,YAAM,EAAE,8CAA8C,GAAG,SAAS;AAClE,YAAM,cAAc,MAAM,EAAE;AAAA,IAC9B,QAAQ;AACN,eAAS,EAAE,4CAA4C,CAAC;AAAA,IAC1D,UAAE;AACA,2BAAqB,KAAK;AAAA,IAC5B;AAAA,EACF,GAAG,CAAC,OAAO,SAAS,mBAAmB,iBAAiB,eAAe,CAAC,CAAC;AAEzE,QAAM,mBAAmB,MAAM,YAAY,OAAO,MAAY,sBAAyC;AACrG,QAAI,CAAC,SAAS,UAAW;AACzB,UAAM,kBAAkB,MAAM,uBAAuB,MAAM,CAAC;AAC5D,QAAI,iBAAiB;AACnB,eAAS,eAAe;AACxB,YAAM,iBAAiB,OAAO;AAC9B;AAAA,IACF;AACA,iBAAa,IAAI;AACjB,4BAAwB,mBAAmB,MAAM,KAAK;AACtD,aAAS,IAAI;AACb,QAAI;AACF,YAAM,OAAO,IAAI,SAAS;AAC1B,WAAK,IAAI,WAAW,MAAM,EAAE;AAC5B,WAAK,IAAI,QAAQ,IAAI;AACrB,UAAI,kBAAmB,MAAK,IAAI,gBAAgB,kBAAkB,EAAE;AACpE,YAAM,SAAS,MAAM,gBAAgB,YAAY;AAAA,QAC/C,WAAW,MAAM,QAAwB,2CAA2C;AAAA,UAClF,QAAQ,oBAAoB,QAAQ;AAAA,UACpC,aAAa;AAAA,UACb,MAAM;AAAA,QACR,CAAC;AAAA,QACD,SAAS;AAAA,UACP,UAAU;AAAA,UACV,UAAU;AAAA,UACV,WAAW,oBAAoB,8BAA8B;AAAA,UAC7D,SAAS,MAAM;AAAA,UACf,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,YAAY,mBAAmB,MAAM,MAAM;AAAA,UAC3C,mBAAmB,gBAAgB;AAAA,QACrC;AAAA,QACA,iBAAiB;AAAA,UACf,SAAS,MAAM;AAAA,UACf,cAAc,mBAAmB;AAAA,UACjC,UAAU,KAAK;AAAA,UACf,UAAU,KAAK;AAAA,QACjB;AAAA,MACF,CAAC;AACD,UAAI,CAAC,OAAO,MAAM,CAAC,OAAO,QAAQ,IAAI;AACpC,iBAAS,oBACL,EAAE,wDAAwD,gCAAgC,IAC1F,EAAE,+CAA+C,CAAC;AACtD;AAAA,MACF;AACA;AAAA,QACE,oBACI,EAAE,0DAA0D,sBAAsB,IAClF,EAAE,iDAAiD;AAAA,QACvD;AAAA,MACF;AACA,YAAM,mBAAmB,MAAM,EAAE;AAAA,IACnC,QAAQ;AACN,eAAS,oBACL,EAAE,wDAAwD,gCAAgC,IAC1F,EAAE,+CAA+C,CAAC;AAAA,IACxD,UAAE;AACA,mBAAa,KAAK;AAClB,8BAAwB,IAAI;AAC5B,sBAAgB,CAAC,YAAY,UAAU,CAAC;AAAA,IAC1C;AAAA,EACF,GAAG,CAAC,OAAO,iBAAiB,oBAAoB,GAAG,SAAS,CAAC;AAE7D,QAAM,mBAAmB,MAAM,YAAY,OAAO,eAAiC;AACjF,QAAI,CAAC,SAAS,UAAW;AACzB,UAAM,YAAY,MAAM,QAAQ;AAAA,MAC9B,OAAO,EAAE,kDAAkD,mBAAmB;AAAA,MAC9E,MAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA,EAAE,MAAM,WAAW,SAAS;AAAA,MAC9B;AAAA,MACA,aAAa,EAAE,kDAAkD,mBAAmB;AAAA,MACpF,YAAY,EAAE,8CAA8C,QAAQ;AAAA,MACpE,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,UAAW;AAChB,iBAAa,IAAI;AACjB,4BAAwB,WAAW,EAAE;AACrC,aAAS,IAAI;AACb,QAAI;AACF,YAAM,SAAS,MAAM,gBAAgB,YAAY;AAAA,QAC/C,WAAW,MAAM;AAAA,UACf,wDAAwD,mBAAmB,WAAW,EAAE,CAAC;AAAA,UACzF,EAAE,QAAQ,UAAU,aAAa,UAAU;AAAA,QAC7C;AAAA,QACA,SAAS;AAAA,UACP,UAAU;AAAA,UACV,UAAU;AAAA,UACV,WAAW;AAAA,UACX,SAAS,MAAM;AAAA,UACf,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,YAAY,WAAW;AAAA,UACvB,mBAAmB,gBAAgB;AAAA,QACrC;AAAA,QACA,iBAAiB,EAAE,SAAS,MAAM,IAAI,cAAc,WAAW,GAAG;AAAA,MACpE,CAAC;AACD,UAAI,CAAC,OAAO,MAAM,CAAC,OAAO,QAAQ,IAAI;AACpC,iBAAS,EAAE,uDAAuD,6BAA6B,CAAC;AAChG;AAAA,MACF;AACA,YAAM,EAAE,yDAAyD,qBAAqB,GAAG,SAAS;AAClG,YAAM,mBAAmB,MAAM,EAAE;AAAA,IACnC,QAAQ;AACN,eAAS,EAAE,uDAAuD,6BAA6B,CAAC;AAAA,IAClG,UAAE;AACA,mBAAa,KAAK;AAClB,8BAAwB,IAAI;AAAA,IAC9B;AAAA,EACF,GAAG,CAAC,OAAO,SAAS,iBAAiB,oBAAoB,GAAG,SAAS,CAAC;AAEtE,QAAM,+BAA+B,MAAM,YAAY,CAAC,eAAiC;AACvF,QAAI,UAAW;AACf,yBAAqB,UAAU;AAC/B,wBAAoB,SAAS,MAAM;AAAA,EACrC,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,iBAAiB,MAAM,YAAY,OAAO,WAA8B;AAC5E,QAAI,CAAC,SAAS,iBAAkB;AAChC,wBAAoB,IAAI;AACxB,aAAS,IAAI;AACb,QAAI;AACF,YAAM,WAAW,sCAAsC,mBAAmB,MAAM,EAAE,CAAC,IAAI,MAAM;AAC7F,YAAM,SAAS,MAAM,gBAAgB,YAAY;AAAA,QAC/C,WAAW,MAAM;AAAA,UACf,0BAA0B,MAAM,SAAS;AAAA,UACzC,MAAM,QAA6B,UAAU;AAAA,YAC3C,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,aAAa;AAAA,YACb,MAAM,KAAK,UAAU,CAAC,CAAC;AAAA,UACzB,CAAC;AAAA,QACH;AAAA,QACA,SAAS;AAAA,UACP,UAAU;AAAA,UACV,UAAU;AAAA,UACV,WAAW,WAAW,WAAW,kBAAkB;AAAA,UACnD,SAAS,MAAM;AAAA,UACf,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,YAAY,MAAM;AAAA,UAClB,mBAAmB,gBAAgB;AAAA,QACrC;AAAA,QACA,iBAAiB,EAAE,SAAS,MAAM,IAAI,OAAO;AAAA,MAC/C,CAAC;AACD,UAAI,CAAC,OAAO,MAAM,CAAC,OAAO,QAAQ,IAAI;AACpC,cAAM,UAAU,OAAO,WAAW,MAC9B,EAAE,gDAAgD,mFAA8E,IAChI,WAAW,WACT,EAAE,6CAA6C,mCAAmC,IAClF,EAAE,+CAA+C,mCAAmC;AAC1F,iBAAS,OAAO;AAChB,cAAM,SAAS,OAAO;AACtB,YAAI,OAAO,WAAW,KAAK;AACzB,yBAAe,IAAI;AACnB,gBAAM,UAAU;AAAA,QAClB;AACA;AAAA,MACF;AACA,qBAAe,IAAI;AACnB;AAAA,QACE,WAAW,WACP,EAAE,+CAA+C,gCAAgC,IACjF,EAAE,iDAAiD,gCAAgC;AAAA,QACvF;AAAA,MACF;AACA,YAAM,UAAU;AAAA,IAClB,QAAQ;AACN,YAAM,UAAU,WAAW,WACvB,EAAE,6CAA6C,mCAAmC,IAClF,EAAE,+CAA+C,mCAAmC;AACxF,eAAS,OAAO;AAChB,YAAM,SAAS,OAAO;AAAA,IACxB,UAAE;AACA,0BAAoB,KAAK;AAAA,IAC3B;AAAA,EACF,GAAG,CAAC,kBAAkB,OAAO,iBAAiB,WAAW,CAAC,CAAC;AAE3D,QAAM,2BAA2B,MAAM,YAAY,CAAC,UAA+C;AACjG,UAAM,OAAO,MAAM,OAAO,QAAQ,CAAC,KAAK;AACxC,QAAI,CAAC,KAAM;AACX,SAAK,iBAAiB,IAAI;AAAA,EAC5B,GAAG,CAAC,gBAAgB,CAAC;AAErB,QAAM,8BAA8B,MAAM,YAAY,CAAC,UAA+C;AACpG,UAAM,oBAAoB,qBAAqB;AAC/C,yBAAqB,UAAU;AAC/B,UAAM,OAAO,MAAM,OAAO,QAAQ,CAAC,KAAK;AACxC,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,qBAAqB,CAAC,KAAM;AACjC,SAAK,iBAAiB,MAAM,iBAAiB;AAAA,EAC/C,GAAG,CAAC,gBAAgB,CAAC;AAErB,QAAM,sBAAsB,MAAM,YAAY,CAAC,UAA4C;AACzF,UAAM,eAAe;AACrB,SAAK,cAAc;AAAA,EACrB,GAAG,CAAC,aAAa,CAAC;AAElB,QAAM,uBAAuB,MAAM,YAAY,CAAC,UAAoD;AAClG,SAAK,MAAM,WAAW,MAAM,YAAY,MAAM,QAAQ,SAAS;AAC7D,YAAM,eAAe;AACrB,WAAK,cAAc;AACnB;AAAA,IACF;AACA,QAAI,MAAM,QAAQ,UAAU;AAC1B,YAAM,eAAe;AACrB,iBAAW,EAAE;AAAA,IACf;AAAA,EACF,GAAG,CAAC,aAAa,CAAC;AAElB,MAAI,SAAS;AACX,WAAO,oBAAC,SAAI,WAAU,0CAAyC,8BAAC,WAAQ,GAAE;AAAA,EAC5E;AAEA,MAAI,CAAC,KAAM,QAAO;AAElB,MAAI,WAAW;AACb,WAAO,oBAAC,kBAAe,OAAO,EAAE,uCAAuC,GAAG;AAAA,EAC5E;AAEA,MAAI,UAAU;AACZ,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,oBAAC,eAAY,WAAU,UAAS;AAAA,QACtC,OAAO,EAAE,6CAA6C;AAAA,QACtD,aAAa,EAAE,mDAAmD;AAAA,QAClE,QACE,oBAAC,UAAO,SAAO,MACb,+BAAC,QAAK,MAAM,IAAI,OAAO,OAAO,kBAC5B;AAAA,8BAAC,aAAU,WAAU,UAAS,eAAY,QAAO;AAAA,UAChD,EAAE,oCAAoC;AAAA,WACzC,GACF;AAAA;AAAA,IAEJ;AAAA,EAEJ;AAEA,MAAI,CAAC,OAAO;AACV,WAAO,oBAAC,gBAAa,OAAO,SAAS,EAAE,yCAAyC,GAAG;AAAA,EACrF;AAEA,QAAM,eAAe,kBAAkB,OAAO,GAAG,MAAM;AACvD,QAAM,UAAU,MAAM,WAAW;AACjC,QAAM,cAAc,MAAM,WAAW,WAAW,MAAM,WAAW;AACjE,QAAM,OAAO,WAAW,MAAM,MAAM;AACpC,QAAM,cAAc,oBAAoB,IAAI;AAC5C,QAAM,aAAa,SAAS,UAAU,cAAc,SAAS,YAAY,gBAAgB;AACzF,QAAM,kBAAkB,qBAAqB,QAAQ,aAAa,CAAC;AAEnE,SACE,qBAAC,SAAI,WAAU,gDACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,IAAI,OAAO,OAAO;AAAA,QACxB,WAAU;AAAA,QAET;AAAA;AAAA,UAAI;AAAA,UAAG,EAAE,kCAAkC;AAAA;AAAA;AAAA,IAC9C;AAAA,IAEC,QAAQ,oBAAC,gBAAa,OAAO,OAAO,IAAK;AAAA,IAE1C,qBAAC,SAAI,WAAU,kEACb;AAAA,2BAAC,SAAI,WAAU,6CACb;AAAA,6BAAC,SAAI,WAAU,kCACb;AAAA,+BAAC,SAAI,WAAU,mFACb;AAAA,gCAAC,UAAK,WAAU,6CACb,YAAE,6BAA6B,MAAM,SAAS,EAAE,GACnD;AAAA,YACA,oBAAC,UAAK,WAAU,yCACb,gBAAM,aACT;AAAA,aACF;AAAA,UACA,oBAAC,SAAI,WAAU,mBAAkB;AAAA,UAChC,UACC;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,MAAK;AAAA,cACL,WAAW;AAAA,cACX,SAAS,MAAM,eAAe,QAAQ;AAAA,cACtC,UAAU;AAAA,cAET,YAAE,+BAA+B;AAAA;AAAA,UACpC,IACE;AAAA,UACH,cACC;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,WAAU;AAAA,cACV,SAAS,MAAM,eAAe,UAAU;AAAA,cACxC,UAAU;AAAA,cAET,YAAE,0CAA0C,gBAAgB;AAAA;AAAA,UAC/D,IACE;AAAA,WACN;AAAA,QACA,oBAAC,QAAG,WAAU,qCACX,0BAAgB,OAAO,eAAe,CAAC,GAC1C;AAAA,QACA,oBAAC,OAAE,WAAU,iCACV,6BAAmB,OAAO,GAAG,MAAM,GACtC;AAAA,SACF;AAAA,MAEA,oBAAC,SAAI,WAAU,oBACb,+BAAC,SAAI,WAAW,sDAAsD,YAAY,OAAO,IACvF;AAAA,4BAAC,cAAW,WAAW,yBAAyB,YAAY,IAAI,IAAI,eAAY,QAAO;AAAA,QACvF,oBAAC,OAAE,WAAW,2BAA2B,YAAY,IAAI,IACtD,wBAAc,OAAO,CAAC,GACzB;AAAA,SACF,GACF;AAAA,MAEA,oBAAC,SAAI,WAAU,kCACZ,uBAAa,IAAI,CAAC,MAAM,UACvB,qBAAC,SAAkB,WAAU,mCAC3B;AAAA,6BAAC,SAAI,WAAU,8BACZ;AAAA,eAAK,UAAU,aACd,oBAAC,UAAK,WAAU,wFACd,8BAAC,SAAM,WAAU,iCAAgC,aAAa,KAAK,eAAY,QAAO,GACxF,IACE,KAAK,UAAU,YACjB,oBAAC,UAAK,WAAU,0HACb,kBAAQ,GACX,IAEA,oBAAC,UAAK,WAAU,kIACb,kBAAQ,GACX;AAAA,UAED,QAAQ,aAAa,SAAS,IAC7B;AAAA,YAAC;AAAA;AAAA,cACC,WAAW,uBAAuB,KAAK,UAAU,aAAa,2BAA2B,WAAW;AAAA;AAAA,UACtG,IACE;AAAA,WACN;AAAA,QACA,qBAAC,SAAI,WAAU,4CACb;AAAA,+BAAC,SAAI,WAAU,kCACb;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,WACE,KAAK,UAAU,YACX,8CACA;AAAA,gBAGL,eAAK;AAAA;AAAA,YACR;AAAA,YACA,oBAAC,SAAI,WAAU,mBAAkB;AAAA,YAChC,KAAK,YACJ,oBAAC,OAAE,WAAU,iCAAiC,eAAK,WAAU,IAC3D;AAAA,aACN;AAAA,UACA,oBAAC,OAAE,WAAU,wCAAwC,eAAK,aAAY;AAAA,WACxE;AAAA,WAtCQ,KAAK,EAuCf,CACD,GACH;AAAA,MAEA,qBAAC,SAAI,WAAW,eACd;AAAA,4BAAC,OAAE,WAAW,uBAAwB,YAAE,sCAAsC,GAAE;AAAA,QAC/E,MAAM,MAAM,SAAS,IACpB,MAAM,MAAM,IAAI,CAAC,SACf;AAAA,UAAC;AAAA;AAAA,YAEC,WAAU;AAAA,YAEV;AAAA,mCAAC,SAAI,WAAU,uCACb;AAAA,oCAAC,OAAE,WAAU,uCACV,eAAK,eAAe,KAAK,OAAO,EAAE,0CAA0C,GAC/E;AAAA,gBACA,oBAAC,OAAE,WAAU,iCACV,eAAK,MACF,EAAE,2CAA2C;AAAA,kBAC3C,KAAK,KAAK;AAAA,kBACV,KAAK,eAAe,KAAK,YAAY,EAAE,2CAA2C,CAAC;AAAA,gBACrF,CAAC,IACD,EAAE,0CAA0C;AAAA,kBAC1C,KAAK,eAAe,KAAK,YAAY,EAAE,2CAA2C,CAAC;AAAA,gBACrF,CAAC,GACP;AAAA,iBACF;AAAA,cACA,qBAAC,SAAI,WAAU,4FACb;AAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,WAAW,gCAAgC,wBAAwB,KAAK,UAAU,KAAK,wBAAwB;AAAA,oBAC/G,eAAY;AAAA;AAAA,gBACd;AAAA,gBACA,oBAAC,UAAK,WAAU,uCACb,YAAE,8BAA8B,KAAK,UAAU,EAAE,GACpD;AAAA,iBACF;AAAA;AAAA;AAAA,UA1BK,KAAK;AAAA,QA2BZ,CACD,IAED,oBAAC,OAAE,WAAU,iCACV,YAAE,sDAAsD,GAC3D;AAAA,SAEJ;AAAA,MAEA,qBAAC,SAAI,WAAW,eACd;AAAA,4BAAC,OAAE,WAAW,uBAAwB,YAAE,yCAAyC,GAAE;AAAA,QAClF,gBAAgB,SAAS,IACxB,gBAAgB,IAAI,CAAC,UACnB,qBAAC,SAAmB,WAAU,iCAC5B;AAAA,8BAAC,UAAK,WAAU,4DAA2D,eAAY,QAAO;AAAA,UAC9F,qBAAC,SAAI,WAAU,wCACb;AAAA,iCAAC,SAAI,WAAU,iCACb;AAAA,kCAAC,OAAE,WAAU,uCAAuC,gBAAM,OAAM;AAAA,cAChE,oBAAC,SAAI,WAAU,mBAAkB;AAAA,cAChC,MAAM,YACL,oBAAC,OAAE,WAAU,iCAAiC,0BAAgB,MAAM,WAAW,MAAM,GAAE,IACrF;AAAA,eACN;AAAA,YACC,MAAM,OACL,qBAAC,SAAI,WAAU,qCACb;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAM,MAAM;AAAA,kBACZ,QAAO;AAAA,kBACP,KAAI;AAAA,kBACJ,WAAU;AAAA,kBAET,gBAAM;AAAA;AAAA,cACT;AAAA,cACC,MAAM,aACL,qBAAC,SAAI,WAAU,2BACb;AAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,SAAQ;AAAA,oBACR,MAAK;AAAA,oBACL,WAAU;AAAA,oBACV,cAAY;AAAA,sBACV;AAAA,sBACA;AAAA,sBACA,EAAE,MAAM,MAAM,WAAW,SAAS;AAAA,oBACpC;AAAA,oBACA,SAAS,MAAM,6BAA6B,MAAM,UAAW;AAAA,oBAC7D,UAAU;AAAA,oBAET;AAAA,+CAAyB,MAAM,WAAW,KACzC,oBAAC,WAAQ,WAAU,UAAS,IAE5B,oBAAC,aAAU,WAAU,UAAS,eAAY,QAAO;AAAA,sBAElD,EAAE,mDAAmD,SAAS;AAAA;AAAA;AAAA,gBACjE;AAAA,gBACA;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,SAAQ;AAAA,oBACR,MAAK;AAAA,oBACL,WAAU;AAAA,oBACV,cAAY;AAAA,sBACV;AAAA,sBACA;AAAA,sBACA,EAAE,MAAM,MAAM,WAAW,SAAS;AAAA,oBACpC;AAAA,oBACA,SAAS,MAAM;AAAE,2BAAK,iBAAiB,MAAM,UAAW;AAAA,oBAAE;AAAA,oBAC1D,UAAU;AAAA,oBAEV;AAAA,0CAAC,UAAO,WAAU,UAAS,eAAY,QAAO;AAAA,sBAC7C,EAAE,kDAAkD,mBAAmB;AAAA;AAAA;AAAA,gBAC1E;AAAA,iBACF,IACE;AAAA,eACN,IAEA,oBAAC,OAAE,WAAU,qDAAqD,gBAAM,aAAY;AAAA,aAExF;AAAA,aAhEQ,MAAM,EAiEhB,CACD,IAED,oBAAC,OAAE,WAAU,iCACV,YAAE,yDAAyD,GAC9D;AAAA,QAEF;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,MAAK;AAAA,YACL,QAAQ;AAAA,YACR,UAAU;AAAA,YACV,UAAU;AAAA,YACV,WAAU;AAAA,YACV,cAAY,EAAE,6DAA6D,yBAAyB;AAAA;AAAA,QACtG;AAAA,SACF;AAAA,MAEA,qBAAC,SAAI,WAAW,eACd;AAAA,4BAAC,OAAE,WAAW,uBAAwB,YAAE,wCAAwC,GAAE;AAAA,QAClF,qBAAC,UAAK,IAAG,8BAA6B,WAAU,8BAA6B,UAAU,qBACrF;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,UAAU,CAAC,UAAU,WAAW,MAAM,OAAO,KAAK;AAAA,cAClD,WAAW;AAAA,cACX,aAAa,EAAE,mDAAmD;AAAA,cAClE,UAAU;AAAA,cACV,WAAW;AAAA,cACX,WAAU;AAAA;AAAA,UACZ;AAAA,UACA,qBAAC,SAAI,WAAU,kCACb;AAAA,iCAAC,WAAM,WAAW,2BAA2B,YAAY,8BAA8B,gBAAgB,IACpG;AAAA,0BACC,oBAAC,WAAQ,WAAU,UAAS,IAE5B,oBAAC,aAAU,WAAU,gCAA+B,eAAY,QAAO;AAAA,cAEzE,oBAAC,UAAK,WAAU,qFACb,sBACG,EAAE,yCAAyC,IAC3C,EAAE,4CAA4C,GACpD;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBAEC,MAAK;AAAA,kBACL,QAAQ;AAAA,kBACR,UAAU;AAAA,kBACV,UAAU;AAAA,kBACV,WAAU;AAAA;AAAA,gBALL;AAAA,cAMP;AAAA,eACF;AAAA,YACA,oBAAC,SAAI,WAAU,mBAAkB;AAAA,YACjC,oBAAC,UAAO,MAAK,UAAS,MAAK,MAAK,WAAW,mBAAmB,UAAU,qBAAqB,CAAC,QAAQ,KAAK,GACxG,8BACG,EAAE,wCAAwC,IAC1C,EAAE,qCAAqC,GAC7C;AAAA,aACF;AAAA,WACF;AAAA,SACF;AAAA,OACF;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,gBAAgB;AAAA,QACtB,cAAc,CAAC,SAAS;AACtB,cAAI,CAAC,QAAQ,CAAC,iBAAkB,gBAAe,IAAI;AAAA,QACrD;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,WAAW,CAAC,UAAU;AACpB,mBAAK,MAAM,WAAW,MAAM,YAAY,MAAM,QAAQ,SAAS;AAC7D,sBAAM,eAAe;AACrB,oBAAI,eAAe,CAAC,iBAAkB,MAAK,eAAe,WAAW;AAAA,cACvE;AACA,kBAAI,MAAM,QAAQ,YAAY,CAAC,kBAAkB;AAC/C,+BAAe,IAAI;AAAA,cACrB;AAAA,YACF;AAAA,YAEA;AAAA,mCAAC,gBACC;AAAA,oCAAC,eACE,0BAAgB,aACb,EAAE,sDAAsD,sBAAsB,IAC9E,EAAE,oDAAoD,oBAAoB,GAChF;AAAA,gBACA,oBAAC,qBACE,0BAAgB,aACb,EAAE,4DAA4D,4EAA4E,IAC1I,EAAE,0DAA0D,2GAA2G,GAC7K;AAAA,iBACF;AAAA,cACA,qBAAC,gBACC;AAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,SAAQ;AAAA,oBACR,SAAS,MAAM,eAAe,IAAI;AAAA,oBAClC,UAAU;AAAA,oBAET,YAAE,8CAA8C,QAAQ;AAAA;AAAA,gBAC3D;AAAA,gBACA;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,SAAS,gBAAgB,aAAa,gBAAgB;AAAA,oBACtD,SAAS,MAAM;AACb,0BAAI,YAAa,MAAK,eAAe,WAAW;AAAA,oBAClD;AAAA,oBACA,UAAU;AAAA,oBAET,0BAAgB,aACZ,mBACC,EAAE,6CAA6C,gBAAgB,IAC/D,EAAE,iDAAiD,gBAAgB,IACpE,mBACC,EAAE,4CAA4C,eAAe,IAC7D,EAAE,+CAA+C,cAAc;AAAA;AAAA,gBACvE;AAAA,iBACF;AAAA;AAAA;AAAA,QACF;AAAA;AAAA,IACF;AAAA,IACC;AAAA,KACH;AAEJ;",
6
6
  "names": ["error"]
7
7
  }
@@ -1,3 +1,4 @@
1
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
1
2
  import { Dictionary, DictionaryEntry } from "@open-mercato/core/modules/dictionaries/data/entities";
2
3
  import {
3
4
  normalizeDictionaryValue,
@@ -71,6 +72,24 @@ async function loadWarrantyClaimDictionaryOptions(em, scope, kind) {
71
72
  );
72
73
  return entries.map((entry) => ({ value: entry.value, label: entry.label }));
73
74
  }
75
+ async function loadWarrantyClaimDictionaryLabel(em, scope, kind, value) {
76
+ const dictionary = await findOneWithDecryption(
77
+ em,
78
+ Dictionary,
79
+ { ...scope, key: getWarrantyClaimDictionaryDefinition(kind).key, isActive: true, deletedAt: null },
80
+ {},
81
+ scope
82
+ );
83
+ if (!dictionary) return null;
84
+ const entry = await findOneWithDecryption(
85
+ em,
86
+ DictionaryEntry,
87
+ { ...scope, dictionary: dictionary.id, value },
88
+ {},
89
+ scope
90
+ );
91
+ return entry?.label ?? null;
92
+ }
74
93
  async function ensureWarrantyClaimDictionary(params) {
75
94
  const { em, tenantId, organizationId, kind } = params;
76
95
  const def = getWarrantyClaimDictionaryDefinition(kind);
@@ -151,6 +170,7 @@ async function seedWarrantyClaimDictionaries(em, scope) {
151
170
  export {
152
171
  ensureWarrantyClaimDictionary,
153
172
  getWarrantyClaimDictionaryDefinition,
173
+ loadWarrantyClaimDictionaryLabel,
154
174
  loadWarrantyClaimDictionaryOptions,
155
175
  seedWarrantyClaimDictionaries
156
176
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/warranty_claims/lib/dictionaries.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport {\n normalizeDictionaryValue,\n sanitizeDictionaryColor,\n sanitizeDictionaryIcon,\n} from '@open-mercato/core/modules/dictionaries/lib/utils'\nimport type { WarrantyClaimDictionaryKind } from '../data/constants'\n\ntype WarrantyClaimDictionaryDefinition = {\n key: string\n name: string\n singular: string\n description: string\n resourceKind: string\n commandPrefix: string\n}\n\ntype WarrantyClaimDictionarySeed = {\n value: string\n label: string\n color?: string | null\n icon?: string | null\n}\n\ntype SeedScope = {\n tenantId: string\n organizationId: string\n}\n\nconst DEFINITIONS: Record<WarrantyClaimDictionaryKind, WarrantyClaimDictionaryDefinition> = {\n 'warranty-claim-fault-code': {\n key: 'warranty_claims.warranty_claim_fault_code',\n name: 'Warranty claim fault codes',\n singular: 'Warranty claim fault code',\n description: 'Configurable fault codes used on warranty claim lines.',\n resourceKind: 'warranty_claims.warranty-claim-fault-code',\n commandPrefix: 'warranty_claims.warranty-claim-fault-codes',\n },\n 'warranty-claim-reason': {\n key: 'warranty_claims.warranty_claim_reason',\n name: 'Warranty claim reasons',\n singular: 'Warranty claim reason',\n description: 'Configurable reasons used when creating warranty and return claims.',\n resourceKind: 'warranty_claims.warranty-claim-reason',\n commandPrefix: 'warranty_claims.warranty-claim-reasons',\n },\n 'warranty-claim-rejection-reason': {\n key: 'warranty_claims.warranty_claim_rejection_reason',\n name: 'Warranty claim rejection reasons',\n singular: 'Warranty claim rejection reason',\n description: 'Configurable reasons used when rejecting warranty and return claims.',\n resourceKind: 'warranty_claims.warranty-claim-rejection-reason',\n commandPrefix: 'warranty_claims.warranty-claim-rejection-reasons',\n },\n}\n\nconst FAULT_CODE_DEFAULTS: WarrantyClaimDictionarySeed[] = [\n { value: 'defective', label: 'Defective', color: '#ef4444', icon: 'lucide:triangle-alert' },\n { value: 'damaged-in-transit', label: 'Damaged in transit', color: '#f59e0b', icon: 'lucide:truck' },\n { value: 'wrong-item', label: 'Wrong item', color: '#6366f1', icon: 'lucide:package-x' },\n { value: 'worn', label: 'Worn', color: '#94a3b8', icon: 'lucide:rotate-ccw' },\n { value: 'doa', label: 'Dead on arrival', color: '#dc2626', icon: 'lucide:zap-off' },\n]\n\nconst CLAIM_REASON_DEFAULTS: WarrantyClaimDictionarySeed[] = [\n { value: 'warranty-defect', label: 'Warranty defect', color: '#ef4444', icon: 'lucide:shield-alert' },\n { value: 'customer-remorse', label: 'Customer remorse', color: '#94a3b8', icon: 'lucide:undo-2' },\n { value: 'wrong-order', label: 'Wrong order', color: '#6366f1', icon: 'lucide:list-x' },\n { value: 'core-exchange', label: 'Core exchange', color: '#0d9488', icon: 'lucide:repeat-2' },\n { value: 'vendor-recall', label: 'Vendor recall', color: '#f59e0b', icon: 'lucide:megaphone' },\n]\n\nconst REJECTION_REASON_DEFAULTS: WarrantyClaimDictionarySeed[] = [\n { value: 'out-of-warranty', label: 'Out of warranty', color: '#ef4444', icon: 'lucide:calendar-x' },\n { value: 'no-proof-of-purchase', label: 'No proof of purchase', color: '#f59e0b', icon: 'lucide:receipt' },\n { value: 'physical-abuse', label: 'Physical abuse', color: '#dc2626', icon: 'lucide:hammer' },\n { value: 'not-our-product', label: 'Not our product', color: '#6366f1', icon: 'lucide:package-search' },\n { value: 'duplicate-claim', label: 'Duplicate claim', color: '#94a3b8', icon: 'lucide:copy-x' },\n]\n\nexport function getWarrantyClaimDictionaryDefinition(\n kind: WarrantyClaimDictionaryKind\n): WarrantyClaimDictionaryDefinition {\n return DEFINITIONS[kind]\n}\n\nexport type WarrantyClaimDictionaryOption = {\n value: string\n label: string\n}\n\nexport async function loadWarrantyClaimDictionaryOptions(\n em: EntityManager,\n scope: SeedScope,\n kind: WarrantyClaimDictionaryKind\n): Promise<WarrantyClaimDictionaryOption[]> {\n const def = getWarrantyClaimDictionaryDefinition(kind)\n const dictionary = await em.findOne(Dictionary, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n key: def.key,\n isActive: true,\n deletedAt: null,\n })\n if (!dictionary) return []\n const entries = await em.find(\n DictionaryEntry,\n { dictionary, tenantId: scope.tenantId, organizationId: scope.organizationId },\n { orderBy: { position: 'asc', label: 'asc' } }\n )\n return entries.map((entry) => ({ value: entry.value, label: entry.label }))\n}\n\nexport async function ensureWarrantyClaimDictionary(params: {\n em: EntityManager\n tenantId: string\n organizationId: string\n kind: WarrantyClaimDictionaryKind\n}): Promise<Dictionary> {\n const { em, tenantId, organizationId, kind } = params\n const def = getWarrantyClaimDictionaryDefinition(kind)\n let dictionary = await em.findOne(Dictionary, {\n tenantId,\n organizationId,\n key: def.key,\n deletedAt: null,\n })\n if (!dictionary) {\n dictionary = em.create(Dictionary, {\n tenantId,\n organizationId,\n key: def.key,\n name: def.name,\n description: def.description,\n isSystem: true,\n isActive: true,\n managerVisibility: 'hidden',\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(dictionary)\n await em.flush()\n }\n return dictionary\n}\n\nasync function ensureWarrantyClaimDictionaryEntry(\n em: EntityManager,\n scope: SeedScope,\n kind: WarrantyClaimDictionaryKind,\n seed: WarrantyClaimDictionarySeed\n): Promise<DictionaryEntry | null> {\n const value = seed.value?.trim()\n if (!value) return null\n const dictionary = await ensureWarrantyClaimDictionary({\n em,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n kind,\n })\n const normalizedValue = normalizeDictionaryValue(value)\n const color = seed.color === undefined ? undefined : sanitizeDictionaryColor(seed.color)\n const icon = seed.icon === undefined ? undefined : sanitizeDictionaryIcon(seed.icon)\n const existing = await em.findOne(DictionaryEntry, {\n dictionary,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n normalizedValue,\n })\n if (existing) return existing\n const now = new Date()\n const entry = em.create(DictionaryEntry, {\n dictionary,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n value,\n label: seed.label?.trim() || value,\n normalizedValue,\n color: color ?? null,\n icon: icon ?? null,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(entry)\n return entry\n}\n\nasync function seedWarrantyClaimDictionary(\n em: EntityManager,\n scope: SeedScope,\n kind: WarrantyClaimDictionaryKind,\n defaults: WarrantyClaimDictionarySeed[]\n): Promise<void> {\n for (const seed of defaults) {\n await ensureWarrantyClaimDictionaryEntry(em, scope, kind, seed)\n }\n}\n\nexport async function seedWarrantyClaimDictionaries(\n em: EntityManager,\n scope: SeedScope\n): Promise<void> {\n await seedWarrantyClaimDictionary(em, scope, 'warranty-claim-fault-code', FAULT_CODE_DEFAULTS)\n await seedWarrantyClaimDictionary(em, scope, 'warranty-claim-reason', CLAIM_REASON_DEFAULTS)\n await seedWarrantyClaimDictionary(\n em,\n scope,\n 'warranty-claim-rejection-reason',\n REJECTION_REASON_DEFAULTS\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport {\n normalizeDictionaryValue,\n sanitizeDictionaryColor,\n sanitizeDictionaryIcon,\n} from '@open-mercato/core/modules/dictionaries/lib/utils'\nimport type { WarrantyClaimDictionaryKind } from '../data/constants'\n\ntype WarrantyClaimDictionaryDefinition = {\n key: string\n name: string\n singular: string\n description: string\n resourceKind: string\n commandPrefix: string\n}\n\ntype WarrantyClaimDictionarySeed = {\n value: string\n label: string\n color?: string | null\n icon?: string | null\n}\n\ntype SeedScope = {\n tenantId: string\n organizationId: string\n}\n\nconst DEFINITIONS: Record<WarrantyClaimDictionaryKind, WarrantyClaimDictionaryDefinition> = {\n 'warranty-claim-fault-code': {\n key: 'warranty_claims.warranty_claim_fault_code',\n name: 'Warranty claim fault codes',\n singular: 'Warranty claim fault code',\n description: 'Configurable fault codes used on warranty claim lines.',\n resourceKind: 'warranty_claims.warranty-claim-fault-code',\n commandPrefix: 'warranty_claims.warranty-claim-fault-codes',\n },\n 'warranty-claim-reason': {\n key: 'warranty_claims.warranty_claim_reason',\n name: 'Warranty claim reasons',\n singular: 'Warranty claim reason',\n description: 'Configurable reasons used when creating warranty and return claims.',\n resourceKind: 'warranty_claims.warranty-claim-reason',\n commandPrefix: 'warranty_claims.warranty-claim-reasons',\n },\n 'warranty-claim-rejection-reason': {\n key: 'warranty_claims.warranty_claim_rejection_reason',\n name: 'Warranty claim rejection reasons',\n singular: 'Warranty claim rejection reason',\n description: 'Configurable reasons used when rejecting warranty and return claims.',\n resourceKind: 'warranty_claims.warranty-claim-rejection-reason',\n commandPrefix: 'warranty_claims.warranty-claim-rejection-reasons',\n },\n}\n\nconst FAULT_CODE_DEFAULTS: WarrantyClaimDictionarySeed[] = [\n { value: 'defective', label: 'Defective', color: '#ef4444', icon: 'lucide:triangle-alert' },\n { value: 'damaged-in-transit', label: 'Damaged in transit', color: '#f59e0b', icon: 'lucide:truck' },\n { value: 'wrong-item', label: 'Wrong item', color: '#6366f1', icon: 'lucide:package-x' },\n { value: 'worn', label: 'Worn', color: '#94a3b8', icon: 'lucide:rotate-ccw' },\n { value: 'doa', label: 'Dead on arrival', color: '#dc2626', icon: 'lucide:zap-off' },\n]\n\nconst CLAIM_REASON_DEFAULTS: WarrantyClaimDictionarySeed[] = [\n { value: 'warranty-defect', label: 'Warranty defect', color: '#ef4444', icon: 'lucide:shield-alert' },\n { value: 'customer-remorse', label: 'Customer remorse', color: '#94a3b8', icon: 'lucide:undo-2' },\n { value: 'wrong-order', label: 'Wrong order', color: '#6366f1', icon: 'lucide:list-x' },\n { value: 'core-exchange', label: 'Core exchange', color: '#0d9488', icon: 'lucide:repeat-2' },\n { value: 'vendor-recall', label: 'Vendor recall', color: '#f59e0b', icon: 'lucide:megaphone' },\n]\n\nconst REJECTION_REASON_DEFAULTS: WarrantyClaimDictionarySeed[] = [\n { value: 'out-of-warranty', label: 'Out of warranty', color: '#ef4444', icon: 'lucide:calendar-x' },\n { value: 'no-proof-of-purchase', label: 'No proof of purchase', color: '#f59e0b', icon: 'lucide:receipt' },\n { value: 'physical-abuse', label: 'Physical abuse', color: '#dc2626', icon: 'lucide:hammer' },\n { value: 'not-our-product', label: 'Not our product', color: '#6366f1', icon: 'lucide:package-search' },\n { value: 'duplicate-claim', label: 'Duplicate claim', color: '#94a3b8', icon: 'lucide:copy-x' },\n]\n\nexport function getWarrantyClaimDictionaryDefinition(\n kind: WarrantyClaimDictionaryKind\n): WarrantyClaimDictionaryDefinition {\n return DEFINITIONS[kind]\n}\n\nexport type WarrantyClaimDictionaryOption = {\n value: string\n label: string\n}\n\nexport async function loadWarrantyClaimDictionaryOptions(\n em: EntityManager,\n scope: SeedScope,\n kind: WarrantyClaimDictionaryKind\n): Promise<WarrantyClaimDictionaryOption[]> {\n const def = getWarrantyClaimDictionaryDefinition(kind)\n const dictionary = await em.findOne(Dictionary, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n key: def.key,\n isActive: true,\n deletedAt: null,\n })\n if (!dictionary) return []\n const entries = await em.find(\n DictionaryEntry,\n { dictionary, tenantId: scope.tenantId, organizationId: scope.organizationId },\n { orderBy: { position: 'asc', label: 'asc' } }\n )\n return entries.map((entry) => ({ value: entry.value, label: entry.label }))\n}\n\nexport async function loadWarrantyClaimDictionaryLabel(\n em: EntityManager,\n scope: SeedScope,\n kind: WarrantyClaimDictionaryKind,\n value: string,\n): Promise<string | null> {\n const dictionary = await findOneWithDecryption(\n em,\n Dictionary,\n { ...scope, key: getWarrantyClaimDictionaryDefinition(kind).key, isActive: true, deletedAt: null },\n {},\n scope,\n )\n if (!dictionary) return null\n const entry = await findOneWithDecryption(\n em,\n DictionaryEntry,\n { ...scope, dictionary: dictionary.id, value },\n {},\n scope,\n )\n return entry?.label ?? null\n}\n\nexport async function ensureWarrantyClaimDictionary(params: {\n em: EntityManager\n tenantId: string\n organizationId: string\n kind: WarrantyClaimDictionaryKind\n}): Promise<Dictionary> {\n const { em, tenantId, organizationId, kind } = params\n const def = getWarrantyClaimDictionaryDefinition(kind)\n let dictionary = await em.findOne(Dictionary, {\n tenantId,\n organizationId,\n key: def.key,\n deletedAt: null,\n })\n if (!dictionary) {\n dictionary = em.create(Dictionary, {\n tenantId,\n organizationId,\n key: def.key,\n name: def.name,\n description: def.description,\n isSystem: true,\n isActive: true,\n managerVisibility: 'hidden',\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(dictionary)\n await em.flush()\n }\n return dictionary\n}\n\nasync function ensureWarrantyClaimDictionaryEntry(\n em: EntityManager,\n scope: SeedScope,\n kind: WarrantyClaimDictionaryKind,\n seed: WarrantyClaimDictionarySeed\n): Promise<DictionaryEntry | null> {\n const value = seed.value?.trim()\n if (!value) return null\n const dictionary = await ensureWarrantyClaimDictionary({\n em,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n kind,\n })\n const normalizedValue = normalizeDictionaryValue(value)\n const color = seed.color === undefined ? undefined : sanitizeDictionaryColor(seed.color)\n const icon = seed.icon === undefined ? undefined : sanitizeDictionaryIcon(seed.icon)\n const existing = await em.findOne(DictionaryEntry, {\n dictionary,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n normalizedValue,\n })\n if (existing) return existing\n const now = new Date()\n const entry = em.create(DictionaryEntry, {\n dictionary,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n value,\n label: seed.label?.trim() || value,\n normalizedValue,\n color: color ?? null,\n icon: icon ?? null,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(entry)\n return entry\n}\n\nasync function seedWarrantyClaimDictionary(\n em: EntityManager,\n scope: SeedScope,\n kind: WarrantyClaimDictionaryKind,\n defaults: WarrantyClaimDictionarySeed[]\n): Promise<void> {\n for (const seed of defaults) {\n await ensureWarrantyClaimDictionaryEntry(em, scope, kind, seed)\n }\n}\n\nexport async function seedWarrantyClaimDictionaries(\n em: EntityManager,\n scope: SeedScope\n): Promise<void> {\n await seedWarrantyClaimDictionary(em, scope, 'warranty-claim-fault-code', FAULT_CODE_DEFAULTS)\n await seedWarrantyClaimDictionary(em, scope, 'warranty-claim-reason', CLAIM_REASON_DEFAULTS)\n await seedWarrantyClaimDictionary(\n em,\n scope,\n 'warranty-claim-rejection-reason',\n REJECTION_REASON_DEFAULTS\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.10.1-develop.7941.1.2d89db94ca",
3
+ "version": "0.10.1-develop.7954.1.48da4a7d13",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -266,16 +266,16 @@
266
266
  "zod": "^4.6.6"
267
267
  },
268
268
  "peerDependencies": {
269
- "@open-mercato/ai-assistant": "0.10.1-develop.7941.1.2d89db94ca",
270
- "@open-mercato/shared": "0.10.1-develop.7941.1.2d89db94ca",
271
- "@open-mercato/ui": "0.10.1-develop.7941.1.2d89db94ca",
269
+ "@open-mercato/ai-assistant": "0.10.1-develop.7954.1.48da4a7d13",
270
+ "@open-mercato/shared": "0.10.1-develop.7954.1.48da4a7d13",
271
+ "@open-mercato/ui": "0.10.1-develop.7954.1.48da4a7d13",
272
272
  "react": "^19.0.0",
273
273
  "react-dom": "^19.0.0"
274
274
  },
275
275
  "devDependencies": {
276
- "@open-mercato/ai-assistant": "0.10.1-develop.7941.1.2d89db94ca",
277
- "@open-mercato/shared": "0.10.1-develop.7941.1.2d89db94ca",
278
- "@open-mercato/ui": "0.10.1-develop.7941.1.2d89db94ca",
276
+ "@open-mercato/ai-assistant": "0.10.1-develop.7954.1.48da4a7d13",
277
+ "@open-mercato/shared": "0.10.1-develop.7954.1.48da4a7d13",
278
+ "@open-mercato/ui": "0.10.1-develop.7954.1.48da4a7d13",
279
279
  "@testing-library/dom": "^10.4.1",
280
280
  "@testing-library/jest-dom": "^7.0.1",
281
281
  "@testing-library/react": "^16.3.3",
@@ -4,9 +4,11 @@ import type { EntityManager } from '@mikro-orm/postgresql'
4
4
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
5
  import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
6
6
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
7
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
7
8
  import type { CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
8
9
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
9
10
  import { WarrantyClaim, WarrantyClaimLine } from '../../../../data/entities'
11
+ import { loadWarrantyClaimDictionaryLabel } from '../../../../lib/dictionaries'
10
12
  import { loadPortalOwnedClaim } from '../../../../lib/portalClaimAccess'
11
13
  import { resolveLinkedCustomerAuth } from '../../../../lib/portalAuthGuard'
12
14
 
@@ -102,6 +104,27 @@ function serializeClaim(claim: WarrantyClaim, lines: WarrantyClaimLine[]) {
102
104
  }
103
105
  }
104
106
 
107
+ async function loadRejectionReasonLabel(
108
+ context: PortalContext,
109
+ rejectionReasonCode: string | null | undefined,
110
+ ): Promise<string | null> {
111
+ if (!rejectionReasonCode) return null
112
+ try {
113
+ return await loadWarrantyClaimDictionaryLabel(
114
+ context.em.fork(),
115
+ { tenantId: context.tenantId, organizationId: context.organizationId },
116
+ 'warranty-claim-rejection-reason',
117
+ rejectionReasonCode,
118
+ )
119
+ } catch (error) {
120
+ getTelemetryRuntime()?.reportError(error, {
121
+ module: 'warranty_claims',
122
+ code: 'warranty_claims.portal_rejection_label_failed',
123
+ })
124
+ return null
125
+ }
126
+ }
127
+
105
128
  export async function GET(req: Request, ctx: RouteContext) {
106
129
  const contextOrResponse = await resolvePortalContext(req)
107
130
  if (contextOrResponse instanceof Response) return contextOrResponse
@@ -131,7 +154,8 @@ export async function GET(req: Request, ctx: RouteContext) {
131
154
  { orderBy: { lineNo: 'ASC' } },
132
155
  scope,
133
156
  )
134
- return NextResponse.json({ item: serializeClaim(claim, lines) })
157
+ const rejectionReasonLabel = await loadRejectionReasonLabel(context, claim.rejectionReasonCode)
158
+ return NextResponse.json({ item: { ...serializeClaim(claim, lines), rejectionReasonLabel } })
135
159
  }
136
160
 
137
161
  const lineSchema = z.object({
@@ -166,6 +190,7 @@ const claimSchema = z.object({
166
190
  orderNumber: z.string().nullable(),
167
191
  reasonCode: z.string().nullable(),
168
192
  rejectionReasonCode: z.string().nullable(),
193
+ rejectionReasonLabel: z.string().nullable().optional(),
169
194
  resolutionSummary: z.string().nullable(),
170
195
  slaDueAt: z.string().nullable(),
171
196
  submittedAt: z.string().nullable(),
@@ -60,6 +60,7 @@ type PortalClaim = {
60
60
  orderNumber: string | null
61
61
  reasonCode: string | null
62
62
  rejectionReasonCode: string | null
63
+ rejectionReasonLabel?: string | null
63
64
  resolutionSummary: string | null
64
65
  submittedAt: string | null
65
66
  resolvedAt: string | null
@@ -195,7 +196,7 @@ function bannerMessage(claim: PortalClaim, t: TranslateFn): string {
195
196
  return claim.resolutionSummary ?? t('warranty_claims.portal.tracker.banner.resolved')
196
197
  case 'rejected': {
197
198
  const reason = claim.rejectionReasonCode
198
- ? localizeDictionaryLabel(t, 'rejection', claim.rejectionReasonCode, claim.rejectionReasonCode)
199
+ ? localizeDictionaryLabel(t, 'rejection', claim.rejectionReasonCode, claim.rejectionReasonLabel || claim.rejectionReasonCode)
199
200
  : null
200
201
  const base = t('warranty_claims.portal.detail.terminal.rejected')
201
202
  const parts = [base, reason, claim.resolutionSummary].filter((part): part is string => Boolean(part))
@@ -1,4 +1,5 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
2
3
  import { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'
3
4
  import {
4
5
  normalizeDictionaryValue,
@@ -112,6 +113,30 @@ export async function loadWarrantyClaimDictionaryOptions(
112
113
  return entries.map((entry) => ({ value: entry.value, label: entry.label }))
113
114
  }
114
115
 
116
+ export async function loadWarrantyClaimDictionaryLabel(
117
+ em: EntityManager,
118
+ scope: SeedScope,
119
+ kind: WarrantyClaimDictionaryKind,
120
+ value: string,
121
+ ): Promise<string | null> {
122
+ const dictionary = await findOneWithDecryption(
123
+ em,
124
+ Dictionary,
125
+ { ...scope, key: getWarrantyClaimDictionaryDefinition(kind).key, isActive: true, deletedAt: null },
126
+ {},
127
+ scope,
128
+ )
129
+ if (!dictionary) return null
130
+ const entry = await findOneWithDecryption(
131
+ em,
132
+ DictionaryEntry,
133
+ { ...scope, dictionary: dictionary.id, value },
134
+ {},
135
+ scope,
136
+ )
137
+ return entry?.label ?? null
138
+ }
139
+
115
140
  export async function ensureWarrantyClaimDictionary(params: {
116
141
  em: EntityManager
117
142
  tenantId: string