@open-mercato/core 0.7.1-develop.7106.1.53fe9dd1b1 → 0.7.1-develop.7111.1.c0d5d7286f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/business_rules/backend/logs/[id]/page.js +3 -9
  3. package/dist/modules/business_rules/backend/logs/[id]/page.js.map +2 -2
  4. package/dist/modules/business_rules/backend/rules/[id]/page.js +3 -9
  5. package/dist/modules/business_rules/backend/rules/[id]/page.js.map +2 -2
  6. package/dist/modules/business_rules/backend/sets/[id]/page.js +3 -9
  7. package/dist/modules/business_rules/backend/sets/[id]/page.js.map +2 -2
  8. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js +6 -4
  9. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js.map +2 -2
  10. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  11. package/dist/modules/customers/components/detail/ActivitiesSection.js +4 -12
  12. package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
  13. package/dist/modules/customers/components/detail/authorUserLookup.js +46 -0
  14. package/dist/modules/customers/components/detail/authorUserLookup.js.map +7 -0
  15. package/dist/modules/query_index/lib/engine.js +2 -1
  16. package/dist/modules/query_index/lib/engine.js.map +2 -2
  17. package/dist/modules/workflows/backend/definitions/[id]/page.js +3 -9
  18. package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
  19. package/dist/modules/workflows/backend/events/[id]/page.js +3 -9
  20. package/dist/modules/workflows/backend/events/[id]/page.js.map +2 -2
  21. package/package.json +7 -7
  22. package/src/modules/business_rules/backend/logs/[id]/page.tsx +3 -11
  23. package/src/modules/business_rules/backend/rules/[id]/page.tsx +3 -11
  24. package/src/modules/business_rules/backend/sets/[id]/page.tsx +3 -11
  25. package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.tsx +7 -5
  26. package/src/modules/communication_channels/i18n/de.json +1 -0
  27. package/src/modules/communication_channels/i18n/en.json +1 -0
  28. package/src/modules/communication_channels/i18n/es.json +1 -0
  29. package/src/modules/communication_channels/i18n/ko.json +1 -0
  30. package/src/modules/communication_channels/i18n/pl.json +1 -0
  31. package/src/modules/currencies/backend/currencies/[id]/page.tsx +1 -1
  32. package/src/modules/customers/components/detail/ActivitiesSection.tsx +8 -18
  33. package/src/modules/customers/components/detail/authorUserLookup.ts +85 -0
  34. package/src/modules/query_index/lib/engine.ts +9 -0
  35. package/src/modules/workflows/backend/definitions/[id]/page.tsx +3 -11
  36. package/src/modules/workflows/backend/events/[id]/page.tsx +3 -12
@@ -1,4 +1,4 @@
1
- [build:core] found 4363 entry points
1
+ [build:core] found 4364 entry points
2
2
  [build:core] built successfully
3
3
  [build:core:generated] found 218 entry points
4
4
  [build:core:generated] built successfully
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import Link from "next/link";
4
- import { useParams, useRouter } from "next/navigation";
4
+ import { useRouter } from "next/navigation";
5
5
  import { useQuery } from "@tanstack/react-query";
6
6
  import { apiFetch } from "@open-mercato/ui/backend/utils/api";
7
7
  import { useT } from "@open-mercato/shared/lib/i18n/context";
@@ -10,15 +10,9 @@ import { Button } from "@open-mercato/ui/primitives/button";
10
10
  import { Spinner } from "@open-mercato/ui/primitives/spinner";
11
11
  import { JsonDisplay } from "@open-mercato/ui/backend/JsonDisplay";
12
12
  import { RecordNotFoundState, ErrorMessage } from "@open-mercato/ui/backend/detail";
13
- function ExecutionLogDetailPage() {
13
+ function ExecutionLogDetailPage({ params }) {
14
14
  const router = useRouter();
15
- const params = useParams();
16
- let logId;
17
- if (params?.slug && Array.isArray(params.slug)) {
18
- logId = params.slug[1];
19
- } else if (params?.id) {
20
- logId = Array.isArray(params.id) ? params.id[0] : params.id;
21
- }
15
+ const logId = params?.id;
22
16
  const t = useT();
23
17
  const { data: log, isLoading, error } = useQuery({
24
18
  queryKey: ["business-rules", "logs", logId],
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/business_rules/backend/logs/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useParams, useRouter } from 'next/navigation'\nimport { useQuery } from '@tanstack/react-query'\nimport { apiFetch } from '@open-mercato/ui/backend/utils/api'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { JsonDisplay } from '@open-mercato/ui/backend/JsonDisplay'\nimport { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'\n\ntype RuleExecutionLog = {\n id: string\n ruleId: string\n rule?: {\n id: string\n ruleId: string\n ruleName: string\n ruleType: string\n } | null\n entityType: string\n entityId: string | null\n eventType: string | null\n executedAt: string\n executionTimeMs: number\n executionResult: 'SUCCESS' | 'FAILURE' | 'ERROR'\n resultValue: any | null\n errorMessage: string | null\n inputContext: any | null\n outputContext: any | null\n executedBy: string | null\n tenantId: string | null\n organizationId: string | null\n}\n\nexport default function ExecutionLogDetailPage() {\n const router = useRouter()\n const params = useParams()\n\n // Handle catch-all route: params.slug = ['logs', 'id']\n let logId: string | undefined\n if (params?.slug && Array.isArray(params.slug)) {\n logId = params.slug[1] // Second element is the ID\n } else if (params?.id) {\n logId = Array.isArray(params.id) ? params.id[0] : params.id\n }\n\n const t = useT()\n\n const { data: log, isLoading, error } = useQuery({\n queryKey: ['business-rules', 'logs', logId],\n queryFn: async () => {\n const response = await apiFetch(`/api/business_rules/logs/${logId}`)\n if (!response.ok) {\n const httpErr = new Error(\n response.status === 404\n ? t('business_rules.logs.errors.notFound', 'Execution log not found.')\n : t('business_rules.logs.errors.fetchFailed')\n ) as Error & { status: number }\n httpErr.status = response.status\n throw httpErr\n }\n const result = await response.json()\n return result as RuleExecutionLog\n },\n enabled: !!logId,\n })\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <Spinner className=\"h-6 w-6\" />\n <span>{t('business_rules.logs.detail.loading')}</span>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n const isNotFound = !isLoading && (error as (Error & { status?: number }) | null)?.status === 404\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('business_rules.logs.errors.notFound', 'Execution log not found.')}\n backHref=\"/backend/logs\"\n backLabel={t('business_rules.logs.backToList', 'Back to logs')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !log) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={(error as Error | null)?.message ?? t('business_rules.logs.errors.loadFailed')}\n action={\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/logs\">{t('business_rules.logs.backToList', 'Back to logs')}</Link>\n </Button>\n }\n />\n </PageBody>\n </Page>\n )\n }\n\n const getResultBadgeClass = (result: string) => {\n switch (result) {\n case 'SUCCESS':\n return 'bg-green-100 text-green-800'\n case 'FAILURE':\n return 'bg-yellow-100 text-yellow-800'\n case 'ERROR':\n return 'bg-red-100 text-red-800'\n default:\n return 'bg-muted text-foreground'\n }\n }\n\n return (\n <Page>\n <PageBody>\n <div className=\"space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {t('business_rules.logs.detail.title')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {t('business_rules.logs.detail.logId')}: {log.id}\n </p>\n </div>\n <Button onClick={() => router.push('/backend/logs')} variant=\"outline\">\n {t('business_rules.logs.backToList')}\n </Button>\n </div>\n\n {/* Execution Summary */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('business_rules.logs.detail.summary')}\n </h2>\n <dl className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.executedAt')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {new Date(log.executedAt).toLocaleString()}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.result')}\n </dt>\n <dd className=\"mt-1\">\n <span\n className={`inline-flex items-center px-2 py-1 rounded text-xs font-medium ${getResultBadgeClass(\n log.executionResult\n )}`}\n >\n {t(`business_rules.logs.result.${log.executionResult.toLowerCase()}`)}\n </span>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.executionTime')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">{log.executionTimeMs}ms</dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.executedBy')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {log.executedBy || t('common.unknown')}\n </dd>\n </div>\n </dl>\n </div>\n\n {/* Rule Information */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('business_rules.logs.detail.ruleInfo')}\n </h2>\n {log.rule ? (\n <dl className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.ruleName')}\n </dt>\n <dd className=\"mt-1\">\n <Link\n href={`/backend/rules/${log.rule.id}`}\n className=\"text-sm text-primary hover:underline\"\n >\n {log.rule.ruleName}\n </Link>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.ruleType')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">{log.rule.ruleType}</dd>\n </div>\n <div className=\"md:col-span-2\">\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.ruleId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">{log.rule.ruleId}</dd>\n </div>\n </dl>\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t('business_rules.logs.ruleDeleted')}</p>\n )}\n </div>\n\n {/* Entity Information */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('business_rules.logs.detail.entityInfo')}\n </h2>\n <dl className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.entityType')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">{log.entityType}</dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.eventType')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {log.eventType || t('common.none')}\n </dd>\n </div>\n {log.entityId && (\n <div className=\"md:col-span-2\">\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.entityId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono break-all\">\n {log.entityId}\n </dd>\n </div>\n )}\n </dl>\n </div>\n\n {/* Error Message (if present) */}\n {log.errorMessage && (\n <div className=\"rounded-lg border border-destructive bg-destructive/5 p-6\">\n <h2 className=\"text-lg font-semibold mb-4 text-destructive\">\n {t('business_rules.logs.detail.errorMessage')}\n </h2>\n <pre className=\"text-sm text-destructive whitespace-pre-wrap font-mono\">\n {log.errorMessage}\n </pre>\n </div>\n )}\n\n {/* Input Context */}\n {log.inputContext && (\n <JsonDisplay\n data={log.inputContext}\n title={t('business_rules.logs.detail.inputContext')}\n />\n )}\n\n {/* Output Context */}\n {log.outputContext && (\n <JsonDisplay\n data={log.outputContext}\n title={t('business_rules.logs.detail.outputContext')}\n />\n )}\n\n {/* Result Value */}\n {log.resultValue && (\n <JsonDisplay\n data={log.resultValue}\n title={t('business_rules.logs.detail.resultValue')}\n />\n )}\n </div>\n </PageBody>\n </Page>\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 { useRouter } from 'next/navigation'\nimport { useQuery } from '@tanstack/react-query'\nimport { apiFetch } from '@open-mercato/ui/backend/utils/api'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { JsonDisplay } from '@open-mercato/ui/backend/JsonDisplay'\nimport { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'\n\ntype RuleExecutionLog = {\n id: string\n ruleId: string\n rule?: {\n id: string\n ruleId: string\n ruleName: string\n ruleType: string\n } | null\n entityType: string\n entityId: string | null\n eventType: string | null\n executedAt: string\n executionTimeMs: number\n executionResult: 'SUCCESS' | 'FAILURE' | 'ERROR'\n resultValue: any | null\n errorMessage: string | null\n inputContext: any | null\n outputContext: any | null\n executedBy: string | null\n tenantId: string | null\n organizationId: string | null\n}\n\nexport default function ExecutionLogDetailPage({ params }: { params?: { id?: string } }) {\n const router = useRouter()\n const logId = params?.id\n\n const t = useT()\n\n const { data: log, isLoading, error } = useQuery({\n queryKey: ['business-rules', 'logs', logId],\n queryFn: async () => {\n const response = await apiFetch(`/api/business_rules/logs/${logId}`)\n if (!response.ok) {\n const httpErr = new Error(\n response.status === 404\n ? t('business_rules.logs.errors.notFound', 'Execution log not found.')\n : t('business_rules.logs.errors.fetchFailed')\n ) as Error & { status: number }\n httpErr.status = response.status\n throw httpErr\n }\n const result = await response.json()\n return result as RuleExecutionLog\n },\n enabled: !!logId,\n })\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <Spinner className=\"h-6 w-6\" />\n <span>{t('business_rules.logs.detail.loading')}</span>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n const isNotFound = !isLoading && (error as (Error & { status?: number }) | null)?.status === 404\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('business_rules.logs.errors.notFound', 'Execution log not found.')}\n backHref=\"/backend/logs\"\n backLabel={t('business_rules.logs.backToList', 'Back to logs')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !log) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={(error as Error | null)?.message ?? t('business_rules.logs.errors.loadFailed')}\n action={\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/logs\">{t('business_rules.logs.backToList', 'Back to logs')}</Link>\n </Button>\n }\n />\n </PageBody>\n </Page>\n )\n }\n\n const getResultBadgeClass = (result: string) => {\n switch (result) {\n case 'SUCCESS':\n return 'bg-green-100 text-green-800'\n case 'FAILURE':\n return 'bg-yellow-100 text-yellow-800'\n case 'ERROR':\n return 'bg-red-100 text-red-800'\n default:\n return 'bg-muted text-foreground'\n }\n }\n\n return (\n <Page>\n <PageBody>\n <div className=\"space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {t('business_rules.logs.detail.title')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {t('business_rules.logs.detail.logId')}: {log.id}\n </p>\n </div>\n <Button onClick={() => router.push('/backend/logs')} variant=\"outline\">\n {t('business_rules.logs.backToList')}\n </Button>\n </div>\n\n {/* Execution Summary */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('business_rules.logs.detail.summary')}\n </h2>\n <dl className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.executedAt')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {new Date(log.executedAt).toLocaleString()}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.result')}\n </dt>\n <dd className=\"mt-1\">\n <span\n className={`inline-flex items-center px-2 py-1 rounded text-xs font-medium ${getResultBadgeClass(\n log.executionResult\n )}`}\n >\n {t(`business_rules.logs.result.${log.executionResult.toLowerCase()}`)}\n </span>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.executionTime')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">{log.executionTimeMs}ms</dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.executedBy')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {log.executedBy || t('common.unknown')}\n </dd>\n </div>\n </dl>\n </div>\n\n {/* Rule Information */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('business_rules.logs.detail.ruleInfo')}\n </h2>\n {log.rule ? (\n <dl className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.ruleName')}\n </dt>\n <dd className=\"mt-1\">\n <Link\n href={`/backend/rules/${log.rule.id}`}\n className=\"text-sm text-primary hover:underline\"\n >\n {log.rule.ruleName}\n </Link>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.ruleType')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">{log.rule.ruleType}</dd>\n </div>\n <div className=\"md:col-span-2\">\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.ruleId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">{log.rule.ruleId}</dd>\n </div>\n </dl>\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t('business_rules.logs.ruleDeleted')}</p>\n )}\n </div>\n\n {/* Entity Information */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('business_rules.logs.detail.entityInfo')}\n </h2>\n <dl className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.entityType')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">{log.entityType}</dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.eventType')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {log.eventType || t('common.none')}\n </dd>\n </div>\n {log.entityId && (\n <div className=\"md:col-span-2\">\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.entityId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono break-all\">\n {log.entityId}\n </dd>\n </div>\n )}\n </dl>\n </div>\n\n {/* Error Message (if present) */}\n {log.errorMessage && (\n <div className=\"rounded-lg border border-destructive bg-destructive/5 p-6\">\n <h2 className=\"text-lg font-semibold mb-4 text-destructive\">\n {t('business_rules.logs.detail.errorMessage')}\n </h2>\n <pre className=\"text-sm text-destructive whitespace-pre-wrap font-mono\">\n {log.errorMessage}\n </pre>\n </div>\n )}\n\n {/* Input Context */}\n {log.inputContext && (\n <JsonDisplay\n data={log.inputContext}\n title={t('business_rules.logs.detail.inputContext')}\n />\n )}\n\n {/* Output Context */}\n {log.outputContext && (\n <JsonDisplay\n data={log.outputContext}\n title={t('business_rules.logs.detail.outputContext')}\n />\n )}\n\n {/* Result Value */}\n {log.resultValue && (\n <JsonDisplay\n data={log.resultValue}\n title={t('business_rules.logs.detail.resultValue')}\n />\n )}\n </div>\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,7 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import Link from "next/link";
5
- import { useRouter, useParams, usePathname } from "next/navigation";
5
+ import { useRouter, usePathname } from "next/navigation";
6
6
  import { useQuery } from "@tanstack/react-query";
7
7
  import { Page, PageBody } from "@open-mercato/ui/backend/Page";
8
8
  import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
@@ -23,16 +23,10 @@ import {
23
23
  import { ConditionBuilder } from "../../../components/ConditionBuilder.js";
24
24
  import { ActionBuilder } from "../../../components/ActionBuilder.js";
25
25
  import { buildRulePayload, parseRuleToFormValues } from "../../../components/utils/formHelpers.js";
26
- function EditBusinessRulePage() {
26
+ function EditBusinessRulePage({ params }) {
27
27
  const router = useRouter();
28
- const params = useParams();
29
28
  const pathname = usePathname();
30
- let ruleId;
31
- if (params?.slug && Array.isArray(params.slug)) {
32
- ruleId = params.slug[1];
33
- } else if (params?.id) {
34
- ruleId = Array.isArray(params.id) ? params.id[0] : params.id;
35
- }
29
+ const ruleId = params?.id;
36
30
  const t = useT();
37
31
  const { organizationId, tenantId } = useOrganizationScopeDetail();
38
32
  const { data: rule, isLoading, error } = useQuery({
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/business_rules/backend/rules/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter, useParams, usePathname } from 'next/navigation'\nimport { useQuery } from '@tanstack/react-query'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiFetch, withScopedApiHeaders } from '@open-mercato/ui/backend/utils/api'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'\nimport { readJsonSafe } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport {\n businessRuleFormSchema,\n createFormGroups,\n createFieldDefinitions,\n type BusinessRuleFormValues,\n} from '../../../components/formConfig'\nimport { ConditionBuilder } from '../../../components/ConditionBuilder'\nimport { ActionBuilder } from '../../../components/ActionBuilder'\nimport { buildRulePayload, parseRuleToFormValues } from '../../../components/utils/formHelpers'\n\nexport default function EditBusinessRulePage() {\n const router = useRouter()\n const params = useParams()\n const pathname = usePathname()\n\n // Handle catch-all route: params.slug = ['rules', 'uuid']\n let ruleId: string | undefined\n if (params?.slug && Array.isArray(params.slug)) {\n ruleId = params.slug[1] // Second element is the ID\n } else if (params?.id) {\n ruleId = Array.isArray(params.id) ? params.id[0] : params.id\n }\n\n const t = useT()\n const { organizationId, tenantId } = useOrganizationScopeDetail()\n\n const { data: rule, isLoading, error } = useQuery({\n queryKey: ['business_rule', ruleId],\n queryFn: async () => {\n const response = await apiFetch(`/api/business_rules/rules/${ruleId}`)\n if (!response.ok) {\n throw new Error(t('business_rules.errors.fetchFailed'))\n }\n const result = await response.json()\n return result\n },\n enabled: !!ruleId,\n })\n\n const initialValues = React.useMemo(() => {\n if (rule) {\n return parseRuleToFormValues(rule)\n }\n return null\n }, [rule])\n\n const handleSubmit = async (values: BusinessRuleFormValues) => {\n // Use tenant/org from the loaded rule if available, otherwise from context\n const effectiveTenantId = rule?.tenantId || tenantId\n const effectiveOrgId = rule?.organizationId || organizationId\n\n if (!effectiveTenantId || !effectiveOrgId) {\n throw new Error(t('business_rules.errors.missingTenantOrOrg'))\n }\n\n const payload = buildRulePayload(values, effectiveTenantId, effectiveOrgId, undefined)\n\n const updateRule = () => apiFetch('/api/business_rules/rules', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...payload,\n id: ruleId,\n }),\n })\n const headers = buildOptimisticLockHeader(rule?.updatedAt ?? rule?.updated_at ?? null)\n const response = Object.keys(headers).length\n ? await withScopedApiHeaders(headers, updateRule)\n : await updateRule()\n\n if (!response.ok) {\n const body = (await readJsonSafe<Record<string, unknown>>(response)) ?? {}\n const message =\n (typeof body.error === 'string' && body.error) ||\n (typeof body.message === 'string' && body.message) ||\n t('business_rules.errors.updateFailed')\n throw new CrudHttpError(response.status, { ...body, error: message })\n }\n\n router.push('/backend/rules')\n router.refresh()\n }\n\n const fields = React.useMemo(() => createFieldDefinitions(t), [t])\n\n const formGroups = React.useMemo(\n () => createFormGroups(t, ConditionBuilder, ActionBuilder),\n [t]\n )\n\n // Publish page-load record context to the AppShell-owned `backend:record:current`\n // mount so the enterprise record_locks widget resolves `business_rules.rule` + id\n // explicitly. The resourceKind mirrors the route's `enforceCommandOptimisticLock`\n // call so the held lock matches the save-time conflict surface for the same rule.\n useSetCurrentRecordInjectionContext(\n buildRecordInjectionContext({\n resourceKind: 'business_rules.rule',\n resourceId: ruleId ?? null,\n updatedAt: rule?.updatedAt ?? rule?.updated_at ?? null,\n data: (rule ?? null) as Record<string, unknown> | null,\n path: pathname,\n }),\n )\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <Spinner className=\"h-6 w-6\" />\n <span>{t('business_rules.rules.edit.loading')}</span>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (error || !rule) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <p>{t('business_rules.errors.loadFailed')}</p>\n <Button asChild variant=\"outline\">\n <Link href=\"/backend/rules\">{t('business_rules.rules.backToList')}</Link>\n </Button>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (!initialValues) {\n return null\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n key={ruleId}\n title={t('business_rules.rules.edit.title')}\n backHref=\"/backend/rules\"\n schema={businessRuleFormSchema}\n fields={fields}\n initialValues={initialValues}\n optimisticLockUpdatedAt={rule?.updatedAt ?? rule?.updated_at ?? null}\n onSubmit={handleSubmit}\n cancelHref=\"/backend/rules\"\n groups={formGroups}\n submitLabel={t('business_rules.rules.form.update')}\n />\n </PageBody>\n </Page>\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 { useRouter, usePathname } from 'next/navigation'\nimport { useQuery } from '@tanstack/react-query'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiFetch, withScopedApiHeaders } from '@open-mercato/ui/backend/utils/api'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'\nimport { readJsonSafe } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport {\n businessRuleFormSchema,\n createFormGroups,\n createFieldDefinitions,\n type BusinessRuleFormValues,\n} from '../../../components/formConfig'\nimport { ConditionBuilder } from '../../../components/ConditionBuilder'\nimport { ActionBuilder } from '../../../components/ActionBuilder'\nimport { buildRulePayload, parseRuleToFormValues } from '../../../components/utils/formHelpers'\n\nexport default function EditBusinessRulePage({ params }: { params?: { id?: string } }) {\n const router = useRouter()\n const pathname = usePathname()\n const ruleId = params?.id\n\n const t = useT()\n const { organizationId, tenantId } = useOrganizationScopeDetail()\n\n const { data: rule, isLoading, error } = useQuery({\n queryKey: ['business_rule', ruleId],\n queryFn: async () => {\n const response = await apiFetch(`/api/business_rules/rules/${ruleId}`)\n if (!response.ok) {\n throw new Error(t('business_rules.errors.fetchFailed'))\n }\n const result = await response.json()\n return result\n },\n enabled: !!ruleId,\n })\n\n const initialValues = React.useMemo(() => {\n if (rule) {\n return parseRuleToFormValues(rule)\n }\n return null\n }, [rule])\n\n const handleSubmit = async (values: BusinessRuleFormValues) => {\n // Use tenant/org from the loaded rule if available, otherwise from context\n const effectiveTenantId = rule?.tenantId || tenantId\n const effectiveOrgId = rule?.organizationId || organizationId\n\n if (!effectiveTenantId || !effectiveOrgId) {\n throw new Error(t('business_rules.errors.missingTenantOrOrg'))\n }\n\n const payload = buildRulePayload(values, effectiveTenantId, effectiveOrgId, undefined)\n\n const updateRule = () => apiFetch('/api/business_rules/rules', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...payload,\n id: ruleId,\n }),\n })\n const headers = buildOptimisticLockHeader(rule?.updatedAt ?? rule?.updated_at ?? null)\n const response = Object.keys(headers).length\n ? await withScopedApiHeaders(headers, updateRule)\n : await updateRule()\n\n if (!response.ok) {\n const body = (await readJsonSafe<Record<string, unknown>>(response)) ?? {}\n const message =\n (typeof body.error === 'string' && body.error) ||\n (typeof body.message === 'string' && body.message) ||\n t('business_rules.errors.updateFailed')\n throw new CrudHttpError(response.status, { ...body, error: message })\n }\n\n router.push('/backend/rules')\n router.refresh()\n }\n\n const fields = React.useMemo(() => createFieldDefinitions(t), [t])\n\n const formGroups = React.useMemo(\n () => createFormGroups(t, ConditionBuilder, ActionBuilder),\n [t]\n )\n\n // Publish page-load record context to the AppShell-owned `backend:record:current`\n // mount so the enterprise record_locks widget resolves `business_rules.rule` + id\n // explicitly. The resourceKind mirrors the route's `enforceCommandOptimisticLock`\n // call so the held lock matches the save-time conflict surface for the same rule.\n useSetCurrentRecordInjectionContext(\n buildRecordInjectionContext({\n resourceKind: 'business_rules.rule',\n resourceId: ruleId ?? null,\n updatedAt: rule?.updatedAt ?? rule?.updated_at ?? null,\n data: (rule ?? null) as Record<string, unknown> | null,\n path: pathname,\n }),\n )\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <Spinner className=\"h-6 w-6\" />\n <span>{t('business_rules.rules.edit.loading')}</span>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (error || !rule) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <p>{t('business_rules.errors.loadFailed')}</p>\n <Button asChild variant=\"outline\">\n <Link href=\"/backend/rules\">{t('business_rules.rules.backToList')}</Link>\n </Button>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (!initialValues) {\n return null\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n key={ruleId}\n title={t('business_rules.rules.edit.title')}\n backHref=\"/backend/rules\"\n schema={businessRuleFormSchema}\n fields={fields}\n initialValues={initialValues}\n optimisticLockUpdatedAt={rule?.updatedAt ?? rule?.updated_at ?? null}\n onSubmit={handleSubmit}\n cancelHref=\"/backend/rules\"\n groups={formGroups}\n submitLabel={t('business_rules.rules.form.update')}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,7 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import Link from "next/link";
5
- import { useRouter, useParams, usePathname } from "next/navigation";
5
+ import { useRouter, usePathname } from "next/navigation";
6
6
  import { useQuery, useQueryClient } from "@tanstack/react-query";
7
7
  import { Page, PageBody } from "@open-mercato/ui/backend/Page";
8
8
  import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
@@ -26,16 +26,10 @@ const ruleSetFormSchema = z.object({
26
26
  description: z.string().max(5e3).optional().nullable(),
27
27
  enabled: z.boolean().optional()
28
28
  });
29
- function EditRuleSetPage() {
29
+ function EditRuleSetPage({ params }) {
30
30
  const router = useRouter();
31
- const params = useParams();
32
31
  const pathname = usePathname();
33
- let setId;
34
- if (params?.slug && Array.isArray(params.slug)) {
35
- setId = params.slug[1];
36
- } else if (params?.id) {
37
- setId = Array.isArray(params.id) ? params.id[0] : params.id;
38
- }
32
+ const setId = params?.id;
39
33
  const t = useT();
40
34
  const { organizationId, tenantId } = useOrganizationScopeDetail();
41
35
  const queryClient = useQueryClient();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/business_rules/backend/sets/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter, useParams, usePathname } from 'next/navigation'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport type { CrudField } from '@open-mercato/ui/backend/CrudForm'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiFetch, withScopedApiHeaders } from '@open-mercato/ui/backend/utils/api'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'\nimport { readJsonSafe } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { z } from 'zod'\nimport { RuleSetMembers } from '@open-mercato/core/modules/business_rules/components/RuleSetMembers'\n\nconst ruleSetFormSchema = z.object({\n setId: z.string().min(1).max(50),\n setName: z.string().min(1).max(200),\n description: z.string().max(5000).optional().nullable(),\n enabled: z.boolean().optional(),\n})\n\ntype RuleSetFormValues = z.infer<typeof ruleSetFormSchema>\n\ntype RuleSetDetail = {\n id: string\n setId: string\n setName: string\n description: string | null\n enabled: boolean\n tenantId: string\n organizationId: string\n createdBy: string | null\n updatedBy: string | null\n createdAt: string\n updatedAt: string\n members: Array<{\n id: string\n ruleId: string\n ruleName: string\n ruleType: string\n sequence: number\n enabled: boolean\n }>\n}\n\nexport default function EditRuleSetPage() {\n const router = useRouter()\n const params = useParams()\n const pathname = usePathname()\n\n // Handle catch-all route: params.slug = ['sets', 'uuid']\n let setId: string | undefined\n if (params?.slug && Array.isArray(params.slug)) {\n setId = params.slug[1] // Second element is the ID\n } else if (params?.id) {\n setId = Array.isArray(params.id) ? params.id[0] : params.id\n }\n\n const t = useT()\n const { organizationId, tenantId } = useOrganizationScopeDetail()\n const queryClient = useQueryClient()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const { data: ruleSet, isLoading, error } = useQuery({\n queryKey: ['business-rules', 'sets', setId],\n queryFn: async () => {\n const response = await apiFetch(`/api/business_rules/sets/${setId}`)\n if (!response.ok) {\n throw new Error(t('business_rules.sets.errors.fetchFailed'))\n }\n const result = await response.json()\n return result as RuleSetDetail\n },\n enabled: !!setId,\n })\n\n const handleSubmit = async (values: RuleSetFormValues) => {\n const payload = {\n id: setId,\n ...values,\n }\n\n const updateSet = () => apiFetch('/api/business_rules/sets', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n const headers = buildOptimisticLockHeader(ruleSet?.updatedAt ?? null)\n const response = Object.keys(headers).length\n ? await withScopedApiHeaders(headers, updateSet)\n : await updateSet()\n\n if (!response.ok) {\n const body = (await readJsonSafe<Record<string, unknown>>(response)) ?? {}\n const message =\n (typeof body.error === 'string' && body.error) ||\n (typeof body.message === 'string' && body.message) ||\n t('business_rules.sets.errors.updateFailed')\n throw new CrudHttpError(response.status, { ...body, error: message })\n }\n\n flash(t('business_rules.sets.messages.updated'), 'success')\n queryClient.invalidateQueries({ queryKey: ['business-rules', 'sets', setId] })\n }\n\n const handleAddMember = async (ruleId: string, sequence: number) => {\n const result = await apiCall(`/api/business_rules/sets/${setId}/members`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ruleId,\n sequence,\n enabled: true,\n }),\n })\n\n if (result.ok) {\n flash(t('business_rules.sets.members.messages.added'), 'success')\n queryClient.invalidateQueries({ queryKey: ['business-rules', 'sets', setId] })\n } else {\n const error = result.result as { error?: string; message?: string } | null\n if (error?.error?.includes('duplicate') || error?.message?.includes('exists')) {\n flash(t('business_rules.sets.members.messages.alreadyExists'), 'error')\n } else {\n flash(t('business_rules.sets.members.messages.addFailed'), 'error')\n }\n }\n }\n\n const handleUpdateMember = async (memberId: string, updates: { sequence?: number; enabled?: boolean }) => {\n const result = await apiCall(`/api/business_rules/sets/${setId}/members`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n memberId,\n ...updates,\n }),\n })\n\n if (result.ok) {\n flash(t('business_rules.sets.members.messages.updated'), 'success')\n queryClient.invalidateQueries({ queryKey: ['business-rules', 'sets', setId] })\n } else {\n flash(t('business_rules.sets.members.messages.updateFailed'), 'error')\n }\n }\n\n const handleRemoveMember = async (memberId: string, ruleName: string) => {\n const confirmed = await confirm({\n title: t('business_rules.sets.members.confirm.remove', { name: ruleName }),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n const result = await apiCall(`/api/business_rules/sets/${setId}/members?memberId=${memberId}`, {\n method: 'DELETE',\n })\n\n if (result.ok) {\n flash(t('business_rules.sets.members.messages.removed'), 'success')\n queryClient.invalidateQueries({ queryKey: ['business-rules', 'sets', setId] })\n } else {\n flash(t('business_rules.sets.members.messages.removeFailed'), 'error')\n }\n }\n\n const fields: CrudField[] = React.useMemo(() => [\n {\n id: 'setId',\n label: t('business_rules.sets.form.setId'),\n type: 'text',\n required: true,\n placeholder: t('business_rules.sets.form.placeholders.setId'),\n description: t('business_rules.sets.form.descriptions.setId'),\n },\n {\n id: 'setName',\n label: t('business_rules.sets.form.setName'),\n type: 'text',\n required: true,\n placeholder: t('business_rules.sets.form.placeholders.setName'),\n },\n {\n id: 'description',\n label: t('business_rules.sets.form.description'),\n type: 'textarea',\n placeholder: t('business_rules.sets.form.placeholders.description'),\n },\n {\n id: 'enabled',\n label: t('business_rules.sets.form.enabled'),\n type: 'checkbox',\n description: t('business_rules.sets.form.descriptions.enabled'),\n },\n ], [t])\n\n const initialValues: Partial<RuleSetFormValues> = React.useMemo(() => {\n if (!ruleSet) return {}\n return {\n setId: ruleSet.setId,\n setName: ruleSet.setName,\n description: ruleSet.description,\n enabled: ruleSet.enabled,\n }\n }, [ruleSet])\n\n const formGroups = React.useMemo(() => {\n if (!ruleSet) return []\n return [\n {\n id: 'details',\n title: t('business_rules.sets.edit.detailsSection'),\n column: 1 as const,\n fields: ['setId', 'setName', 'description', 'enabled'],\n },\n {\n id: 'members',\n title: t('business_rules.sets.edit.membersSection'),\n column: 1 as const,\n component: () => (\n <RuleSetMembers\n members={ruleSet.members}\n onAdd={handleAddMember}\n onUpdate={handleUpdateMember}\n onRemove={handleRemoveMember}\n />\n ),\n },\n ]\n }, [t, ruleSet, handleAddMember, handleUpdateMember, handleRemoveMember])\n\n // Publish page-load record context to the AppShell-owned `backend:record:current`\n // mount so the enterprise record_locks widget resolves the rule set + id\n // explicitly. The resourceKind mirrors the route's `enforceCommandOptimisticLock`\n // call (`business_rules.ruleSet`) so the held lock matches the save-time conflict\n // surface for the same rule set.\n useSetCurrentRecordInjectionContext(\n buildRecordInjectionContext({\n resourceKind: 'business_rules.ruleSet',\n resourceId: setId ?? null,\n updatedAt: ruleSet?.updatedAt ?? null,\n data: (ruleSet ?? null) as Record<string, unknown> | null,\n path: pathname,\n }),\n )\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <Spinner className=\"h-6 w-6\" />\n <span>{t('business_rules.sets.edit.loading')}</span>\n </div>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n if (error || !ruleSet) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <p>{t('business_rules.sets.messages.loadFailed')}</p>\n <Button asChild variant=\"outline\">\n <Link href=\"/backend/sets\">{t('business_rules.sets.backToList')}</Link>\n </Button>\n </div>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={t('business_rules.sets.edit.title')}\n backHref=\"/backend/sets\"\n schema={ruleSetFormSchema}\n fields={fields}\n initialValues={initialValues}\n optimisticLockUpdatedAt={ruleSet.updatedAt}\n onSubmit={handleSubmit}\n cancelHref=\"/backend/sets\"\n submitLabel={t('business_rules.sets.form.update')}\n groups={formGroups}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\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 { useRouter, usePathname } from 'next/navigation'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport type { CrudField } from '@open-mercato/ui/backend/CrudForm'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiFetch, withScopedApiHeaders } from '@open-mercato/ui/backend/utils/api'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'\nimport { readJsonSafe } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { z } from 'zod'\nimport { RuleSetMembers } from '@open-mercato/core/modules/business_rules/components/RuleSetMembers'\n\nconst ruleSetFormSchema = z.object({\n setId: z.string().min(1).max(50),\n setName: z.string().min(1).max(200),\n description: z.string().max(5000).optional().nullable(),\n enabled: z.boolean().optional(),\n})\n\ntype RuleSetFormValues = z.infer<typeof ruleSetFormSchema>\n\ntype RuleSetDetail = {\n id: string\n setId: string\n setName: string\n description: string | null\n enabled: boolean\n tenantId: string\n organizationId: string\n createdBy: string | null\n updatedBy: string | null\n createdAt: string\n updatedAt: string\n members: Array<{\n id: string\n ruleId: string\n ruleName: string\n ruleType: string\n sequence: number\n enabled: boolean\n }>\n}\n\nexport default function EditRuleSetPage({ params }: { params?: { id?: string } }) {\n const router = useRouter()\n const pathname = usePathname()\n const setId = params?.id\n\n const t = useT()\n const { organizationId, tenantId } = useOrganizationScopeDetail()\n const queryClient = useQueryClient()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const { data: ruleSet, isLoading, error } = useQuery({\n queryKey: ['business-rules', 'sets', setId],\n queryFn: async () => {\n const response = await apiFetch(`/api/business_rules/sets/${setId}`)\n if (!response.ok) {\n throw new Error(t('business_rules.sets.errors.fetchFailed'))\n }\n const result = await response.json()\n return result as RuleSetDetail\n },\n enabled: !!setId,\n })\n\n const handleSubmit = async (values: RuleSetFormValues) => {\n const payload = {\n id: setId,\n ...values,\n }\n\n const updateSet = () => apiFetch('/api/business_rules/sets', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n const headers = buildOptimisticLockHeader(ruleSet?.updatedAt ?? null)\n const response = Object.keys(headers).length\n ? await withScopedApiHeaders(headers, updateSet)\n : await updateSet()\n\n if (!response.ok) {\n const body = (await readJsonSafe<Record<string, unknown>>(response)) ?? {}\n const message =\n (typeof body.error === 'string' && body.error) ||\n (typeof body.message === 'string' && body.message) ||\n t('business_rules.sets.errors.updateFailed')\n throw new CrudHttpError(response.status, { ...body, error: message })\n }\n\n flash(t('business_rules.sets.messages.updated'), 'success')\n queryClient.invalidateQueries({ queryKey: ['business-rules', 'sets', setId] })\n }\n\n const handleAddMember = async (ruleId: string, sequence: number) => {\n const result = await apiCall(`/api/business_rules/sets/${setId}/members`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ruleId,\n sequence,\n enabled: true,\n }),\n })\n\n if (result.ok) {\n flash(t('business_rules.sets.members.messages.added'), 'success')\n queryClient.invalidateQueries({ queryKey: ['business-rules', 'sets', setId] })\n } else {\n const error = result.result as { error?: string; message?: string } | null\n if (error?.error?.includes('duplicate') || error?.message?.includes('exists')) {\n flash(t('business_rules.sets.members.messages.alreadyExists'), 'error')\n } else {\n flash(t('business_rules.sets.members.messages.addFailed'), 'error')\n }\n }\n }\n\n const handleUpdateMember = async (memberId: string, updates: { sequence?: number; enabled?: boolean }) => {\n const result = await apiCall(`/api/business_rules/sets/${setId}/members`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n memberId,\n ...updates,\n }),\n })\n\n if (result.ok) {\n flash(t('business_rules.sets.members.messages.updated'), 'success')\n queryClient.invalidateQueries({ queryKey: ['business-rules', 'sets', setId] })\n } else {\n flash(t('business_rules.sets.members.messages.updateFailed'), 'error')\n }\n }\n\n const handleRemoveMember = async (memberId: string, ruleName: string) => {\n const confirmed = await confirm({\n title: t('business_rules.sets.members.confirm.remove', { name: ruleName }),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n const result = await apiCall(`/api/business_rules/sets/${setId}/members?memberId=${memberId}`, {\n method: 'DELETE',\n })\n\n if (result.ok) {\n flash(t('business_rules.sets.members.messages.removed'), 'success')\n queryClient.invalidateQueries({ queryKey: ['business-rules', 'sets', setId] })\n } else {\n flash(t('business_rules.sets.members.messages.removeFailed'), 'error')\n }\n }\n\n const fields: CrudField[] = React.useMemo(() => [\n {\n id: 'setId',\n label: t('business_rules.sets.form.setId'),\n type: 'text',\n required: true,\n placeholder: t('business_rules.sets.form.placeholders.setId'),\n description: t('business_rules.sets.form.descriptions.setId'),\n },\n {\n id: 'setName',\n label: t('business_rules.sets.form.setName'),\n type: 'text',\n required: true,\n placeholder: t('business_rules.sets.form.placeholders.setName'),\n },\n {\n id: 'description',\n label: t('business_rules.sets.form.description'),\n type: 'textarea',\n placeholder: t('business_rules.sets.form.placeholders.description'),\n },\n {\n id: 'enabled',\n label: t('business_rules.sets.form.enabled'),\n type: 'checkbox',\n description: t('business_rules.sets.form.descriptions.enabled'),\n },\n ], [t])\n\n const initialValues: Partial<RuleSetFormValues> = React.useMemo(() => {\n if (!ruleSet) return {}\n return {\n setId: ruleSet.setId,\n setName: ruleSet.setName,\n description: ruleSet.description,\n enabled: ruleSet.enabled,\n }\n }, [ruleSet])\n\n const formGroups = React.useMemo(() => {\n if (!ruleSet) return []\n return [\n {\n id: 'details',\n title: t('business_rules.sets.edit.detailsSection'),\n column: 1 as const,\n fields: ['setId', 'setName', 'description', 'enabled'],\n },\n {\n id: 'members',\n title: t('business_rules.sets.edit.membersSection'),\n column: 1 as const,\n component: () => (\n <RuleSetMembers\n members={ruleSet.members}\n onAdd={handleAddMember}\n onUpdate={handleUpdateMember}\n onRemove={handleRemoveMember}\n />\n ),\n },\n ]\n }, [t, ruleSet, handleAddMember, handleUpdateMember, handleRemoveMember])\n\n // Publish page-load record context to the AppShell-owned `backend:record:current`\n // mount so the enterprise record_locks widget resolves the rule set + id\n // explicitly. The resourceKind mirrors the route's `enforceCommandOptimisticLock`\n // call (`business_rules.ruleSet`) so the held lock matches the save-time conflict\n // surface for the same rule set.\n useSetCurrentRecordInjectionContext(\n buildRecordInjectionContext({\n resourceKind: 'business_rules.ruleSet',\n resourceId: setId ?? null,\n updatedAt: ruleSet?.updatedAt ?? null,\n data: (ruleSet ?? null) as Record<string, unknown> | null,\n path: pathname,\n }),\n )\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <Spinner className=\"h-6 w-6\" />\n <span>{t('business_rules.sets.edit.loading')}</span>\n </div>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n if (error || !ruleSet) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <p>{t('business_rules.sets.messages.loadFailed')}</p>\n <Button asChild variant=\"outline\">\n <Link href=\"/backend/sets\">{t('business_rules.sets.backToList')}</Link>\n </Button>\n </div>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={t('business_rules.sets.edit.title')}\n backHref=\"/backend/sets\"\n schema={ruleSetFormSchema}\n fields={fields}\n initialValues={initialValues}\n optimisticLockUpdatedAt={ruleSet.updatedAt}\n onSubmit={handleSubmit}\n cancelHref=\"/backend/sets\"\n submitLabel={t('business_rules.sets.form.update')}\n groups={formGroups}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["error"]
7
7
  }
@@ -1,15 +1,13 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { useParams } from "next/navigation";
5
4
  import { Page, PageBody } from "@open-mercato/ui/backend/Page";
6
5
  import { LoadingMessage, ErrorMessage, RecordNotFoundState } from "@open-mercato/ui/backend/detail";
7
6
  import { Tag } from "@open-mercato/ui/primitives/tag";
8
7
  import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
9
8
  import { useT } from "@open-mercato/shared/lib/i18n/context";
10
- function ChannelDetailPage() {
9
+ function ChannelDetailPage({ params }) {
11
10
  const t = useT();
12
- const params = useParams();
13
11
  const id = params?.id ?? "";
14
12
  const [detail, setDetail] = React.useState(null);
15
13
  const [health, setHealth] = React.useState(null);
@@ -17,7 +15,11 @@ function ChannelDetailPage() {
17
15
  const [errorMessage, setErrorMessage] = React.useState(null);
18
16
  const [notFound, setNotFound] = React.useState(false);
19
17
  React.useEffect(() => {
20
- if (!id) return;
18
+ if (!id) {
19
+ setIsLoading(false);
20
+ setErrorMessage(t("communication_channels.errors.missingChannelId", "No channel selected."));
21
+ return;
22
+ }
21
23
  let cancelled = false;
22
24
  async function load() {
23
25
  setIsLoading(true);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/communication_channels/backend/communication_channels/channels/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { LoadingMessage, ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype ChannelDetail = {\n id: string\n providerKey: string\n channelType: string\n displayName: string\n externalIdentifier: string | null\n capabilities: Record<string, unknown> | null\n isActive: boolean\n}\n\ntype ChannelHealth = {\n channelId: string\n providerKey: string\n channelType: string\n windowHours: number\n totalsLast24h: number\n counts: Record<string, number>\n recentFailures: Array<{\n id: string\n messageId: string\n direction: string\n createdAt: string | null\n lastError: string | null\n transient: boolean | null\n }>\n}\n\nexport default function ChannelDetailPage() {\n const t = useT()\n const params = useParams<{ id: string }>()\n const id = (params?.id as string) ?? ''\n\n const [detail, setDetail] = React.useState<ChannelDetail | null>(null)\n const [health, setHealth] = React.useState<ChannelHealth | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [errorMessage, setErrorMessage] = React.useState<string | null>(null)\n const [notFound, setNotFound] = React.useState(false)\n\n React.useEffect(() => {\n if (!id) return\n let cancelled = false\n async function load() {\n setIsLoading(true)\n setErrorMessage(null)\n setNotFound(false)\n const [detailRes, healthRes] = await Promise.all([\n apiCall<ChannelDetail>(`/api/communication_channels/channels/${encodeURIComponent(id)}`),\n apiCall<ChannelHealth>(`/api/communication_channels/channels/${encodeURIComponent(id)}/health`),\n ]).catch((err: unknown) => {\n return [\n { ok: false, result: { error: err instanceof Error ? err.message : 'load failed' } },\n { ok: false, result: { error: err instanceof Error ? err.message : 'load failed' } },\n ] as const\n })\n if (cancelled) return\n if (!detailRes.ok) {\n const status = (detailRes as { status?: number }).status\n if (status === 404) {\n setNotFound(true)\n } else {\n const body = detailRes.result as { error?: string } | undefined\n setErrorMessage(\n body?.error ?? t('communication_channels.errors.loadDetail', 'Failed to load channel'),\n )\n }\n setDetail(null)\n setHealth(null)\n } else {\n setDetail(detailRes.result ?? null)\n setHealth(healthRes.ok ? (healthRes.result ?? null) : null)\n }\n setIsLoading(false)\n }\n void load()\n return () => {\n cancelled = true\n }\n }, [id, t])\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <LoadingMessage label={t('communication_channels.detail.loading', 'Loading channel...')} />\n </PageBody>\n </Page>\n )\n }\n if (notFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('communication_channels.detail.notFound', 'Channel not found')}\n backHref=\"/backend/communication_channels/channels\"\n />\n </PageBody>\n </Page>\n )\n }\n if (errorMessage || !detail) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={errorMessage ?? t('communication_channels.errors.loadDetail', 'Failed to load channel')}\n />\n </PageBody>\n </Page>\n )\n }\n\n const capabilities = (detail.capabilities ?? {}) as Record<string, unknown>\n const capabilityKeys = Object.keys(capabilities)\n\n return (\n <Page>\n <PageBody>\n <header className=\"mb-4 flex flex-wrap items-baseline justify-between gap-4\">\n <div>\n <h2 className=\"text-2xl font-semibold\">{detail.displayName}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {detail.providerKey} \u00B7 {detail.channelType}\n {detail.externalIdentifier ? ` \u00B7 ${detail.externalIdentifier}` : ''}\n </p>\n </div>\n {detail.isActive ? (\n <Tag variant=\"success\" dot>\n {t('communication_channels.status.active', 'Active')}\n </Tag>\n ) : (\n <Tag variant=\"neutral\">{t('communication_channels.status.inactive', 'Inactive')}</Tag>\n )}\n </header>\n\n <section\n className=\"mb-4 rounded-md border bg-card p-4\"\n aria-label={t('communication_channels.detail.capabilities', 'Capabilities')}\n >\n <header className=\"mb-2 text-overline text-muted-foreground\">\n {t('communication_channels.detail.capabilities', 'Capabilities')}\n </header>\n {capabilityKeys.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'communication_channels.detail.noCapabilities',\n 'No capabilities snapshot recorded for this channel yet.',\n )}\n </p>\n ) : (\n <dl className=\"grid grid-cols-2 gap-x-6 gap-y-1 text-sm md:grid-cols-3\">\n {capabilityKeys.sort((a, b) => a.localeCompare(b)).map((key) => (\n <React.Fragment key={key}>\n <dt className=\"text-muted-foreground\">{key}</dt>\n <dd className=\"md:col-span-2\">{renderCapabilityValue(capabilities[key])}</dd>\n </React.Fragment>\n ))}\n </dl>\n )}\n </section>\n\n <section\n className=\"rounded-md border bg-card p-4\"\n aria-label={t('communication_channels.detail.health', 'Delivery health (last 24h)')}\n >\n <header className=\"mb-2 flex items-baseline justify-between text-overline text-muted-foreground\">\n <span>{t('communication_channels.detail.health', 'Delivery health (last 24h)')}</span>\n {health ? (\n <span className=\"text-xs\">\n {health.totalsLast24h}{' '}\n {t('communication_channels.detail.messages', 'messages')}\n </span>\n ) : null}\n </header>\n {health ? (\n <>\n <ul className=\"mb-3 flex flex-wrap gap-2 text-xs\">\n {Object.entries(health.counts).map(([status, count]) => (\n <li key={status}>\n <Tag variant={tagVariantForStatus(status)}>\n {status}: {count}\n </Tag>\n </li>\n ))}\n </ul>\n {health.recentFailures.length > 0 ? (\n <details>\n <summary className=\"cursor-pointer text-sm text-muted-foreground\">\n {t('communication_channels.detail.recentFailures', 'Recent failures')} (\n {health.recentFailures.length})\n </summary>\n <ul className=\"mt-2 space-y-1 text-xs\">\n {health.recentFailures.map((failure) => (\n <li key={failure.id} className=\"rounded border bg-muted p-2\">\n <div className=\"font-mono text-xs text-muted-foreground\">\n {failure.messageId} \u00B7 {failure.direction} \u00B7{' '}\n {failure.createdAt ?? '\u2014'}\n </div>\n {failure.lastError ? (\n <div className=\"mt-1\">{failure.lastError}</div>\n ) : null}\n </li>\n ))}\n </ul>\n </details>\n ) : null}\n </>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n {t('communication_channels.detail.noHealth', 'No health data available.')}\n </p>\n )}\n </section>\n </PageBody>\n </Page>\n )\n}\n\nfunction renderCapabilityValue(value: unknown): string {\n if (value === null || value === undefined) return '\u2014'\n if (Array.isArray(value)) return value.join(', ')\n if (typeof value === 'object') return JSON.stringify(value)\n return String(value)\n}\n\nfunction tagVariantForStatus(status: string): 'success' | 'warning' | 'error' | 'info' | 'neutral' {\n switch (status) {\n case 'sent':\n case 'delivered':\n case 'read':\n return 'success'\n case 'failed':\n return 'error'\n case 'pending':\n case 'queued':\n return 'warning'\n default:\n return 'neutral'\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { LoadingMessage, ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype ChannelDetail = {\n id: string\n providerKey: string\n channelType: string\n displayName: string\n externalIdentifier: string | null\n capabilities: Record<string, unknown> | null\n isActive: boolean\n}\n\ntype ChannelHealth = {\n channelId: string\n providerKey: string\n channelType: string\n windowHours: number\n totalsLast24h: number\n counts: Record<string, number>\n recentFailures: Array<{\n id: string\n messageId: string\n direction: string\n createdAt: string | null\n lastError: string | null\n transient: boolean | null\n }>\n}\n\nexport default function ChannelDetailPage({ params }: { params?: { id?: string } }) {\n const t = useT()\n const id = params?.id ?? ''\n\n const [detail, setDetail] = React.useState<ChannelDetail | null>(null)\n const [health, setHealth] = React.useState<ChannelHealth | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [errorMessage, setErrorMessage] = React.useState<string | null>(null)\n const [notFound, setNotFound] = React.useState(false)\n\n React.useEffect(() => {\n if (!id) {\n setIsLoading(false)\n setErrorMessage(t('communication_channels.errors.missingChannelId', 'No channel selected.'))\n return\n }\n let cancelled = false\n async function load() {\n setIsLoading(true)\n setErrorMessage(null)\n setNotFound(false)\n const [detailRes, healthRes] = await Promise.all([\n apiCall<ChannelDetail>(`/api/communication_channels/channels/${encodeURIComponent(id)}`),\n apiCall<ChannelHealth>(`/api/communication_channels/channels/${encodeURIComponent(id)}/health`),\n ]).catch((err: unknown) => {\n return [\n { ok: false, result: { error: err instanceof Error ? err.message : 'load failed' } },\n { ok: false, result: { error: err instanceof Error ? err.message : 'load failed' } },\n ] as const\n })\n if (cancelled) return\n if (!detailRes.ok) {\n const status = (detailRes as { status?: number }).status\n if (status === 404) {\n setNotFound(true)\n } else {\n const body = detailRes.result as { error?: string } | undefined\n setErrorMessage(\n body?.error ?? t('communication_channels.errors.loadDetail', 'Failed to load channel'),\n )\n }\n setDetail(null)\n setHealth(null)\n } else {\n setDetail(detailRes.result ?? null)\n setHealth(healthRes.ok ? (healthRes.result ?? null) : null)\n }\n setIsLoading(false)\n }\n void load()\n return () => {\n cancelled = true\n }\n }, [id, t])\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <LoadingMessage label={t('communication_channels.detail.loading', 'Loading channel...')} />\n </PageBody>\n </Page>\n )\n }\n if (notFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('communication_channels.detail.notFound', 'Channel not found')}\n backHref=\"/backend/communication_channels/channels\"\n />\n </PageBody>\n </Page>\n )\n }\n if (errorMessage || !detail) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={errorMessage ?? t('communication_channels.errors.loadDetail', 'Failed to load channel')}\n />\n </PageBody>\n </Page>\n )\n }\n\n const capabilities = (detail.capabilities ?? {}) as Record<string, unknown>\n const capabilityKeys = Object.keys(capabilities)\n\n return (\n <Page>\n <PageBody>\n <header className=\"mb-4 flex flex-wrap items-baseline justify-between gap-4\">\n <div>\n <h2 className=\"text-2xl font-semibold\">{detail.displayName}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {detail.providerKey} \u00B7 {detail.channelType}\n {detail.externalIdentifier ? ` \u00B7 ${detail.externalIdentifier}` : ''}\n </p>\n </div>\n {detail.isActive ? (\n <Tag variant=\"success\" dot>\n {t('communication_channels.status.active', 'Active')}\n </Tag>\n ) : (\n <Tag variant=\"neutral\">{t('communication_channels.status.inactive', 'Inactive')}</Tag>\n )}\n </header>\n\n <section\n className=\"mb-4 rounded-md border bg-card p-4\"\n aria-label={t('communication_channels.detail.capabilities', 'Capabilities')}\n >\n <header className=\"mb-2 text-overline text-muted-foreground\">\n {t('communication_channels.detail.capabilities', 'Capabilities')}\n </header>\n {capabilityKeys.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'communication_channels.detail.noCapabilities',\n 'No capabilities snapshot recorded for this channel yet.',\n )}\n </p>\n ) : (\n <dl className=\"grid grid-cols-2 gap-x-6 gap-y-1 text-sm md:grid-cols-3\">\n {capabilityKeys.sort((a, b) => a.localeCompare(b)).map((key) => (\n <React.Fragment key={key}>\n <dt className=\"text-muted-foreground\">{key}</dt>\n <dd className=\"md:col-span-2\">{renderCapabilityValue(capabilities[key])}</dd>\n </React.Fragment>\n ))}\n </dl>\n )}\n </section>\n\n <section\n className=\"rounded-md border bg-card p-4\"\n aria-label={t('communication_channels.detail.health', 'Delivery health (last 24h)')}\n >\n <header className=\"mb-2 flex items-baseline justify-between text-overline text-muted-foreground\">\n <span>{t('communication_channels.detail.health', 'Delivery health (last 24h)')}</span>\n {health ? (\n <span className=\"text-xs\">\n {health.totalsLast24h}{' '}\n {t('communication_channels.detail.messages', 'messages')}\n </span>\n ) : null}\n </header>\n {health ? (\n <>\n <ul className=\"mb-3 flex flex-wrap gap-2 text-xs\">\n {Object.entries(health.counts).map(([status, count]) => (\n <li key={status}>\n <Tag variant={tagVariantForStatus(status)}>\n {status}: {count}\n </Tag>\n </li>\n ))}\n </ul>\n {health.recentFailures.length > 0 ? (\n <details>\n <summary className=\"cursor-pointer text-sm text-muted-foreground\">\n {t('communication_channels.detail.recentFailures', 'Recent failures')} (\n {health.recentFailures.length})\n </summary>\n <ul className=\"mt-2 space-y-1 text-xs\">\n {health.recentFailures.map((failure) => (\n <li key={failure.id} className=\"rounded border bg-muted p-2\">\n <div className=\"font-mono text-xs text-muted-foreground\">\n {failure.messageId} \u00B7 {failure.direction} \u00B7{' '}\n {failure.createdAt ?? '\u2014'}\n </div>\n {failure.lastError ? (\n <div className=\"mt-1\">{failure.lastError}</div>\n ) : null}\n </li>\n ))}\n </ul>\n </details>\n ) : null}\n </>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n {t('communication_channels.detail.noHealth', 'No health data available.')}\n </p>\n )}\n </section>\n </PageBody>\n </Page>\n )\n}\n\nfunction renderCapabilityValue(value: unknown): string {\n if (value === null || value === undefined) return '\u2014'\n if (Array.isArray(value)) return value.join(', ')\n if (typeof value === 'object') return JSON.stringify(value)\n return String(value)\n}\n\nfunction tagVariantForStatus(status: string): 'success' | 'warning' | 'error' | 'info' | 'neutral' {\n switch (status) {\n case 'sent':\n case 'delivered':\n case 'read':\n return 'success'\n case 'failed':\n return 'error'\n case 'pending':\n case 'queued':\n return 'warning'\n default:\n return 'neutral'\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/currencies/backend/currencies/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter, useParams, usePathname } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport { DataLoader } from '@open-mercato/ui/primitives/DataLoader'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'\n\ntype CurrencyData = {\n id: string\n code: string\n name: string\n symbol: string | null\n decimalPlaces: number\n thousandsSeparator: string | null\n decimalSeparator: string | null\n isBase: boolean\n isActive: boolean\n organizationId: string\n tenantId: string\n updatedAt?: string | null\n updated_at?: string | null\n}\n\nexport default function EditCurrencyPage({ params }: { params?: { id?: string } }) {\n const t = useT()\n const router = useRouter()\n const pathname = usePathname()\n const { confirm: confirmDialog, ConfirmDialogElement } = useConfirmDialog()\n const mutationContextId = 'currencies-edit:delete'\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: mutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const [currency, setCurrency] = React.useState<CurrencyData | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n\n React.useEffect(() => {\n async function loadCurrency() {\n try {\n const response = await apiCall<{ items: CurrencyData[] }>(`/api/currencies/currencies?id=${params?.id}`)\n if (response.ok && response.result && response.result.items.length > 0) {\n setCurrency(response.result.items[0])\n } else if (!response.ok) {\n setError(t('currencies.form.errors.load'))\n } else {\n setIsNotFound(true)\n }\n } catch (err) {\n setError(t('currencies.form.errors.load'))\n } finally {\n setLoading(false)\n }\n }\n loadCurrency()\n }, [params, t])\n\n const groups = React.useMemo<CrudFormGroup[]>(\n () => [\n {\n id: 'basic',\n column: 1,\n title: t('currencies.form.group.details'),\n fields: [\n {\n id: 'code',\n type: 'text',\n label: t('currencies.form.field.code'),\n placeholder: t('currencies.form.field.codePlaceholder'),\n required: true,\n maxLength: 3,\n helpText: t('currencies.form.field.codeHelp'),\n },\n {\n id: 'name',\n type: 'text',\n label: t('currencies.form.field.name'),\n placeholder: t('currencies.form.field.namePlaceholder'),\n required: true,\n },\n {\n id: 'symbol',\n type: 'text',\n label: t('currencies.form.field.symbol'),\n placeholder: t('currencies.form.field.symbolPlaceholder'),\n },\n ],\n },\n {\n id: 'formatting',\n column: 2,\n title: t('currencies.form.group.formatting'),\n fields: [\n {\n id: 'decimalPlaces',\n type: 'number',\n label: t('currencies.form.field.decimalPlaces'),\n min: 0,\n max: 8,\n },\n {\n id: 'thousandsSeparator',\n type: 'text',\n label: t('currencies.form.field.thousandsSeparator'),\n placeholder: ',',\n maxLength: 5,\n },\n {\n id: 'decimalSeparator',\n type: 'text',\n label: t('currencies.form.field.decimalSeparator'),\n placeholder: '.',\n maxLength: 5,\n },\n {\n id: 'isBase',\n type: 'checkbox',\n label: t('currencies.form.field.isBase'),\n },\n {\n id: 'isActive',\n type: 'checkbox',\n label: t('currencies.form.field.isActive'),\n },\n ],\n },\n ],\n [t]\n )\n\n const handleDelete = React.useCallback(async () => {\n if (!currency) return\n\n const confirmed = await confirmDialog({\n title: t('currencies.list.confirmDelete', { code: currency.code }),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n try {\n await runMutation({\n operation: async () => {\n const headers = buildOptimisticLockHeader(currency.updatedAt ?? currency.updated_at ?? null)\n const call = await withScopedApiRequestHeaders(headers, () => (\n apiCall('/api/currencies/currencies', {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: currency.id, organizationId: currency.organizationId, tenantId: currency.tenantId }),\n })\n ))\n if (!call.ok) {\n throw Object.assign(new Error('[internal] currencies.delete failed'), {\n status: call.status,\n ...((call.result as Record<string, unknown> | null) ?? {}),\n })\n }\n return call\n },\n context: {\n formId: mutationContextId,\n resourceKind: 'currencies.currency',\n resourceId: currency.id,\n retryLastMutation,\n },\n mutationPayload: { id: currency.id },\n })\n\n flash(t('currencies.flash.deleted'), 'success')\n router.push('/backend/currencies')\n } catch (error) {\n if (surfaceRecordConflict(error, t)) return\n flash(t('currencies.flash.deleteError'), 'error')\n }\n }, [currency, t, router, confirmDialog, mutationContextId, retryLastMutation, runMutation])\n\n // Publish page-load record context to the AppShell-owned `backend:record:current`\n // mount so the enterprise record_locks widget resolves `currencies.currency` + id\n // explicitly. The resourceKind mirrors the CrudForm `versionHistory` so the held\n // lock matches the save-time conflict surface for the same currency.\n useSetCurrentRecordInjectionContext(\n buildRecordInjectionContext({\n resourceKind: 'currencies.currency',\n resourceId: currency?.id ?? null,\n updatedAt: currency?.updatedAt ?? currency?.updated_at ?? null,\n data: currency as Record<string, unknown> | null,\n path: pathname,\n }),\n )\n\n if (loading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex items-center justify-center p-8\">\n <div className=\"text-muted-foreground\">{t('currencies.form.loading')}</div>\n </div>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('currencies.form.errors.notFound', 'Currency not found.')}\n backHref=\"/backend/currencies\"\n backLabel={t('currencies.form.actions.backToList', 'Back to currencies')}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n if (error || !currency) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error ?? t('currencies.form.errors.notFound', 'Currency not found.')} />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={t('currencies.edit.title')}\n backHref=\"/backend/currencies\"\n versionHistory={{ resourceKind: 'currencies.currency', resourceId: currency.id }}\n extraActions={(\n <SendObjectMessageDialog\n object={{\n entityModule: 'currencies',\n entityType: 'currency',\n entityId: currency.id,\n previewData: {\n title: currency.name,\n subtitle: currency.code,\n metadata: {\n [t('currencies.form.field.code')]: currency.code,\n [t('currencies.form.field.name')]: currency.name,\n [t('currencies.form.field.symbol')]: currency.symbol || '-',\n },\n },\n }}\n viewHref={`/backend/currencies/${currency.id}`}\n />\n )}\n fields={[]}\n groups={groups}\n optimisticLockUpdatedAt={currency.updatedAt ?? currency.updated_at ?? null}\n initialValues={{\n code: currency.code,\n name: currency.name,\n symbol: currency.symbol || '',\n decimalPlaces: currency.decimalPlaces,\n thousandsSeparator: currency.thousandsSeparator || '',\n decimalSeparator: currency.decimalSeparator || '',\n isBase: currency.isBase,\n isActive: currency.isActive,\n }}\n submitLabel={t('currencies.form.action.save')}\n cancelHref=\"/backend/currencies\"\n onSubmit={async (values) => {\n // Validate currency code\n const code = String(values.code || '').trim().toUpperCase()\n if (!/^[A-Z]{3}$/.test(code)) {\n throw createCrudFormError(t('currencies.form.errors.codeFormat'), {\n code: t('currencies.form.errors.codeFormat'),\n })\n }\n\n const payload = {\n id: currency.id,\n code,\n name: String(values.name || '').trim(),\n symbol: values.symbol ? String(values.symbol).trim() : null,\n decimalPlaces: values.decimalPlaces ? parseInt(String(values.decimalPlaces)) : 2,\n thousandsSeparator: values.thousandsSeparator ? String(values.thousandsSeparator) : null,\n decimalSeparator: values.decimalSeparator ? String(values.decimalSeparator) : null,\n isBase: !!values.isBase,\n isActive: values.isActive !== false,\n }\n\n await updateCrud('currencies/currencies', payload)\n\n flash(t('currencies.flash.updated'), 'success')\n router.push('/backend/currencies')\n }}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
- "mappings": ";AAmNM,SAGM,KAHN;AAjNN,YAAY,WAAW;AACvB,SAAS,WAAsB,mBAAmB;AAClD,SAAS,MAAM,gBAAgB;AAC/B,SAAS,gBAAoC;AAC7C,SAAS,kBAA8B;AACvC,SAAS,2BAA2B;AACpC,SAAS,aAAa;AACtB,SAAS,SAAS,mCAAmC;AACrD,SAAS,iCAAiC;AAC1C,SAAS,6BAA6B;AACtC,SAAS,6BAA6B,2CAA2C;AACjF,SAAS,0BAA0B;AACnC,SAAS,YAAY;AACrB,SAAS,+BAA+B;AAExC,SAAS,wBAAwB;AACjC,SAAS,qBAAqB,oBAAoB;AAkBnC,SAAR,iBAAkC,EAAE,OAAO,GAAiC;AACjF,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,UAAU;AACzB,QAAM,WAAW,YAAY;AAC7B,QAAM,EAAE,SAAS,eAAe,qBAAqB,IAAI,iBAAiB;AAC1E,QAAM,oBAAoB;AAC1B,QAAM,EAAE,aAAa,kBAAkB,IAAI,mBAKxC;AAAA,IACD,WAAW;AAAA,IACX,gBAAgB,EAAE,8BAA8B,4BAA4B;AAAA,EAC9E,CAAC;AAED,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAA8B,IAAI;AACxE,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,IAAI;AACjD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AAExD,QAAM,UAAU,MAAM;AACpB,mBAAe,eAAe;AAC5B,UAAI;AACF,cAAM,WAAW,MAAM,QAAmC,iCAAiC,QAAQ,EAAE,EAAE;AACvG,YAAI,SAAS,MAAM,SAAS,UAAU,SAAS,OAAO,MAAM,SAAS,GAAG;AACtE,sBAAY,SAAS,OAAO,MAAM,CAAC,CAAC;AAAA,QACtC,WAAW,CAAC,SAAS,IAAI;AACvB,mBAAS,EAAE,6BAA6B,CAAC;AAAA,QAC3C,OAAO;AACL,wBAAc,IAAI;AAAA,QACpB;AAAA,MACF,SAAS,KAAK;AACZ,iBAAS,EAAE,6BAA6B,CAAC;AAAA,MAC3C,UAAE;AACA,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF;AACA,iBAAa;AAAA,EACf,GAAG,CAAC,QAAQ,CAAC,CAAC;AAEd,QAAM,SAAS,MAAM;AAAA,IACnB,MAAM;AAAA,MACJ;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,OAAO,EAAE,+BAA+B;AAAA,QACxC,QAAQ;AAAA,UACN;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,4BAA4B;AAAA,YACrC,aAAa,EAAE,uCAAuC;AAAA,YACtD,UAAU;AAAA,YACV,WAAW;AAAA,YACX,UAAU,EAAE,gCAAgC;AAAA,UAC9C;AAAA,UACA;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,4BAA4B;AAAA,YACrC,aAAa,EAAE,uCAAuC;AAAA,YACtD,UAAU;AAAA,UACZ;AAAA,UACA;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,8BAA8B;AAAA,YACvC,aAAa,EAAE,yCAAyC;AAAA,UAC1D;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,OAAO,EAAE,kCAAkC;AAAA,QAC3C,QAAQ;AAAA,UACN;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,qCAAqC;AAAA,YAC9C,KAAK;AAAA,YACL,KAAK;AAAA,UACP;AAAA,UACA;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,0CAA0C;AAAA,YACnD,aAAa;AAAA,YACb,WAAW;AAAA,UACb;AAAA,UACA;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,wCAAwC;AAAA,YACjD,aAAa;AAAA,YACb,WAAW;AAAA,UACb;AAAA,UACA;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,8BAA8B;AAAA,UACzC;AAAA,UACA;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,gCAAgC;AAAA,UAC3C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,eAAe,MAAM,YAAY,YAAY;AACjD,QAAI,CAAC,SAAU;AAEf,UAAM,YAAY,MAAM,cAAc;AAAA,MACpC,OAAO,EAAE,iCAAiC,EAAE,MAAM,SAAS,KAAK,CAAC;AAAA,MACjE,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,UAAW;AAEhB,QAAI;AACF,YAAM,YAAY;AAAA,QAChB,WAAW,YAAY;AACrB,gBAAM,UAAU,0BAA0B,SAAS,aAAa,SAAS,cAAc,IAAI;AAC3F,gBAAM,OAAO,MAAM,4BAA4B,SAAS,MACtD,QAAQ,8BAA8B;AAAA,YACpC,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU,EAAE,IAAI,SAAS,IAAI,gBAAgB,SAAS,gBAAgB,UAAU,SAAS,SAAS,CAAC;AAAA,UAChH,CAAC,CACF;AACD,cAAI,CAAC,KAAK,IAAI;AACZ,kBAAM,OAAO,OAAO,IAAI,MAAM,qCAAqC,GAAG;AAAA,cACpE,QAAQ,KAAK;AAAA,cACb,GAAK,KAAK,UAA6C,CAAC;AAAA,YAC1D,CAAC;AAAA,UACH;AACA,iBAAO;AAAA,QACT;AAAA,QACA,SAAS;AAAA,UACP,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,YAAY,SAAS;AAAA,UACrB;AAAA,QACF;AAAA,QACA,iBAAiB,EAAE,IAAI,SAAS,GAAG;AAAA,MACrC,CAAC;AAED,YAAM,EAAE,0BAA0B,GAAG,SAAS;AAC9C,aAAO,KAAK,qBAAqB;AAAA,IACnC,SAASA,QAAO;AACd,UAAI,sBAAsBA,QAAO,CAAC,EAAG;AACrC,YAAM,EAAE,8BAA8B,GAAG,OAAO;AAAA,IAClD;AAAA,EACF,GAAG,CAAC,UAAU,GAAG,QAAQ,eAAe,mBAAmB,mBAAmB,WAAW,CAAC;AAM1F;AAAA,IACE,4BAA4B;AAAA,MAC1B,cAAc;AAAA,MACd,YAAY,UAAU,MAAM;AAAA,MAC5B,WAAW,UAAU,aAAa,UAAU,cAAc;AAAA,MAC1D,MAAM;AAAA,MACN,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAEA,MAAI,SAAS;AACX,WACE,qBAAC,QACC;AAAA,0BAAC,YACC,8BAAC,SAAI,WAAU,wCACb,8BAAC,SAAI,WAAU,yBAAyB,YAAE,yBAAyB,GAAE,GACvE,GACF;AAAA,MACC;AAAA,OACH;AAAA,EAEJ;AAEA,MAAI,YAAY;AACd,WACE,qBAAC,QACC;AAAA,0BAAC,YACC;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,EAAE,mCAAmC,qBAAqB;AAAA,UACjE,UAAS;AAAA,UACT,WAAW,EAAE,sCAAsC,oBAAoB;AAAA;AAAA,MACzE,GACF;AAAA,MACC;AAAA,OACH;AAAA,EAEJ;AAEA,MAAI,SAAS,CAAC,UAAU;AACtB,WACE,qBAAC,QACC;AAAA,0BAAC,YACC,8BAAC,gBAAa,OAAO,SAAS,EAAE,mCAAmC,qBAAqB,GAAG,GAC7F;AAAA,MACC;AAAA,OACH;AAAA,EAEJ;AAEA,SACE,qBAAC,QACC;AAAA,wBAAC,YACC;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,uBAAuB;AAAA,QAChC,UAAS;AAAA,QACT,gBAAgB,EAAE,cAAc,uBAAuB,YAAY,SAAS,GAAG;AAAA,QAC/E,cACE;AAAA,UAAC;AAAA;AAAA,YACC,QAAQ;AAAA,cACN,cAAc;AAAA,cACd,YAAY;AAAA,cACZ,UAAU,SAAS;AAAA,cACnB,aAAa;AAAA,gBACX,OAAO,SAAS;AAAA,gBAChB,UAAU,SAAS;AAAA,gBACnB,UAAU;AAAA,kBACR,CAAC,EAAE,4BAA4B,CAAC,GAAG,SAAS;AAAA,kBAC5C,CAAC,EAAE,4BAA4B,CAAC,GAAG,SAAS;AAAA,kBAC5C,CAAC,EAAE,8BAA8B,CAAC,GAAG,SAAS,UAAU;AAAA,gBAC1D;AAAA,cACF;AAAA,YACF;AAAA,YACA,UAAU,uBAAuB,SAAS,EAAE;AAAA;AAAA,QAC9C;AAAA,QAEF,QAAQ,CAAC;AAAA,QACT;AAAA,QACA,yBAAyB,SAAS,aAAa,SAAS,cAAc;AAAA,QACtE,eAAe;AAAA,UACb,MAAM,SAAS;AAAA,UACf,MAAM,SAAS;AAAA,UACf,QAAQ,SAAS,UAAU;AAAA,UAC3B,eAAe,SAAS;AAAA,UACxB,oBAAoB,SAAS,sBAAsB;AAAA,UACnD,kBAAkB,SAAS,oBAAoB;AAAA,UAC/C,QAAQ,SAAS;AAAA,UACjB,UAAU,SAAS;AAAA,QACrB;AAAA,QACA,aAAa,EAAE,6BAA6B;AAAA,QAC5C,YAAW;AAAA,QACX,UAAU,OAAO,WAAW;AAE1B,gBAAM,OAAO,OAAO,OAAO,QAAQ,EAAE,EAAE,KAAK,EAAE,YAAY;AAC1D,cAAI,CAAC,aAAa,KAAK,IAAI,GAAG;AAC5B,kBAAM,oBAAoB,EAAE,mCAAmC,GAAG;AAAA,cAChE,MAAM,EAAE,mCAAmC;AAAA,YAC7C,CAAC;AAAA,UACH;AAEA,gBAAM,UAAU;AAAA,YACd,IAAI,SAAS;AAAA,YACb;AAAA,YACA,MAAM,OAAO,OAAO,QAAQ,EAAE,EAAE,KAAK;AAAA,YACrC,QAAQ,OAAO,SAAS,OAAO,OAAO,MAAM,EAAE,KAAK,IAAI;AAAA,YACvD,eAAe,OAAO,gBAAgB,SAAS,OAAO,OAAO,aAAa,CAAC,IAAI;AAAA,YAC/E,oBAAoB,OAAO,qBAAqB,OAAO,OAAO,kBAAkB,IAAI;AAAA,YACpF,kBAAkB,OAAO,mBAAmB,OAAO,OAAO,gBAAgB,IAAI;AAAA,YAC9E,QAAQ,CAAC,CAAC,OAAO;AAAA,YACjB,UAAU,OAAO,aAAa;AAAA,UAChC;AAEA,gBAAM,WAAW,yBAAyB,OAAO;AAEjD,gBAAM,EAAE,0BAA0B,GAAG,SAAS;AAC9C,iBAAO,KAAK,qBAAqB;AAAA,QACnC;AAAA;AAAA,IACF,GACF;AAAA,IACC;AAAA,KACH;AAEJ;",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter, usePathname } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport { DataLoader } from '@open-mercato/ui/primitives/DataLoader'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'\n\ntype CurrencyData = {\n id: string\n code: string\n name: string\n symbol: string | null\n decimalPlaces: number\n thousandsSeparator: string | null\n decimalSeparator: string | null\n isBase: boolean\n isActive: boolean\n organizationId: string\n tenantId: string\n updatedAt?: string | null\n updated_at?: string | null\n}\n\nexport default function EditCurrencyPage({ params }: { params?: { id?: string } }) {\n const t = useT()\n const router = useRouter()\n const pathname = usePathname()\n const { confirm: confirmDialog, ConfirmDialogElement } = useConfirmDialog()\n const mutationContextId = 'currencies-edit:delete'\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: mutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const [currency, setCurrency] = React.useState<CurrencyData | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n\n React.useEffect(() => {\n async function loadCurrency() {\n try {\n const response = await apiCall<{ items: CurrencyData[] }>(`/api/currencies/currencies?id=${params?.id}`)\n if (response.ok && response.result && response.result.items.length > 0) {\n setCurrency(response.result.items[0])\n } else if (!response.ok) {\n setError(t('currencies.form.errors.load'))\n } else {\n setIsNotFound(true)\n }\n } catch (err) {\n setError(t('currencies.form.errors.load'))\n } finally {\n setLoading(false)\n }\n }\n loadCurrency()\n }, [params, t])\n\n const groups = React.useMemo<CrudFormGroup[]>(\n () => [\n {\n id: 'basic',\n column: 1,\n title: t('currencies.form.group.details'),\n fields: [\n {\n id: 'code',\n type: 'text',\n label: t('currencies.form.field.code'),\n placeholder: t('currencies.form.field.codePlaceholder'),\n required: true,\n maxLength: 3,\n helpText: t('currencies.form.field.codeHelp'),\n },\n {\n id: 'name',\n type: 'text',\n label: t('currencies.form.field.name'),\n placeholder: t('currencies.form.field.namePlaceholder'),\n required: true,\n },\n {\n id: 'symbol',\n type: 'text',\n label: t('currencies.form.field.symbol'),\n placeholder: t('currencies.form.field.symbolPlaceholder'),\n },\n ],\n },\n {\n id: 'formatting',\n column: 2,\n title: t('currencies.form.group.formatting'),\n fields: [\n {\n id: 'decimalPlaces',\n type: 'number',\n label: t('currencies.form.field.decimalPlaces'),\n min: 0,\n max: 8,\n },\n {\n id: 'thousandsSeparator',\n type: 'text',\n label: t('currencies.form.field.thousandsSeparator'),\n placeholder: ',',\n maxLength: 5,\n },\n {\n id: 'decimalSeparator',\n type: 'text',\n label: t('currencies.form.field.decimalSeparator'),\n placeholder: '.',\n maxLength: 5,\n },\n {\n id: 'isBase',\n type: 'checkbox',\n label: t('currencies.form.field.isBase'),\n },\n {\n id: 'isActive',\n type: 'checkbox',\n label: t('currencies.form.field.isActive'),\n },\n ],\n },\n ],\n [t]\n )\n\n const handleDelete = React.useCallback(async () => {\n if (!currency) return\n\n const confirmed = await confirmDialog({\n title: t('currencies.list.confirmDelete', { code: currency.code }),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n try {\n await runMutation({\n operation: async () => {\n const headers = buildOptimisticLockHeader(currency.updatedAt ?? currency.updated_at ?? null)\n const call = await withScopedApiRequestHeaders(headers, () => (\n apiCall('/api/currencies/currencies', {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: currency.id, organizationId: currency.organizationId, tenantId: currency.tenantId }),\n })\n ))\n if (!call.ok) {\n throw Object.assign(new Error('[internal] currencies.delete failed'), {\n status: call.status,\n ...((call.result as Record<string, unknown> | null) ?? {}),\n })\n }\n return call\n },\n context: {\n formId: mutationContextId,\n resourceKind: 'currencies.currency',\n resourceId: currency.id,\n retryLastMutation,\n },\n mutationPayload: { id: currency.id },\n })\n\n flash(t('currencies.flash.deleted'), 'success')\n router.push('/backend/currencies')\n } catch (error) {\n if (surfaceRecordConflict(error, t)) return\n flash(t('currencies.flash.deleteError'), 'error')\n }\n }, [currency, t, router, confirmDialog, mutationContextId, retryLastMutation, runMutation])\n\n // Publish page-load record context to the AppShell-owned `backend:record:current`\n // mount so the enterprise record_locks widget resolves `currencies.currency` + id\n // explicitly. The resourceKind mirrors the CrudForm `versionHistory` so the held\n // lock matches the save-time conflict surface for the same currency.\n useSetCurrentRecordInjectionContext(\n buildRecordInjectionContext({\n resourceKind: 'currencies.currency',\n resourceId: currency?.id ?? null,\n updatedAt: currency?.updatedAt ?? currency?.updated_at ?? null,\n data: currency as Record<string, unknown> | null,\n path: pathname,\n }),\n )\n\n if (loading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex items-center justify-center p-8\">\n <div className=\"text-muted-foreground\">{t('currencies.form.loading')}</div>\n </div>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('currencies.form.errors.notFound', 'Currency not found.')}\n backHref=\"/backend/currencies\"\n backLabel={t('currencies.form.actions.backToList', 'Back to currencies')}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n if (error || !currency) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error ?? t('currencies.form.errors.notFound', 'Currency not found.')} />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={t('currencies.edit.title')}\n backHref=\"/backend/currencies\"\n versionHistory={{ resourceKind: 'currencies.currency', resourceId: currency.id }}\n extraActions={(\n <SendObjectMessageDialog\n object={{\n entityModule: 'currencies',\n entityType: 'currency',\n entityId: currency.id,\n previewData: {\n title: currency.name,\n subtitle: currency.code,\n metadata: {\n [t('currencies.form.field.code')]: currency.code,\n [t('currencies.form.field.name')]: currency.name,\n [t('currencies.form.field.symbol')]: currency.symbol || '-',\n },\n },\n }}\n viewHref={`/backend/currencies/${currency.id}`}\n />\n )}\n fields={[]}\n groups={groups}\n optimisticLockUpdatedAt={currency.updatedAt ?? currency.updated_at ?? null}\n initialValues={{\n code: currency.code,\n name: currency.name,\n symbol: currency.symbol || '',\n decimalPlaces: currency.decimalPlaces,\n thousandsSeparator: currency.thousandsSeparator || '',\n decimalSeparator: currency.decimalSeparator || '',\n isBase: currency.isBase,\n isActive: currency.isActive,\n }}\n submitLabel={t('currencies.form.action.save')}\n cancelHref=\"/backend/currencies\"\n onSubmit={async (values) => {\n // Validate currency code\n const code = String(values.code || '').trim().toUpperCase()\n if (!/^[A-Z]{3}$/.test(code)) {\n throw createCrudFormError(t('currencies.form.errors.codeFormat'), {\n code: t('currencies.form.errors.codeFormat'),\n })\n }\n\n const payload = {\n id: currency.id,\n code,\n name: String(values.name || '').trim(),\n symbol: values.symbol ? String(values.symbol).trim() : null,\n decimalPlaces: values.decimalPlaces ? parseInt(String(values.decimalPlaces)) : 2,\n thousandsSeparator: values.thousandsSeparator ? String(values.thousandsSeparator) : null,\n decimalSeparator: values.decimalSeparator ? String(values.decimalSeparator) : null,\n isBase: !!values.isBase,\n isActive: values.isActive !== false,\n }\n\n await updateCrud('currencies/currencies', payload)\n\n flash(t('currencies.flash.updated'), 'success')\n router.push('/backend/currencies')\n }}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["error"]
7
7
  }
@@ -9,6 +9,7 @@ import { Button } from "@open-mercato/ui/primitives/button";
9
9
  import { Kbd } from "@open-mercato/ui/primitives/kbd";
10
10
  import { ActivityTimelineFilters } from "./ActivityTimelineFilters.js";
11
11
  import { ActivityTimeline } from "./ActivityTimeline.js";
12
+ import { resolveAuthorUserNames } from "./authorUserLookup.js";
12
13
  import { createLogger } from "@open-mercato/shared/lib/logger";
13
14
  const logger = createLogger("customers");
14
15
  function toDateOnly(value) {
@@ -247,19 +248,10 @@ function ActivitiesSection({
247
248
  }
248
249
  }
249
250
  if (unresolvedIds.size === 0) return;
250
- for (const uid of unresolvedIds) resolvedUserIdsRef.current.add(uid);
251
251
  const controller = new AbortController();
252
- readApiResultOrThrow(
253
- `/api/auth/users?ids=${[...unresolvedIds].join(",")}`,
254
- { signal: controller.signal }
255
- ).then((data) => {
256
- const users = Array.isArray(data?.items) ? data.items : [];
257
- const nameMap = /* @__PURE__ */ new Map();
258
- for (const user of users) {
259
- const userId = typeof user.id === "string" ? user.id : null;
260
- const name = typeof user.display_name === "string" && user.display_name.trim() ? user.display_name.trim() : typeof user.email === "string" ? user.email : null;
261
- if (userId && name) nameMap.set(userId, name);
262
- }
252
+ resolveAuthorUserNames([...unresolvedIds], controller.signal).then(({ names: nameMap, resolvedIds }) => {
253
+ if (controller.signal.aborted) return;
254
+ for (const uid of resolvedIds) resolvedUserIdsRef.current.add(uid);
263
255
  if (nameMap.size > 0) {
264
256
  setActivities(
265
257
  (prev) => prev.map((a) => {