@open-mercato/core 0.7.1-develop.7106.1.53fe9dd1b1 → 0.7.1-develop.7112.1.d486e6f1b2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/api/users/resend-invite/route.js +1 -1
- package/dist/modules/auth/api/users/resend-invite/route.js.map +2 -2
- package/dist/modules/business_rules/backend/logs/[id]/page.js +3 -9
- package/dist/modules/business_rules/backend/logs/[id]/page.js.map +2 -2
- package/dist/modules/business_rules/backend/rules/[id]/page.js +3 -9
- package/dist/modules/business_rules/backend/rules/[id]/page.js.map +2 -2
- package/dist/modules/business_rules/backend/sets/[id]/page.js +3 -9
- package/dist/modules/business_rules/backend/sets/[id]/page.js.map +2 -2
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js +6 -4
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivitiesSection.js +4 -12
- package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/authorUserLookup.js +46 -0
- package/dist/modules/customers/components/detail/authorUserLookup.js.map +7 -0
- package/dist/modules/query_index/lib/engine.js +2 -1
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/[id]/page.js +3 -9
- package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/events/[id]/page.js +3 -9
- package/dist/modules/workflows/backend/events/[id]/page.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/api/users/resend-invite/route.ts +1 -1
- package/src/modules/business_rules/backend/logs/[id]/page.tsx +3 -11
- package/src/modules/business_rules/backend/rules/[id]/page.tsx +3 -11
- package/src/modules/business_rules/backend/sets/[id]/page.tsx +3 -11
- package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.tsx +7 -5
- package/src/modules/communication_channels/i18n/de.json +1 -0
- package/src/modules/communication_channels/i18n/en.json +1 -0
- package/src/modules/communication_channels/i18n/es.json +1 -0
- package/src/modules/communication_channels/i18n/ko.json +1 -0
- package/src/modules/communication_channels/i18n/pl.json +1 -0
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +1 -1
- package/src/modules/customers/components/detail/ActivitiesSection.tsx +8 -18
- package/src/modules/customers/components/detail/authorUserLookup.ts +85 -0
- package/src/modules/query_index/lib/engine.ts +9 -0
- package/src/modules/workflows/backend/definitions/[id]/page.tsx +3 -11
- package/src/modules/workflows/backend/events/[id]/page.tsx +3 -12
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { useRouter,
|
|
4
|
+
import { useRouter, usePathname } from "next/navigation";
|
|
5
5
|
import { useQuery } from "@tanstack/react-query";
|
|
6
6
|
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
7
7
|
import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
|
|
@@ -30,18 +30,12 @@ import { TransitionsEditor } from "../../../components/TransitionsEditor.js";
|
|
|
30
30
|
import { DefinitionTriggersEditor } from "../../../components/DefinitionTriggersEditor.js";
|
|
31
31
|
import { MobileDefinitionDetail } from "../../../components/mobile/MobileDefinitionDetail.js";
|
|
32
32
|
import { useIsMobile } from "@open-mercato/ui/hooks/useIsMobile";
|
|
33
|
-
function EditWorkflowDefinitionPage() {
|
|
33
|
+
function EditWorkflowDefinitionPage({ params }) {
|
|
34
34
|
const router = useRouter();
|
|
35
|
-
const params = useParams();
|
|
36
35
|
const pathname = usePathname();
|
|
37
36
|
const t = useT();
|
|
38
37
|
const isMobile = useIsMobile();
|
|
39
|
-
|
|
40
|
-
if (params?.slug && Array.isArray(params.slug)) {
|
|
41
|
-
definitionId = params.slug[1];
|
|
42
|
-
} else if (params?.id) {
|
|
43
|
-
definitionId = Array.isArray(params.id) ? params.id[0] : params.id;
|
|
44
|
-
}
|
|
38
|
+
const definitionId = params?.id;
|
|
45
39
|
const { data: definition, isLoading, error } = useQuery({
|
|
46
40
|
queryKey: ["workflow-definition", definitionId],
|
|
47
41
|
queryFn: async () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/workflows/backend/definitions/%5Bid%5D/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\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 { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\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 { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { readJsonSafe } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { formatWorkflowValidationError } from '../../../lib/format-validation-error'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport {\n workflowDefinitionFormSchema,\n createFormGroups,\n createFieldDefinitions,\n parseWorkflowToFormValues,\n buildWorkflowPayload,\n type WorkflowDefinitionFormValues,\n} from '../../../components/formConfig'\nimport { StepsEditor } from '../../../components/StepsEditor'\nimport { TransitionsEditor } from '../../../components/TransitionsEditor'\nimport { DefinitionTriggersEditor } from '../../../components/DefinitionTriggersEditor'\nimport { MobileDefinitionDetail } from '../../../components/mobile/MobileDefinitionDetail'\nimport { useIsMobile } from '@open-mercato/ui/hooks/useIsMobile'\nimport type { WorkflowDefinitionTrigger } from '../../../data/entities'\n\nexport default function EditWorkflowDefinitionPage() {\n const router = useRouter()\n const params = useParams()\n const pathname = usePathname()\n const t = useT()\n const isMobile = useIsMobile()\n\n // Handle catch-all route: params.slug = ['definitions', 'uuid']\n let definitionId: string | undefined\n if (params?.slug && Array.isArray(params.slug)) {\n definitionId = params.slug[1] // Second element is the ID\n } else if (params?.id) {\n definitionId = Array.isArray(params.id) ? params.id[0] : params.id\n }\n\n const { data: definition, isLoading, error } = useQuery({\n queryKey: ['workflow-definition', definitionId],\n queryFn: async () => {\n const response = await apiFetch(`/api/workflows/definitions/${definitionId}`)\n if (!response.ok) {\n const err = new Error(t('workflows.errors.fetchFailed')) as Error & { status?: number }\n err.status = response.status\n throw err\n }\n const result = await response.json()\n return result.data\n },\n enabled: !!definitionId,\n retry: (failureCount, err) => {\n if ((err as { status?: number }).status === 404) return false\n return failureCount < 2\n },\n })\n\n const isNotFound = (error as { status?: number } | null)?.status === 404\n\n const initialValues = React.useMemo(() => {\n if (definition) {\n return parseWorkflowToFormValues(definition)\n }\n return null\n }, [definition])\n\n const [triggers, setTriggers] = React.useState<WorkflowDefinitionTrigger[]>([])\n\n React.useEffect(() => {\n setTriggers(initialValues?.triggers ?? [])\n }, [initialValues])\n\n const source = definition?.source as 'code' | 'code_override' | 'user' | undefined\n const isCodeOnly = source === 'code'\n const isCodeOverride = source === 'code_override'\n\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const mutationContextId = React.useMemo(\n () => `workflows.definitions.detail:${definitionId ?? 'unknown'}`,\n [definitionId],\n )\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: mutationContextId,\n })\n\n const handleSubmit = async (values: WorkflowDefinitionFormValues) => {\n const payload = buildWorkflowPayload({ ...values, triggers })\n const optimisticLockHeader = buildOptimisticLockHeader(\n typeof definition?.updatedAt === 'string' ? definition.updatedAt : null,\n )\n\n await runMutation({\n operation: async () => {\n const updateDefinition = () => apiFetch(`/api/workflows/definitions/${definitionId}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n const response = Object.keys(optimisticLockHeader).length > 0\n ? await withScopedApiHeaders(optimisticLockHeader, updateDefinition)\n : await updateDefinition()\n if (!response.ok) {\n const errorBody = await readJsonSafe<{\n error?: string\n code?: string\n currentUpdatedAt?: string\n expectedUpdatedAt?: string\n details?: Array<{ path?: Array<string | number>; message?: string }>\n }>(response, null)\n throw Object.assign(\n new Error(formatWorkflowValidationError(errorBody, t('workflows.errors.updateFailed'))),\n { status: response.status, ...(errorBody ?? {}) },\n )\n }\n return response\n },\n mutationPayload: { resourceId: definitionId, operation: 'update' },\n context: {\n formId: mutationContextId,\n resourceKind: 'workflows.definition',\n resourceId: definitionId,\n operation: 'update',\n retryLastMutation,\n },\n })\n\n router.push('/backend/definitions')\n router.refresh()\n }\n\n const handleCustomize = async () => {\n try {\n const result = await runMutation({\n operation: async () => {\n const response = await apiFetch(`/api/workflows/definitions/${definitionId}/customize`, {\n method: 'POST',\n })\n if (!response.ok) {\n const errorBody = await readJsonSafe<{ error?: string }>(response, null)\n throw new Error(errorBody?.error || t('workflows.errors.updateFailed'))\n }\n return readJsonSafe<{ data?: { id?: string } }>(response, null)\n },\n mutationPayload: { resourceId: definitionId, operation: 'customize' },\n context: {\n formId: mutationContextId,\n resourceKind: 'workflows.definition',\n resourceId: definitionId,\n operation: 'customize',\n retryLastMutation,\n },\n })\n if (result?.data?.id) {\n router.push(`/backend/definitions/${result.data.id}`)\n router.refresh()\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : t('workflows.errors.updateFailed')\n flash(message, 'error')\n }\n }\n\n const handleResetToCode = async () => {\n const confirmed = await confirm({\n title: t('workflows.actions.resetToCode'),\n description: t('workflows.actions.resetConfirm'),\n confirmText: t('workflows.actions.resetToCode'),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n try {\n const result = await runMutation({\n operation: async () => {\n const response = await apiFetch(`/api/workflows/definitions/${definitionId}/reset-to-code`, {\n method: 'POST',\n })\n if (!response.ok) {\n const errorBody = await readJsonSafe<{ error?: string }>(response, null)\n throw new Error(errorBody?.error || t('workflows.messages.updateFailed'))\n }\n return readJsonSafe<{ data?: { id?: string } }>(response, null)\n },\n mutationPayload: { resourceId: definitionId, operation: 'reset-to-code' },\n context: {\n formId: mutationContextId,\n resourceKind: 'workflows.definition',\n resourceId: definitionId,\n operation: 'reset-to-code',\n retryLastMutation,\n },\n })\n flash(t('workflows.messages.updated'), 'success')\n const codeId = result?.data?.id || `code:${definition?.workflowId}`\n router.push(`/backend/definitions/${codeId}`)\n router.refresh()\n } catch {\n flash(t('workflows.messages.updateFailed'), 'error')\n }\n }\n\n const fields = React.useMemo(() => createFieldDefinitions(t), [t])\n\n const formGroups = React.useMemo(\n () => isMobile ? [] : createFormGroups(t, StepsEditor, TransitionsEditor),\n [t, isMobile]\n )\n\n const navigateToVisualEditor = React.useCallback(() => {\n router.push(`/backend/definitions/visual-editor?id=${definitionId}`)\n }, [router, definitionId])\n\n // Publish page-load record context to the AppShell-owned `backend:record:current`\n // mount so the enterprise record_locks widget resolves `workflows.definition` + id\n // explicitly. The resourceKind mirrors the save-time `useGuardedMutation` context\n // and server guard so the held lock matches the conflict surface for the same\n // definition across both the form and visual editors.\n useSetCurrentRecordInjectionContext(\n buildRecordInjectionContext({\n resourceKind: 'workflows.definition',\n resourceId: definitionId ?? null,\n updatedAt: typeof definition?.updatedAt === 'string' ? definition.updatedAt : null,\n data: (definition ?? 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('workflows.edit.loading')}</span>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('workflows.errors.notFound', t('workflows.errors.loadFailed'))}\n backHref=\"/backend/definitions\"\n backLabel={t('workflows.backToList')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !definition) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={t('workflows.errors.loadFailed')} />\n <div className=\"mt-4 flex justify-center\">\n <Button asChild variant=\"outline\">\n <a href=\"/backend/definitions\">{t('workflows.backToList')}</a>\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 {isCodeOnly && (\n <Alert status=\"information\" className=\"mb-4\">\n <AlertDescription className=\"flex items-center justify-between\">\n <span>{t('workflows.source.code.readonlyBanner')}</span>\n <Button variant=\"outline\" size=\"sm\" onClick={handleCustomize}>\n {t('workflows.actions.customize')}\n </Button>\n </AlertDescription>\n </Alert>\n )}\n {isCodeOverride && (\n <Alert status=\"warning\" className=\"mb-4\">\n <AlertDescription className=\"flex items-center justify-between\">\n <span>{t('workflows.source.code_override.banner')}</span>\n <Button variant=\"outline\" size=\"sm\" onClick={handleResetToCode}>\n {t('workflows.actions.resetToCode')}\n </Button>\n </AlertDescription>\n </Alert>\n )}\n <div className=\"mb-4 p-4 bg-status-info-bg border border-status-info-border rounded-lg flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-start gap-3\">\n <svg className=\"w-5 h-5 text-status-info-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n <div>\n <p className=\"text-sm font-medium text-status-info-text\">\n {t('workflows.edit.visualEditorAvailable')}\n </p>\n <p className=\"text-xs text-status-info-text mt-0.5\">\n {t('workflows.edit.visualEditorDescription')}\n </p>\n </div>\n </div>\n <Button asChild variant=\"outline\" size=\"sm\" className=\"w-full sm:w-auto border-status-info-border text-status-info-text hover:bg-status-info-bg\">\n <a href={`/backend/definitions/visual-editor?id=${definitionId}`}>\n {t('workflows.actions.openVisualEditor')}\n </a>\n </Button>\n </div>\n <CrudForm\n key={definitionId}\n title={isCodeOnly ? definition?.workflowName || t('workflows.edit.title') : t('workflows.edit.title')}\n backHref=\"/backend/definitions\"\n schema={workflowDefinitionFormSchema}\n fields={fields}\n initialValues={initialValues}\n onSubmit={handleSubmit}\n cancelHref=\"/backend/definitions\"\n groups={formGroups}\n submitLabel={isCodeOnly ? t('workflows.actions.customize') : t('workflows.form.update')}\n {...(isCodeOnly ? { readOnly: true } : {})}\n />\n\n {/* Mobile Steps & Transitions View */}\n {isMobile && initialValues && (\n <div className=\"mt-4\">\n <MobileDefinitionDetail\n values={initialValues}\n onEditStep={navigateToVisualEditor}\n onDeleteStep={navigateToVisualEditor}\n onAddStep={navigateToVisualEditor}\n onEditTransition={navigateToVisualEditor}\n onDeleteTransition={navigateToVisualEditor}\n onAddTransition={navigateToVisualEditor}\n />\n </div>\n )}\n\n {/* Event Triggers Section */}\n <div className=\"mt-8\">\n <DefinitionTriggersEditor\n value={triggers}\n onChange={setTriggers}\n />\n </div>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\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 { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\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 { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { readJsonSafe } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { formatWorkflowValidationError } from '../../../lib/format-validation-error'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport {\n workflowDefinitionFormSchema,\n createFormGroups,\n createFieldDefinitions,\n parseWorkflowToFormValues,\n buildWorkflowPayload,\n type WorkflowDefinitionFormValues,\n} from '../../../components/formConfig'\nimport { StepsEditor } from '../../../components/StepsEditor'\nimport { TransitionsEditor } from '../../../components/TransitionsEditor'\nimport { DefinitionTriggersEditor } from '../../../components/DefinitionTriggersEditor'\nimport { MobileDefinitionDetail } from '../../../components/mobile/MobileDefinitionDetail'\nimport { useIsMobile } from '@open-mercato/ui/hooks/useIsMobile'\nimport type { WorkflowDefinitionTrigger } from '../../../data/entities'\n\nexport default function EditWorkflowDefinitionPage({ params }: { params?: { id?: string } }) {\n const router = useRouter()\n const pathname = usePathname()\n const t = useT()\n const isMobile = useIsMobile()\n const definitionId = params?.id\n\n const { data: definition, isLoading, error } = useQuery({\n queryKey: ['workflow-definition', definitionId],\n queryFn: async () => {\n const response = await apiFetch(`/api/workflows/definitions/${definitionId}`)\n if (!response.ok) {\n const err = new Error(t('workflows.errors.fetchFailed')) as Error & { status?: number }\n err.status = response.status\n throw err\n }\n const result = await response.json()\n return result.data\n },\n enabled: !!definitionId,\n retry: (failureCount, err) => {\n if ((err as { status?: number }).status === 404) return false\n return failureCount < 2\n },\n })\n\n const isNotFound = (error as { status?: number } | null)?.status === 404\n\n const initialValues = React.useMemo(() => {\n if (definition) {\n return parseWorkflowToFormValues(definition)\n }\n return null\n }, [definition])\n\n const [triggers, setTriggers] = React.useState<WorkflowDefinitionTrigger[]>([])\n\n React.useEffect(() => {\n setTriggers(initialValues?.triggers ?? [])\n }, [initialValues])\n\n const source = definition?.source as 'code' | 'code_override' | 'user' | undefined\n const isCodeOnly = source === 'code'\n const isCodeOverride = source === 'code_override'\n\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const mutationContextId = React.useMemo(\n () => `workflows.definitions.detail:${definitionId ?? 'unknown'}`,\n [definitionId],\n )\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: mutationContextId,\n })\n\n const handleSubmit = async (values: WorkflowDefinitionFormValues) => {\n const payload = buildWorkflowPayload({ ...values, triggers })\n const optimisticLockHeader = buildOptimisticLockHeader(\n typeof definition?.updatedAt === 'string' ? definition.updatedAt : null,\n )\n\n await runMutation({\n operation: async () => {\n const updateDefinition = () => apiFetch(`/api/workflows/definitions/${definitionId}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n const response = Object.keys(optimisticLockHeader).length > 0\n ? await withScopedApiHeaders(optimisticLockHeader, updateDefinition)\n : await updateDefinition()\n if (!response.ok) {\n const errorBody = await readJsonSafe<{\n error?: string\n code?: string\n currentUpdatedAt?: string\n expectedUpdatedAt?: string\n details?: Array<{ path?: Array<string | number>; message?: string }>\n }>(response, null)\n throw Object.assign(\n new Error(formatWorkflowValidationError(errorBody, t('workflows.errors.updateFailed'))),\n { status: response.status, ...(errorBody ?? {}) },\n )\n }\n return response\n },\n mutationPayload: { resourceId: definitionId, operation: 'update' },\n context: {\n formId: mutationContextId,\n resourceKind: 'workflows.definition',\n resourceId: definitionId,\n operation: 'update',\n retryLastMutation,\n },\n })\n\n router.push('/backend/definitions')\n router.refresh()\n }\n\n const handleCustomize = async () => {\n try {\n const result = await runMutation({\n operation: async () => {\n const response = await apiFetch(`/api/workflows/definitions/${definitionId}/customize`, {\n method: 'POST',\n })\n if (!response.ok) {\n const errorBody = await readJsonSafe<{ error?: string }>(response, null)\n throw new Error(errorBody?.error || t('workflows.errors.updateFailed'))\n }\n return readJsonSafe<{ data?: { id?: string } }>(response, null)\n },\n mutationPayload: { resourceId: definitionId, operation: 'customize' },\n context: {\n formId: mutationContextId,\n resourceKind: 'workflows.definition',\n resourceId: definitionId,\n operation: 'customize',\n retryLastMutation,\n },\n })\n if (result?.data?.id) {\n router.push(`/backend/definitions/${result.data.id}`)\n router.refresh()\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : t('workflows.errors.updateFailed')\n flash(message, 'error')\n }\n }\n\n const handleResetToCode = async () => {\n const confirmed = await confirm({\n title: t('workflows.actions.resetToCode'),\n description: t('workflows.actions.resetConfirm'),\n confirmText: t('workflows.actions.resetToCode'),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n try {\n const result = await runMutation({\n operation: async () => {\n const response = await apiFetch(`/api/workflows/definitions/${definitionId}/reset-to-code`, {\n method: 'POST',\n })\n if (!response.ok) {\n const errorBody = await readJsonSafe<{ error?: string }>(response, null)\n throw new Error(errorBody?.error || t('workflows.messages.updateFailed'))\n }\n return readJsonSafe<{ data?: { id?: string } }>(response, null)\n },\n mutationPayload: { resourceId: definitionId, operation: 'reset-to-code' },\n context: {\n formId: mutationContextId,\n resourceKind: 'workflows.definition',\n resourceId: definitionId,\n operation: 'reset-to-code',\n retryLastMutation,\n },\n })\n flash(t('workflows.messages.updated'), 'success')\n const codeId = result?.data?.id || `code:${definition?.workflowId}`\n router.push(`/backend/definitions/${codeId}`)\n router.refresh()\n } catch {\n flash(t('workflows.messages.updateFailed'), 'error')\n }\n }\n\n const fields = React.useMemo(() => createFieldDefinitions(t), [t])\n\n const formGroups = React.useMemo(\n () => isMobile ? [] : createFormGroups(t, StepsEditor, TransitionsEditor),\n [t, isMobile]\n )\n\n const navigateToVisualEditor = React.useCallback(() => {\n router.push(`/backend/definitions/visual-editor?id=${definitionId}`)\n }, [router, definitionId])\n\n // Publish page-load record context to the AppShell-owned `backend:record:current`\n // mount so the enterprise record_locks widget resolves `workflows.definition` + id\n // explicitly. The resourceKind mirrors the save-time `useGuardedMutation` context\n // and server guard so the held lock matches the conflict surface for the same\n // definition across both the form and visual editors.\n useSetCurrentRecordInjectionContext(\n buildRecordInjectionContext({\n resourceKind: 'workflows.definition',\n resourceId: definitionId ?? null,\n updatedAt: typeof definition?.updatedAt === 'string' ? definition.updatedAt : null,\n data: (definition ?? 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('workflows.edit.loading')}</span>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('workflows.errors.notFound', t('workflows.errors.loadFailed'))}\n backHref=\"/backend/definitions\"\n backLabel={t('workflows.backToList')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !definition) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={t('workflows.errors.loadFailed')} />\n <div className=\"mt-4 flex justify-center\">\n <Button asChild variant=\"outline\">\n <a href=\"/backend/definitions\">{t('workflows.backToList')}</a>\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 {isCodeOnly && (\n <Alert status=\"information\" className=\"mb-4\">\n <AlertDescription className=\"flex items-center justify-between\">\n <span>{t('workflows.source.code.readonlyBanner')}</span>\n <Button variant=\"outline\" size=\"sm\" onClick={handleCustomize}>\n {t('workflows.actions.customize')}\n </Button>\n </AlertDescription>\n </Alert>\n )}\n {isCodeOverride && (\n <Alert status=\"warning\" className=\"mb-4\">\n <AlertDescription className=\"flex items-center justify-between\">\n <span>{t('workflows.source.code_override.banner')}</span>\n <Button variant=\"outline\" size=\"sm\" onClick={handleResetToCode}>\n {t('workflows.actions.resetToCode')}\n </Button>\n </AlertDescription>\n </Alert>\n )}\n <div className=\"mb-4 p-4 bg-status-info-bg border border-status-info-border rounded-lg flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-start gap-3\">\n <svg className=\"w-5 h-5 text-status-info-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n <div>\n <p className=\"text-sm font-medium text-status-info-text\">\n {t('workflows.edit.visualEditorAvailable')}\n </p>\n <p className=\"text-xs text-status-info-text mt-0.5\">\n {t('workflows.edit.visualEditorDescription')}\n </p>\n </div>\n </div>\n <Button asChild variant=\"outline\" size=\"sm\" className=\"w-full sm:w-auto border-status-info-border text-status-info-text hover:bg-status-info-bg\">\n <a href={`/backend/definitions/visual-editor?id=${definitionId}`}>\n {t('workflows.actions.openVisualEditor')}\n </a>\n </Button>\n </div>\n <CrudForm\n key={definitionId}\n title={isCodeOnly ? definition?.workflowName || t('workflows.edit.title') : t('workflows.edit.title')}\n backHref=\"/backend/definitions\"\n schema={workflowDefinitionFormSchema}\n fields={fields}\n initialValues={initialValues}\n onSubmit={handleSubmit}\n cancelHref=\"/backend/definitions\"\n groups={formGroups}\n submitLabel={isCodeOnly ? t('workflows.actions.customize') : t('workflows.form.update')}\n {...(isCodeOnly ? { readOnly: true } : {})}\n />\n\n {/* Mobile Steps & Transitions View */}\n {isMobile && initialValues && (\n <div className=\"mt-4\">\n <MobileDefinitionDetail\n values={initialValues}\n onEditStep={navigateToVisualEditor}\n onDeleteStep={navigateToVisualEditor}\n onAddStep={navigateToVisualEditor}\n onEditTransition={navigateToVisualEditor}\n onDeleteTransition={navigateToVisualEditor}\n onAddTransition={navigateToVisualEditor}\n />\n </div>\n )}\n\n {/* Event Triggers Section */}\n <div className=\"mt-8\">\n <DefinitionTriggersEditor\n value={triggers}\n onChange={setTriggers}\n />\n </div>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AA4OU,SACE,KADF;AA1OV,YAAY,WAAW;AACvB,SAAS,WAAW,mBAAmB;AACvC,SAAS,gBAAgB;AACzB,SAAS,MAAM,gBAAgB;AAC/B,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,OAAO,wBAAwB;AACxC,SAAS,UAAU,4BAA4B;AAC/C,SAAS,iCAAiC;AAC1C,SAAS,6BAA6B,2CAA2C;AACjF,SAAS,cAAc,2BAA2B;AAClD,SAAS,oBAAoB;AAC7B,SAAS,qCAAqC;AAC9C,SAAS,aAAa;AACtB,SAAS,YAAY;AACrB,SAAS,wBAAwB;AACjC,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,gCAAgC;AACzC,SAAS,8BAA8B;AACvC,SAAS,mBAAmB;AAGb,SAAR,2BAA4C,EAAE,OAAO,GAAiC;AAC3F,QAAM,SAAS,UAAU;AACzB,QAAM,WAAW,YAAY;AAC7B,QAAM,IAAI,KAAK;AACf,QAAM,WAAW,YAAY;AAC7B,QAAM,eAAe,QAAQ;AAE7B,QAAM,EAAE,MAAM,YAAY,WAAW,MAAM,IAAI,SAAS;AAAA,IACtD,UAAU,CAAC,uBAAuB,YAAY;AAAA,IAC9C,SAAS,YAAY;AACnB,YAAM,WAAW,MAAM,SAAS,8BAA8B,YAAY,EAAE;AAC5E,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,MAAM,IAAI,MAAM,EAAE,8BAA8B,CAAC;AACvD,YAAI,SAAS,SAAS;AACtB,cAAM;AAAA,MACR;AACA,YAAM,SAAS,MAAM,SAAS,KAAK;AACnC,aAAO,OAAO;AAAA,IAChB;AAAA,IACA,SAAS,CAAC,CAAC;AAAA,IACX,OAAO,CAAC,cAAc,QAAQ;AAC5B,UAAK,IAA4B,WAAW,IAAK,QAAO;AACxD,aAAO,eAAe;AAAA,IACxB;AAAA,EACF,CAAC;AAED,QAAM,aAAc,OAAsC,WAAW;AAErE,QAAM,gBAAgB,MAAM,QAAQ,MAAM;AACxC,QAAI,YAAY;AACd,aAAO,0BAA0B,UAAU;AAAA,IAC7C;AACA,WAAO;AAAA,EACT,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAsC,CAAC,CAAC;AAE9E,QAAM,UAAU,MAAM;AACpB,gBAAY,eAAe,YAAY,CAAC,CAAC;AAAA,EAC3C,GAAG,CAAC,aAAa,CAAC;AAElB,QAAM,SAAS,YAAY;AAC3B,QAAM,aAAa,WAAW;AAC9B,QAAM,iBAAiB,WAAW;AAElC,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAE3D,QAAM,oBAAoB,MAAM;AAAA,IAC9B,MAAM,gCAAgC,gBAAgB,SAAS;AAAA,IAC/D,CAAC,YAAY;AAAA,EACf;AACA,QAAM,EAAE,aAAa,kBAAkB,IAAI,mBAA4C;AAAA,IACrF,WAAW;AAAA,EACb,CAAC;AAED,QAAM,eAAe,OAAO,WAAyC;AACnE,UAAM,UAAU,qBAAqB,EAAE,GAAG,QAAQ,SAAS,CAAC;AAC5D,UAAM,uBAAuB;AAAA,MAC3B,OAAO,YAAY,cAAc,WAAW,WAAW,YAAY;AAAA,IACrE;AAEA,UAAM,YAAY;AAAA,MAChB,WAAW,YAAY;AACrB,cAAM,mBAAmB,MAAM,SAAS,8BAA8B,YAAY,IAAI;AAAA,UACpF,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,OAAO;AAAA,QAC9B,CAAC;AACD,cAAM,WAAW,OAAO,KAAK,oBAAoB,EAAE,SAAS,IACxD,MAAM,qBAAqB,sBAAsB,gBAAgB,IACjE,MAAM,iBAAiB;AAC3B,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,YAAY,MAAM,aAMrB,UAAU,IAAI;AACjB,gBAAM,OAAO;AAAA,YACX,IAAI,MAAM,8BAA8B,WAAW,EAAE,+BAA+B,CAAC,CAAC;AAAA,YACtF,EAAE,QAAQ,SAAS,QAAQ,GAAI,aAAa,CAAC,EAAG;AAAA,UAClD;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,MACA,iBAAiB,EAAE,YAAY,cAAc,WAAW,SAAS;AAAA,MACjE,SAAS;AAAA,QACP,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,YAAY;AAAA,QACZ,WAAW;AAAA,QACX;AAAA,MACF;AAAA,IACF,CAAC;AAED,WAAO,KAAK,sBAAsB;AAClC,WAAO,QAAQ;AAAA,EACjB;AAEA,QAAM,kBAAkB,YAAY;AAClC,QAAI;AACF,YAAM,SAAS,MAAM,YAAY;AAAA,QAC/B,WAAW,YAAY;AACrB,gBAAM,WAAW,MAAM,SAAS,8BAA8B,YAAY,cAAc;AAAA,YACtF,QAAQ;AAAA,UACV,CAAC;AACD,cAAI,CAAC,SAAS,IAAI;AAChB,kBAAM,YAAY,MAAM,aAAiC,UAAU,IAAI;AACvE,kBAAM,IAAI,MAAM,WAAW,SAAS,EAAE,+BAA+B,CAAC;AAAA,UACxE;AACA,iBAAO,aAAyC,UAAU,IAAI;AAAA,QAChE;AAAA,QACA,iBAAiB,EAAE,YAAY,cAAc,WAAW,YAAY;AAAA,QACpE,SAAS;AAAA,UACP,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,YAAY;AAAA,UACZ,WAAW;AAAA,UACX;AAAA,QACF;AAAA,MACF,CAAC;AACD,UAAI,QAAQ,MAAM,IAAI;AACpB,eAAO,KAAK,wBAAwB,OAAO,KAAK,EAAE,EAAE;AACpD,eAAO,QAAQ;AAAA,MACjB;AAAA,IACF,SAASA,QAAO;AACd,YAAM,UAAUA,kBAAiB,QAAQA,OAAM,UAAU,EAAE,+BAA+B;AAC1F,YAAM,SAAS,OAAO;AAAA,IACxB;AAAA,EACF;AAEA,QAAM,oBAAoB,YAAY;AACpC,UAAM,YAAY,MAAM,QAAQ;AAAA,MAC9B,OAAO,EAAE,+BAA+B;AAAA,MACxC,aAAa,EAAE,gCAAgC;AAAA,MAC/C,aAAa,EAAE,+BAA+B;AAAA,MAC9C,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,UAAW;AAEhB,QAAI;AACF,YAAM,SAAS,MAAM,YAAY;AAAA,QAC/B,WAAW,YAAY;AACrB,gBAAM,WAAW,MAAM,SAAS,8BAA8B,YAAY,kBAAkB;AAAA,YAC1F,QAAQ;AAAA,UACV,CAAC;AACD,cAAI,CAAC,SAAS,IAAI;AAChB,kBAAM,YAAY,MAAM,aAAiC,UAAU,IAAI;AACvE,kBAAM,IAAI,MAAM,WAAW,SAAS,EAAE,iCAAiC,CAAC;AAAA,UAC1E;AACA,iBAAO,aAAyC,UAAU,IAAI;AAAA,QAChE;AAAA,QACA,iBAAiB,EAAE,YAAY,cAAc,WAAW,gBAAgB;AAAA,QACxE,SAAS;AAAA,UACP,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,YAAY;AAAA,UACZ,WAAW;AAAA,UACX;AAAA,QACF;AAAA,MACF,CAAC;AACD,YAAM,EAAE,4BAA4B,GAAG,SAAS;AAChD,YAAM,SAAS,QAAQ,MAAM,MAAM,QAAQ,YAAY,UAAU;AACjE,aAAO,KAAK,wBAAwB,MAAM,EAAE;AAC5C,aAAO,QAAQ;AAAA,IACjB,QAAQ;AACN,YAAM,EAAE,iCAAiC,GAAG,OAAO;AAAA,IACrD;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,QAAQ,MAAM,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC;AAEjE,QAAM,aAAa,MAAM;AAAA,IACvB,MAAM,WAAW,CAAC,IAAI,iBAAiB,GAAG,aAAa,iBAAiB;AAAA,IACxE,CAAC,GAAG,QAAQ;AAAA,EACd;AAEA,QAAM,yBAAyB,MAAM,YAAY,MAAM;AACrD,WAAO,KAAK,yCAAyC,YAAY,EAAE;AAAA,EACrE,GAAG,CAAC,QAAQ,YAAY,CAAC;AAOzB;AAAA,IACE,4BAA4B;AAAA,MAC1B,cAAc;AAAA,MACd,YAAY,gBAAgB;AAAA,MAC5B,WAAW,OAAO,YAAY,cAAc,WAAW,WAAW,YAAY;AAAA,MAC9E,MAAO,cAAc;AAAA,MACrB,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAEA,MAAI,WAAW;AACb,WACE,oBAAC,QACC,8BAAC,YACC,+BAAC,SAAI,WAAU,kFACb;AAAA,0BAAC,WAAQ,WAAU,WAAU;AAAA,MAC7B,oBAAC,UAAM,YAAE,wBAAwB,GAAE;AAAA,OACrC,GACF,GACF;AAAA,EAEJ;AAEA,MAAI,YAAY;AACd,WACE,oBAAC,QACC,8BAAC,YACC;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,6BAA6B,EAAE,6BAA6B,CAAC;AAAA,QACtE,UAAS;AAAA,QACT,WAAW,EAAE,sBAAsB;AAAA;AAAA,IACrC,GACF,GACF;AAAA,EAEJ;AAEA,MAAI,SAAS,CAAC,YAAY;AACxB,WACE,oBAAC,QACC,+BAAC,YACC;AAAA,0BAAC,gBAAa,OAAO,EAAE,6BAA6B,GAAG;AAAA,MACvD,oBAAC,SAAI,WAAU,4BACb,8BAAC,UAAO,SAAO,MAAC,SAAQ,WACtB,8BAAC,OAAE,MAAK,wBAAwB,YAAE,sBAAsB,GAAE,GAC5D,GACF;AAAA,OACF,GACF;AAAA,EAEJ;AAEA,MAAI,CAAC,eAAe;AAClB,WAAO;AAAA,EACT;AAEA,SACE,qBAAC,QACC;AAAA,yBAAC,YACE;AAAA,oBACC,oBAAC,SAAM,QAAO,eAAc,WAAU,QACpC,+BAAC,oBAAiB,WAAU,qCAC1B;AAAA,4BAAC,UAAM,YAAE,sCAAsC,GAAE;AAAA,QACjD,oBAAC,UAAO,SAAQ,WAAU,MAAK,MAAK,SAAS,iBAC1C,YAAE,6BAA6B,GAClC;AAAA,SACF,GACF;AAAA,MAED,kBACC,oBAAC,SAAM,QAAO,WAAU,WAAU,QAChC,+BAAC,oBAAiB,WAAU,qCAC1B;AAAA,4BAAC,UAAM,YAAE,uCAAuC,GAAE;AAAA,QAClD,oBAAC,UAAO,SAAQ,WAAU,MAAK,MAAK,SAAS,mBAC1C,YAAE,+BAA+B,GACpC;AAAA,SACF,GACF;AAAA,MAEF,qBAAC,SAAI,WAAU,6IACb;AAAA,6BAAC,SAAI,WAAU,0BACb;AAAA,8BAAC,SAAI,WAAU,iCAAgC,MAAK,QAAO,QAAO,gBAAe,SAAQ,aACvF,8BAAC,UAAK,eAAc,SAAQ,gBAAe,SAAQ,aAAa,GAAG,GAAE,8BAA6B,GACpG;AAAA,UACA,qBAAC,SACC;AAAA,gCAAC,OAAE,WAAU,6CACV,YAAE,sCAAsC,GAC3C;AAAA,YACA,oBAAC,OAAE,WAAU,wCACV,YAAE,wCAAwC,GAC7C;AAAA,aACF;AAAA,WACF;AAAA,QACA,oBAAC,UAAO,SAAO,MAAC,SAAQ,WAAU,MAAK,MAAK,WAAU,4FACpD,8BAAC,OAAE,MAAM,yCAAyC,YAAY,IAC3D,YAAE,oCAAoC,GACzC,GACF;AAAA,SACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UAEC,OAAO,aAAa,YAAY,gBAAgB,EAAE,sBAAsB,IAAI,EAAE,sBAAsB;AAAA,UACpG,UAAS;AAAA,UACT,QAAQ;AAAA,UACR;AAAA,UACA;AAAA,UACA,UAAU;AAAA,UACV,YAAW;AAAA,UACX,QAAQ;AAAA,UACR,aAAa,aAAa,EAAE,6BAA6B,IAAI,EAAE,uBAAuB;AAAA,UACrF,GAAI,aAAa,EAAE,UAAU,KAAK,IAAI,CAAC;AAAA;AAAA,QAVnC;AAAA,MAWP;AAAA,MAGC,YAAY,iBACX,oBAAC,SAAI,WAAU,QACb;AAAA,QAAC;AAAA;AAAA,UACC,QAAQ;AAAA,UACR,YAAY;AAAA,UACZ,cAAc;AAAA,UACd,WAAW;AAAA,UACX,kBAAkB;AAAA,UAClB,oBAAoB;AAAA,UACpB,iBAAiB;AAAA;AAAA,MACnB,GACF;AAAA,MAIF,oBAAC,SAAI,WAAU,QACb;AAAA,QAAC;AAAA;AAAA,UACC,OAAO;AAAA,UACP,UAAU;AAAA;AAAA,MACZ,GACF;AAAA,OACF;AAAA,IACC;AAAA,KACH;AAEJ;",
|
|
6
6
|
"names": ["error"]
|
|
7
7
|
}
|
|
@@ -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 {
|
|
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";
|
|
@@ -11,16 +11,10 @@ import { FormHeader } from "@open-mercato/ui/backend/forms";
|
|
|
11
11
|
import { Spinner } from "@open-mercato/ui/primitives/spinner";
|
|
12
12
|
import { JsonDisplay } from "@open-mercato/ui/backend/JsonDisplay";
|
|
13
13
|
import { RecordNotFoundState, ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
14
|
-
function WorkflowEventDetailPage() {
|
|
14
|
+
function WorkflowEventDetailPage({ params }) {
|
|
15
15
|
const router = useRouter();
|
|
16
|
-
const params = useParams();
|
|
17
16
|
const t = useT();
|
|
18
|
-
|
|
19
|
-
if (params?.id) {
|
|
20
|
-
eventId = Array.isArray(params.id) ? params.id[0] : params.id;
|
|
21
|
-
} else if (params?.slug && Array.isArray(params.slug)) {
|
|
22
|
-
eventId = params.slug[params.slug.length - 1];
|
|
23
|
-
}
|
|
17
|
+
const eventId = params?.id;
|
|
24
18
|
const { data: event, isLoading, error } = useQuery({
|
|
25
19
|
queryKey: ["workflows", "events", eventId],
|
|
26
20
|
queryFn: async () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/workflows/backend/events/%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 { FormHeader } from '@open-mercato/ui/backend/forms'\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 WorkflowEvent = {\n id: string\n workflowInstanceId: string\n stepInstanceId: string | null\n eventType: string\n eventData: any\n occurredAt: string\n userId: string | null\n tenantId: string\n organizationId: string\n workflowInstance: {\n id: string\n workflowId: string\n version: number\n status: string\n currentStepId: string\n correlationKey: string | null\n startedAt: string | null\n completedAt: string | null\n context: any\n } | null\n}\n\nexport default function WorkflowEventDetailPage() {\n const router = useRouter()\n const params = useParams()\n const t = useT()\n\n // Handle both {id: '17'} and {slug: ['events', '17']} formats\n let eventId: string | undefined\n if (params?.id) {\n eventId = Array.isArray(params.id) ? params.id[0] : params.id\n } else if (params?.slug && Array.isArray(params.slug)) {\n // If slug is ['events', '17'], extract '17'\n eventId = params.slug[params.slug.length - 1]\n }\n\n const { data: event, isLoading, error } = useQuery({\n queryKey: ['workflows', 'events', eventId],\n queryFn: async () => {\n const response = await apiFetch(`/api/workflows/events/${eventId}`)\n\n if (!response.ok) {\n const httpErr = new Error(\n response.status === 404\n ? t('workflows.events.notFound', 'Event not found.')\n : t('workflows.events.messages.loadFailed')\n ) as Error & { status: number }\n httpErr.status = response.status\n throw httpErr\n }\n const result = await response.json()\n return result as WorkflowEvent\n },\n enabled: !!eventId,\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('workflows.common.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('workflows.events.notFound', 'Event not found.')}\n backHref=\"/backend/events\"\n backLabel={t('workflows.events.backToList', 'Back to Events')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !event) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={(error as Error | null)?.message ?? t('workflows.events.messages.loadFailed')}\n action={\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/events\">{t('workflows.events.backToList', 'Back to Events')}</Link>\n </Button>\n }\n />\n </PageBody>\n </Page>\n )\n }\n\n const getEventTypeBadgeClass = (eventType: string) => {\n if (eventType.includes('STARTED')) return 'bg-blue-100 text-blue-800'\n if (eventType.includes('COMPLETED')) return 'bg-green-100 text-green-800'\n if (eventType.includes('FAILED') || eventType.includes('REJECTED')) return 'bg-red-100 text-red-800'\n if (eventType.includes('CANCELLED')) return 'bg-muted text-foreground'\n if (eventType.includes('ENTERED') || eventType.includes('EXITED')) return 'bg-purple-100 text-purple-800'\n return 'bg-muted text-foreground'\n }\n\n const getStatusBadgeClass = (status: string) => {\n switch (status) {\n case 'COMPLETED':\n return 'bg-green-100 text-green-800'\n case 'RUNNING':\n return 'bg-blue-100 text-blue-800'\n case 'FAILED':\n return 'bg-red-100 text-red-800'\n case 'CANCELLED':\n return 'bg-muted text-foreground'\n default:\n return 'bg-muted text-foreground'\n }\n }\n\n return (\n <Page>\n <PageBody>\n <div className=\"space-y-6\">\n <FormHeader\n mode=\"detail\"\n backHref=\"/backend/events\"\n backLabel={t('workflows.events.backToList', 'Back to events')}\n entityTypeLabel={t('workflows.events.detail.type', 'Workflow event')}\n title={\n <div className=\"flex flex-wrap items-center gap-2\">\n <span>{event.eventType}</span>\n <span className=\"font-mono text-sm text-muted-foreground\">#{event.id.slice(0, 8)}</span>\n </div>\n }\n statusBadge={\n <span\n className={`inline-flex items-center px-3 py-1 rounded text-sm font-medium ${getEventTypeBadgeClass(event.eventType)}`}\n >\n {event.eventType}\n </span>\n }\n />\n\n {/* Event Summary */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('workflows.events.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('workflows.events.fields.occurredAt')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {new Date(event.occurredAt).toLocaleString()}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.events.fields.eventType')}\n </dt>\n <dd className=\"mt-1\">\n <span\n className={`inline-flex items-center px-2 py-1 rounded text-xs font-medium ${getEventTypeBadgeClass(\n event.eventType\n )}`}\n >\n {event.eventType}\n </span>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.events.fields.userId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">\n {event.userId || t('common.none')}\n </dd>\n </div>\n {event.stepInstanceId && (\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.events.detail.stepInstanceId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">\n {event.stepInstanceId}\n </dd>\n </div>\n )}\n </dl>\n </div>\n\n {/* Workflow Instance Information */}\n {event.workflowInstance && (\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('workflows.events.detail.workflowInstance')}\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('workflows.instances.fields.instanceId')}\n </dt>\n <dd className=\"mt-1\">\n <Link\n href={`/backend/instances/${event.workflowInstance.id}`}\n className=\"text-sm text-primary hover:underline font-mono\"\n >\n {event.workflowInstance.id}\n </Link>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.instances.fields.status')}\n </dt>\n <dd className=\"mt-1\">\n <span\n className={`inline-flex items-center px-2 py-1 rounded text-xs font-medium ${getStatusBadgeClass(\n event.workflowInstance.status\n )}`}\n >\n {event.workflowInstance.status}\n </span>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.instances.fields.workflowId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">\n {event.workflowInstance.workflowId}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.instances.fields.version')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {event.workflowInstance.version}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.instances.fields.currentStep')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">\n {event.workflowInstance.currentStepId}\n </dd>\n </div>\n {event.workflowInstance.correlationKey && (\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.instances.fields.correlationKey')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">\n {event.workflowInstance.correlationKey}\n </dd>\n </div>\n )}\n {event.workflowInstance.startedAt && (\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.instances.fields.startedAt')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {new Date(event.workflowInstance.startedAt).toLocaleString()}\n </dd>\n </div>\n )}\n {event.workflowInstance.completedAt && (\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.instances.fields.completedAt')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {new Date(event.workflowInstance.completedAt).toLocaleString()}\n </dd>\n </div>\n )}\n </dl>\n </div>\n )}\n\n {/* Event Data */}\n <JsonDisplay\n data={event.eventData}\n title={t('workflows.events.detail.eventData')}\n />\n\n {/* Workflow Context (if available) */}\n {event.workflowInstance?.context && (\n <JsonDisplay\n data={event.workflowInstance.context}\n title={t('workflows.events.detail.workflowContext')}\n />\n )}\n\n {/* Technical Details */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('workflows.events.detail.technicalDetails')}\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('workflows.events.detail.tenantId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">\n {event.tenantId}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.events.detail.organizationId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">\n {event.organizationId}\n </dd>\n </div>\n </dl>\n </div>\n </div>\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
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 { FormHeader } from '@open-mercato/ui/backend/forms'\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 WorkflowEvent = {\n id: string\n workflowInstanceId: string\n stepInstanceId: string | null\n eventType: string\n eventData: any\n occurredAt: string\n userId: string | null\n tenantId: string\n organizationId: string\n workflowInstance: {\n id: string\n workflowId: string\n version: number\n status: string\n currentStepId: string\n correlationKey: string | null\n startedAt: string | null\n completedAt: string | null\n context: any\n } | null\n}\n\nexport default function WorkflowEventDetailPage({ params }: { params?: { id?: string } }) {\n const router = useRouter()\n const t = useT()\n const eventId = params?.id\n\n const { data: event, isLoading, error } = useQuery({\n queryKey: ['workflows', 'events', eventId],\n queryFn: async () => {\n const response = await apiFetch(`/api/workflows/events/${eventId}`)\n\n if (!response.ok) {\n const httpErr = new Error(\n response.status === 404\n ? t('workflows.events.notFound', 'Event not found.')\n : t('workflows.events.messages.loadFailed')\n ) as Error & { status: number }\n httpErr.status = response.status\n throw httpErr\n }\n const result = await response.json()\n return result as WorkflowEvent\n },\n enabled: !!eventId,\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('workflows.common.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('workflows.events.notFound', 'Event not found.')}\n backHref=\"/backend/events\"\n backLabel={t('workflows.events.backToList', 'Back to Events')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !event) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={(error as Error | null)?.message ?? t('workflows.events.messages.loadFailed')}\n action={\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/events\">{t('workflows.events.backToList', 'Back to Events')}</Link>\n </Button>\n }\n />\n </PageBody>\n </Page>\n )\n }\n\n const getEventTypeBadgeClass = (eventType: string) => {\n if (eventType.includes('STARTED')) return 'bg-blue-100 text-blue-800'\n if (eventType.includes('COMPLETED')) return 'bg-green-100 text-green-800'\n if (eventType.includes('FAILED') || eventType.includes('REJECTED')) return 'bg-red-100 text-red-800'\n if (eventType.includes('CANCELLED')) return 'bg-muted text-foreground'\n if (eventType.includes('ENTERED') || eventType.includes('EXITED')) return 'bg-purple-100 text-purple-800'\n return 'bg-muted text-foreground'\n }\n\n const getStatusBadgeClass = (status: string) => {\n switch (status) {\n case 'COMPLETED':\n return 'bg-green-100 text-green-800'\n case 'RUNNING':\n return 'bg-blue-100 text-blue-800'\n case 'FAILED':\n return 'bg-red-100 text-red-800'\n case 'CANCELLED':\n return 'bg-muted text-foreground'\n default:\n return 'bg-muted text-foreground'\n }\n }\n\n return (\n <Page>\n <PageBody>\n <div className=\"space-y-6\">\n <FormHeader\n mode=\"detail\"\n backHref=\"/backend/events\"\n backLabel={t('workflows.events.backToList', 'Back to events')}\n entityTypeLabel={t('workflows.events.detail.type', 'Workflow event')}\n title={\n <div className=\"flex flex-wrap items-center gap-2\">\n <span>{event.eventType}</span>\n <span className=\"font-mono text-sm text-muted-foreground\">#{event.id.slice(0, 8)}</span>\n </div>\n }\n statusBadge={\n <span\n className={`inline-flex items-center px-3 py-1 rounded text-sm font-medium ${getEventTypeBadgeClass(event.eventType)}`}\n >\n {event.eventType}\n </span>\n }\n />\n\n {/* Event Summary */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('workflows.events.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('workflows.events.fields.occurredAt')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {new Date(event.occurredAt).toLocaleString()}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.events.fields.eventType')}\n </dt>\n <dd className=\"mt-1\">\n <span\n className={`inline-flex items-center px-2 py-1 rounded text-xs font-medium ${getEventTypeBadgeClass(\n event.eventType\n )}`}\n >\n {event.eventType}\n </span>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.events.fields.userId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">\n {event.userId || t('common.none')}\n </dd>\n </div>\n {event.stepInstanceId && (\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.events.detail.stepInstanceId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">\n {event.stepInstanceId}\n </dd>\n </div>\n )}\n </dl>\n </div>\n\n {/* Workflow Instance Information */}\n {event.workflowInstance && (\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('workflows.events.detail.workflowInstance')}\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('workflows.instances.fields.instanceId')}\n </dt>\n <dd className=\"mt-1\">\n <Link\n href={`/backend/instances/${event.workflowInstance.id}`}\n className=\"text-sm text-primary hover:underline font-mono\"\n >\n {event.workflowInstance.id}\n </Link>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.instances.fields.status')}\n </dt>\n <dd className=\"mt-1\">\n <span\n className={`inline-flex items-center px-2 py-1 rounded text-xs font-medium ${getStatusBadgeClass(\n event.workflowInstance.status\n )}`}\n >\n {event.workflowInstance.status}\n </span>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.instances.fields.workflowId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">\n {event.workflowInstance.workflowId}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.instances.fields.version')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {event.workflowInstance.version}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.instances.fields.currentStep')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">\n {event.workflowInstance.currentStepId}\n </dd>\n </div>\n {event.workflowInstance.correlationKey && (\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.instances.fields.correlationKey')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">\n {event.workflowInstance.correlationKey}\n </dd>\n </div>\n )}\n {event.workflowInstance.startedAt && (\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.instances.fields.startedAt')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {new Date(event.workflowInstance.startedAt).toLocaleString()}\n </dd>\n </div>\n )}\n {event.workflowInstance.completedAt && (\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.instances.fields.completedAt')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {new Date(event.workflowInstance.completedAt).toLocaleString()}\n </dd>\n </div>\n )}\n </dl>\n </div>\n )}\n\n {/* Event Data */}\n <JsonDisplay\n data={event.eventData}\n title={t('workflows.events.detail.eventData')}\n />\n\n {/* Workflow Context (if available) */}\n {event.workflowInstance?.context && (\n <JsonDisplay\n data={event.workflowInstance.context}\n title={t('workflows.events.detail.workflowContext')}\n />\n )}\n\n {/* Technical Details */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('workflows.events.detail.technicalDetails')}\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('workflows.events.detail.tenantId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">\n {event.tenantId}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('workflows.events.detail.organizationId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">\n {event.organizationId}\n </dd>\n </div>\n </dl>\n </div>\n </div>\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.7.1-develop.
|
|
3
|
+
"version": "0.7.1-develop.7112.1.d486e6f1b2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -252,16 +252,16 @@
|
|
|
252
252
|
"zod": "^4.4.3"
|
|
253
253
|
},
|
|
254
254
|
"peerDependencies": {
|
|
255
|
-
"@open-mercato/ai-assistant": "0.7.1-develop.
|
|
256
|
-
"@open-mercato/shared": "0.7.1-develop.
|
|
257
|
-
"@open-mercato/ui": "0.7.1-develop.
|
|
255
|
+
"@open-mercato/ai-assistant": "0.7.1-develop.7112.1.d486e6f1b2",
|
|
256
|
+
"@open-mercato/shared": "0.7.1-develop.7112.1.d486e6f1b2",
|
|
257
|
+
"@open-mercato/ui": "0.7.1-develop.7112.1.d486e6f1b2",
|
|
258
258
|
"react": "^19.0.0",
|
|
259
259
|
"react-dom": "^19.0.0"
|
|
260
260
|
},
|
|
261
261
|
"devDependencies": {
|
|
262
|
-
"@open-mercato/ai-assistant": "0.7.1-develop.
|
|
263
|
-
"@open-mercato/shared": "0.7.1-develop.
|
|
264
|
-
"@open-mercato/ui": "0.7.1-develop.
|
|
262
|
+
"@open-mercato/ai-assistant": "0.7.1-develop.7112.1.d486e6f1b2",
|
|
263
|
+
"@open-mercato/shared": "0.7.1-develop.7112.1.d486e6f1b2",
|
|
264
|
+
"@open-mercato/ui": "0.7.1-develop.7112.1.d486e6f1b2",
|
|
265
265
|
"@testing-library/dom": "^10.4.1",
|
|
266
266
|
"@testing-library/jest-dom": "^7.0.0",
|
|
267
267
|
"@testing-library/react": "^16.3.1",
|
|
@@ -139,7 +139,7 @@ export async function POST(req: Request) {
|
|
|
139
139
|
|
|
140
140
|
let base: string
|
|
141
141
|
try {
|
|
142
|
-
base = getSecurityEmailBaseUrl(req
|
|
142
|
+
base = getSecurityEmailBaseUrl(req)
|
|
143
143
|
} catch (error) {
|
|
144
144
|
const mapped = mapSecurityEmailUrlError(error, {
|
|
145
145
|
scope: 'auth.users.resend-invite',
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import Link from 'next/link'
|
|
5
|
-
import {
|
|
5
|
+
import { useRouter } from 'next/navigation'
|
|
6
6
|
import { useQuery } from '@tanstack/react-query'
|
|
7
7
|
import { apiFetch } from '@open-mercato/ui/backend/utils/api'
|
|
8
8
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
@@ -36,17 +36,9 @@ type RuleExecutionLog = {
|
|
|
36
36
|
organizationId: string | null
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export default function ExecutionLogDetailPage() {
|
|
39
|
+
export default function ExecutionLogDetailPage({ params }: { params?: { id?: string } }) {
|
|
40
40
|
const router = useRouter()
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
// Handle catch-all route: params.slug = ['logs', 'id']
|
|
44
|
-
let logId: string | undefined
|
|
45
|
-
if (params?.slug && Array.isArray(params.slug)) {
|
|
46
|
-
logId = params.slug[1] // Second element is the ID
|
|
47
|
-
} else if (params?.id) {
|
|
48
|
-
logId = Array.isArray(params.id) ? params.id[0] : params.id
|
|
49
|
-
}
|
|
41
|
+
const logId = params?.id
|
|
50
42
|
|
|
51
43
|
const t = useT()
|
|
52
44
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import Link from 'next/link'
|
|
5
|
-
import { useRouter,
|
|
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'
|
|
@@ -25,18 +25,10 @@ import { ConditionBuilder } from '../../../components/ConditionBuilder'
|
|
|
25
25
|
import { ActionBuilder } from '../../../components/ActionBuilder'
|
|
26
26
|
import { buildRulePayload, parseRuleToFormValues } from '../../../components/utils/formHelpers'
|
|
27
27
|
|
|
28
|
-
export default function EditBusinessRulePage() {
|
|
28
|
+
export default function EditBusinessRulePage({ params }: { params?: { id?: string } }) {
|
|
29
29
|
const router = useRouter()
|
|
30
|
-
const params = useParams()
|
|
31
30
|
const pathname = usePathname()
|
|
32
|
-
|
|
33
|
-
// Handle catch-all route: params.slug = ['rules', 'uuid']
|
|
34
|
-
let ruleId: string | undefined
|
|
35
|
-
if (params?.slug && Array.isArray(params.slug)) {
|
|
36
|
-
ruleId = params.slug[1] // Second element is the ID
|
|
37
|
-
} else if (params?.id) {
|
|
38
|
-
ruleId = Array.isArray(params.id) ? params.id[0] : params.id
|
|
39
|
-
}
|
|
31
|
+
const ruleId = params?.id
|
|
40
32
|
|
|
41
33
|
const t = useT()
|
|
42
34
|
const { organizationId, tenantId } = useOrganizationScopeDetail()
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import Link from 'next/link'
|
|
5
|
-
import { useRouter,
|
|
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'
|
|
@@ -53,18 +53,10 @@ type RuleSetDetail = {
|
|
|
53
53
|
}>
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
export default function EditRuleSetPage() {
|
|
56
|
+
export default function EditRuleSetPage({ params }: { params?: { id?: string } }) {
|
|
57
57
|
const router = useRouter()
|
|
58
|
-
const params = useParams()
|
|
59
58
|
const pathname = usePathname()
|
|
60
|
-
|
|
61
|
-
// Handle catch-all route: params.slug = ['sets', 'uuid']
|
|
62
|
-
let setId: string | undefined
|
|
63
|
-
if (params?.slug && Array.isArray(params.slug)) {
|
|
64
|
-
setId = params.slug[1] // Second element is the ID
|
|
65
|
-
} else if (params?.id) {
|
|
66
|
-
setId = Array.isArray(params.id) ? params.id[0] : params.id
|
|
67
|
-
}
|
|
59
|
+
const setId = params?.id
|
|
68
60
|
|
|
69
61
|
const t = useT()
|
|
70
62
|
const { organizationId, tenantId } = useOrganizationScopeDetail()
|
package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
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'
|
|
@@ -35,10 +34,9 @@ type ChannelHealth = {
|
|
|
35
34
|
}>
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
export default function ChannelDetailPage() {
|
|
37
|
+
export default function ChannelDetailPage({ params }: { params?: { id?: string } }) {
|
|
39
38
|
const t = useT()
|
|
40
|
-
const
|
|
41
|
-
const id = (params?.id as string) ?? ''
|
|
39
|
+
const id = params?.id ?? ''
|
|
42
40
|
|
|
43
41
|
const [detail, setDetail] = React.useState<ChannelDetail | null>(null)
|
|
44
42
|
const [health, setHealth] = React.useState<ChannelHealth | null>(null)
|
|
@@ -47,7 +45,11 @@ export default function ChannelDetailPage() {
|
|
|
47
45
|
const [notFound, setNotFound] = React.useState(false)
|
|
48
46
|
|
|
49
47
|
React.useEffect(() => {
|
|
50
|
-
if (!id)
|
|
48
|
+
if (!id) {
|
|
49
|
+
setIsLoading(false)
|
|
50
|
+
setErrorMessage(t('communication_channels.errors.missingChannelId', 'No channel selected.'))
|
|
51
|
+
return
|
|
52
|
+
}
|
|
51
53
|
let cancelled = false
|
|
52
54
|
async function load() {
|
|
53
55
|
setIsLoading(true)
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"communication_channels.emptyState": "Noch keine gemeinsamen Kanäle. Diese Seite listet gemeinsame, mandantenweite Kanäle auf. Ihr persönliches E-Mail-Postfach ist privat – verbinden und verwalten Sie es unter Profil → Kommunikationskanäle.",
|
|
24
24
|
"communication_channels.errors.loadDetail": "Kanal konnte nicht geladen werden",
|
|
25
25
|
"communication_channels.errors.loadList": "Kanäle konnten nicht geladen werden",
|
|
26
|
+
"communication_channels.errors.missingChannelId": "Kein Kanal ausgewählt.",
|
|
26
27
|
"communication_channels.errors.pollNowPushDriven": "Der Kanal ist Push-gesteuert — Abrufen ist nicht anwendbar. Eingehende Nachrichten kommen über die Push-Verbindung des Anbieters.",
|
|
27
28
|
"communication_channels.errors.reactionFailed": "Reaktion fehlgeschlagen",
|
|
28
29
|
"communication_channels.errors.undoBlockedPrimaryConflict": "Ein anderer Kanal ist jetzt für diesen Benutzer als primär festgelegt. Setze ihn auf nicht-primär, bevor du den getrennten Kanal wieder als primär festlegst.",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"communication_channels.emptyState": "No shared channels yet. This page lists shared, tenant-wide channels. Your personal email mailbox is private — connect and manage it under Profile → Communication Channels.",
|
|
24
24
|
"communication_channels.errors.loadDetail": "Failed to load channel",
|
|
25
25
|
"communication_channels.errors.loadList": "Failed to load channels",
|
|
26
|
+
"communication_channels.errors.missingChannelId": "No channel selected.",
|
|
26
27
|
"communication_channels.errors.pollNowPushDriven": "Channel is push-driven — polling does not apply. Inbound messages arrive through the provider push connection.",
|
|
27
28
|
"communication_channels.errors.reactionFailed": "Reaction failed",
|
|
28
29
|
"communication_channels.errors.undoBlockedPrimaryConflict": "Another channel is now primary for this user. Set it as non-primary before restoring the disconnected channel as primary.",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"communication_channels.emptyState": "Aún no hay canales compartidos. Esta página muestra los canales compartidos de toda la organización. Tu buzón de correo personal es privado: conéctalo y gestiónalo en Perfil → Canales de comunicación.",
|
|
24
24
|
"communication_channels.errors.loadDetail": "No se pudo cargar el canal",
|
|
25
25
|
"communication_channels.errors.loadList": "No se pudieron cargar los canales",
|
|
26
|
+
"communication_channels.errors.missingChannelId": "Ningún canal seleccionado.",
|
|
26
27
|
"communication_channels.errors.pollNowPushDriven": "El canal funciona con push — el sondeo no aplica. Los mensajes entrantes llegan por la conexión push del proveedor.",
|
|
27
28
|
"communication_channels.errors.reactionFailed": "La reacción falló",
|
|
28
29
|
"communication_channels.errors.undoBlockedPrimaryConflict": "Otro canal es ahora el principal para este usuario. Configúralo como no principal antes de restaurar el canal desconectado como principal.",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"communication_channels.emptyState": "아직 공유 채널이 없습니다. 이 페이지에는 공유되는 테넌트 전체 채널이 나열됩니다. 개인 이메일 메일함은 비공개이며, 프로필 → 통신 채널에서 연결하고 관리하세요.",
|
|
24
24
|
"communication_channels.errors.loadDetail": "채널을 불러오지 못했습니다",
|
|
25
25
|
"communication_channels.errors.loadList": "채널을 불러오지 못했습니다",
|
|
26
|
+
"communication_channels.errors.missingChannelId": "선택된 채널이 없습니다.",
|
|
26
27
|
"communication_channels.errors.pollNowPushDriven": "이 채널은 푸시 기반입니다 — 폴링은 적용되지 않습니다. 수신 메시지는 공급자의 푸시 연결로 전달됩니다.",
|
|
27
28
|
"communication_channels.errors.reactionFailed": "반응 처리에 실패했습니다",
|
|
28
29
|
"communication_channels.errors.undoBlockedPrimaryConflict": "다른 채널이 현재 이 사용자의 기본 채널로 설정되어 있습니다. 연결이 끊어진 채널을 기본으로 복원하기 전에 해당 채널을 기본이 아닌 채널로 설정하세요.",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"communication_channels.emptyState": "Brak kanałów współdzielonych. Ta strona pokazuje współdzielone kanały obejmujące całą organizację. Twoja osobista skrzynka e-mail jest prywatna — połącz ją i zarządzaj nią w Profil → Kanały komunikacji.",
|
|
24
24
|
"communication_channels.errors.loadDetail": "Nie udało się załadować kanału",
|
|
25
25
|
"communication_channels.errors.loadList": "Nie udało się załadować kanałów",
|
|
26
|
+
"communication_channels.errors.missingChannelId": "Nie wybrano kanału.",
|
|
26
27
|
"communication_channels.errors.pollNowPushDriven": "Kanał działa w trybie push — odpytywanie go nie dotyczy. Wiadomości przychodzące docierają połączeniem push dostawcy.",
|
|
27
28
|
"communication_channels.errors.reactionFailed": "Reakcja nie powiodła się",
|
|
28
29
|
"communication_channels.errors.undoBlockedPrimaryConflict": "Inny kanał jest teraz głównym dla tego użytkownika. Ustaw go jako niegłówny, zanim przywrócisz rozłączony kanał jako główny.",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import { useRouter,
|
|
4
|
+
import { useRouter, usePathname } from 'next/navigation'
|
|
5
5
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
6
6
|
import { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'
|
|
7
7
|
import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
|
|
@@ -10,6 +10,7 @@ import { Button } from '@open-mercato/ui/primitives/button'
|
|
|
10
10
|
import { Kbd } from '@open-mercato/ui/primitives/kbd'
|
|
11
11
|
import { ActivityTimelineFilters } from './ActivityTimelineFilters'
|
|
12
12
|
import { ActivityTimeline } from './ActivityTimeline'
|
|
13
|
+
import { resolveAuthorUserNames } from './authorUserLookup'
|
|
13
14
|
import type { ActivitySummary, InteractionSummary } from './types'
|
|
14
15
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
15
16
|
|
|
@@ -314,25 +315,14 @@ export function ActivitiesSection({
|
|
|
314
315
|
}
|
|
315
316
|
if (unresolvedIds.size === 0) return
|
|
316
317
|
|
|
317
|
-
for (const uid of unresolvedIds) resolvedUserIdsRef.current.add(uid)
|
|
318
|
-
|
|
319
318
|
const controller = new AbortController()
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
for (const user of users) {
|
|
328
|
-
const userId = typeof user.id === 'string' ? user.id : null
|
|
329
|
-
const name = typeof user.display_name === 'string' && user.display_name.trim()
|
|
330
|
-
? user.display_name.trim()
|
|
331
|
-
: typeof user.email === 'string'
|
|
332
|
-
? user.email
|
|
333
|
-
: null
|
|
334
|
-
if (userId && name) nameMap.set(userId, name)
|
|
335
|
-
}
|
|
319
|
+
resolveAuthorUserNames([...unresolvedIds], controller.signal)
|
|
320
|
+
.then(({ names: nameMap, resolvedIds }) => {
|
|
321
|
+
if (controller.signal.aborted) return
|
|
322
|
+
// Only ids the server actually answered for are remembered. Marking an id whose request
|
|
323
|
+
// failed — or one the server silently dropped past its `?ids=` cap — would leave that
|
|
324
|
+
// author blank for the life of the component, even though the next attempt would work.
|
|
325
|
+
for (const uid of resolvedIds) resolvedUserIdsRef.current.add(uid)
|
|
336
326
|
if (nameMap.size > 0) {
|
|
337
327
|
setActivities((prev) =>
|
|
338
328
|
prev.map((a) => {
|