@open-mercato/core 0.6.7-develop.6652.1.3ab996caba → 0.6.7-develop.6660.1.90e1e2eef6
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/business_rules/lib/value-resolver.js +7 -0
- package/dist/modules/business_rules/lib/value-resolver.js.map +2 -2
- package/dist/modules/currencies/commands/currencies.js +22 -8
- package/dist/modules/currencies/commands/currencies.js.map +2 -2
- package/dist/modules/currencies/commands/exchange-rates.js +22 -8
- package/dist/modules/currencies/commands/exchange-rates.js.map +2 -2
- package/dist/modules/currencies/commands/scope.js +22 -0
- package/dist/modules/currencies/commands/scope.js.map +7 -0
- package/dist/modules/customers/components/detail/ActivitiesSection.js +5 -4
- package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityHistorySection.js +5 -4
- package/dist/modules/customers/components/detail/ActivityHistorySection.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +6 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js +3 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +6 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js +3 -0
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js +107 -92
- package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
- package/dist/modules/feature_toggles/api/global/route.js +6 -0
- package/dist/modules/feature_toggles/api/global/route.js.map +2 -2
- package/dist/modules/feature_toggles/commands/global.js +9 -9
- package/dist/modules/feature_toggles/commands/global.js.map +2 -2
- package/dist/modules/query_index/lib/subscriber-scope.js +63 -10
- package/dist/modules/query_index/lib/subscriber-scope.js.map +2 -2
- package/dist/modules/query_index/subscribers/delete_one.js +20 -6
- package/dist/modules/query_index/subscribers/delete_one.js.map +2 -2
- package/dist/modules/query_index/subscribers/upsert_one.js +8 -3
- package/dist/modules/query_index/subscribers/upsert_one.js.map +2 -2
- package/dist/modules/workflows/api/definitions/[id]/customize/route.js +2 -0
- package/dist/modules/workflows/api/definitions/[id]/customize/route.js.map +2 -2
- package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js +4 -0
- package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/business_rules/lib/value-resolver.ts +16 -0
- package/src/modules/currencies/commands/currencies.ts +26 -8
- package/src/modules/currencies/commands/exchange-rates.ts +26 -8
- package/src/modules/currencies/commands/scope.ts +32 -0
- package/src/modules/customers/components/detail/ActivitiesSection.tsx +10 -6
- package/src/modules/customers/components/detail/ActivityHistorySection.tsx +6 -3
- package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +6 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/reorder/route.ts +3 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +6 -0
- package/src/modules/dictionaries/api/[dictionaryId]/entries/set-default/route.ts +3 -0
- package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +15 -4
- package/src/modules/feature_toggles/api/global/route.ts +7 -1
- package/src/modules/feature_toggles/commands/global.ts +9 -12
- package/src/modules/query_index/lib/subscriber-scope.ts +98 -14
- package/src/modules/query_index/subscribers/delete_one.ts +21 -10
- package/src/modules/query_index/subscribers/upsert_one.ts +8 -3
- package/src/modules/workflows/AGENTS.md +22 -0
- package/src/modules/workflows/api/definitions/[id]/customize/route.ts +9 -0
- package/src/modules/workflows/api/definitions/[id]/reset-to-code/route.ts +7 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/dictionaries/api/%5BdictionaryId%5D/entries/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext, resolveDictionaryActorId } from '@open-mercato/core/modules/dictionaries/api/context'\nimport { createDictionaryEntrySchema } from '@open-mercato/core/modules/dictionaries/data/validators'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n createDictionaryEntrySchema as createEntryDocSchema,\n dictionaryEntryListResponseSchema,\n dictionaryEntryResponseSchema,\n dictionaryIdParamsSchema,\n dictionariesErrorSchema,\n dictionariesTag,\n} from '../../openapi'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n buildCollectionTags,\n buildRecordTag,\n isCrudCacheEnabled,\n resolveCrudCache,\n} from '@open-mercato/shared/lib/crud/cache'\nimport {\n resolveDictionaryEntrySortMode,\n sortDictionaryEntries,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('dictionaries').child({ component: 'entries-api' })\n\nconst paramsSchema = z.object({ dictionaryId: z.string().uuid() })\n\nconst DICTIONARY_ENTRY_RESOURCE = 'dictionaries.entry'\nconst DICTIONARY_DEFINITION_RESOURCE = 'dictionaries.dictionary'\nconst DICTIONARY_ENTRIES_TTL_MS = 5 * 60_000\n\nfunction buildEntriesCacheKey(params: {\n dictionaryId: string\n organizationId: string | null\n sortMode: string\n}): string {\n return `dictionaries:entries:${params.dictionaryId}:org=${params.organizationId ?? 'null'}:sort=${params.sortMode}`\n}\n\nasync function loadDictionary(\n context: Awaited<ReturnType<typeof resolveDictionariesRouteContext>>,\n id: string,\n options: { allowInherited?: boolean } = {},\n) {\n const { allowInherited = false } = options\n if (!allowInherited && !context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const baseFilter = {\n id,\n tenantId: context.tenantId,\n deletedAt: null,\n }\n const filter = allowInherited\n ? {\n ...baseFilter,\n ...(context.readableOrganizationIds.length\n ? { organizationId: { $in: context.readableOrganizationIds } }\n : {}),\n }\n : {\n ...baseFilter,\n organizationId: context.organizationId,\n }\n const dictionary = await context.em.findOne(Dictionary, filter)\n if (!dictionary) {\n throw new CrudHttpError(404, { error: context.translate('dictionaries.errors.not_found', 'Dictionary not found') })\n }\n return dictionary\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dictionaries.view'] },\n POST: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nexport async function GET(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n if (!context.auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const dictionary = await loadDictionary(context, dictionaryId, { allowInherited: true })\n const dictionaryTenantId = dictionary.tenantId\n const dictionaryOrgId = dictionary.organizationId ?? null\n const sortMode = resolveDictionaryEntrySortMode(dictionary.entrySortMode)\n\n // Dictionary CF selects hit this on every CrudForm open, so cache the\n // decrypted+sorted options payload. The entry writes flow through the\n // command bus (resourceKind dictionaries.entry), which already flushes the\n // matching collection tag post-commit \u2014 no new invalidation wiring needed.\n const cache = isCrudCacheEnabled() ? resolveCrudCache(context.container) : null\n const cacheKey = cache\n ? buildEntriesCacheKey({ dictionaryId, organizationId: dictionaryOrgId, sortMode })\n : null\n\n if (cache && cacheKey) {\n const cached = await runWithCacheTenant(dictionaryTenantId, () => cache.get(cacheKey))\n if (cached) {\n return NextResponse.json(cached)\n }\n }\n\n const entries = await findWithDecryption(\n context.em,\n DictionaryEntry,\n {\n dictionary,\n organizationId: dictionary.organizationId,\n tenantId: dictionary.tenantId,\n },\n {},\n { tenantId: dictionary.tenantId, organizationId: dictionary.organizationId },\n )\n const sortedEntries = sortDictionaryEntries(entries, sortMode)\n\n const payload = {\n items: sortedEntries.map((entry) => ({\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color,\n icon: entry.icon,\n position: entry.position ?? 0,\n isDefault: entry.isDefault ?? false,\n createdAt: entry.createdAt,\n updatedAt: entry.updatedAt,\n })),\n }\n\n if (cache && cacheKey) {\n try {\n await runWithCacheTenant(dictionaryTenantId, () =>\n cache.set(cacheKey, payload, {\n ttl: DICTIONARY_ENTRIES_TTL_MS,\n tags: [\n ...buildCollectionTags(DICTIONARY_ENTRY_RESOURCE, dictionaryTenantId, [dictionaryOrgId]),\n buildRecordTag(DICTIONARY_DEFINITION_RESOURCE, dictionaryTenantId, dictionaryId),\n ],\n }),\n )\n } catch {}\n }\n\n return NextResponse.json(payload)\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('Failed to load dictionary entries', { err })\n return NextResponse.json({ error: 'Failed to load dictionary entries' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n if (!context.auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const payload = createDictionaryEntrySchema.parse(await req.json().catch(() => ({})))\n const guardUserId = resolveDictionaryActorId(context.auth)\n const guardResult = await validateCrudMutationGuard(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: DICTIONARY_ENTRY_RESOURCE,\n resourceId: dictionaryId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: payload,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n // These nested routes do not use makeCrudRoute, so we invoke the command bus directly.\n const commandBus = (context.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute('dictionaries.entries.create', {\n input: { ...payload, dictionaryId },\n ctx: context.ctx,\n })\n const createResult = (result ?? {}) as { entryId?: string | null }\n const createdEntryId = typeof createResult.entryId === 'string' ? createResult.entryId : null\n if (!createdEntryId) {\n throw new CrudHttpError(500, { error: context.translate('dictionaries.errors.entry_create_failed', 'Failed to create dictionary entry') })\n }\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: DICTIONARY_ENTRY_RESOURCE,\n resourceId: createdEntryId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n const entry = await findOneWithDecryption(\n context.em.fork(),\n DictionaryEntry,\n createdEntryId,\n { populate: ['dictionary'] },\n { tenantId: context.auth.tenantId ?? null, organizationId: context.auth.orgId ?? null },\n )\n if (!entry) {\n throw new CrudHttpError(500, { error: context.translate('dictionaries.errors.entry_create_failed', 'Failed to create dictionary entry') })\n }\n const response = NextResponse.json({\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color,\n icon: entry.icon,\n position: entry.position ?? 0,\n isDefault: entry.isDefault ?? false,\n createdAt: entry.createdAt,\n updatedAt: entry.updatedAt,\n }, { status: 201 })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'dictionaries.entry',\n resourceId: createdEntryId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('Failed to create dictionary entry', { err })\n return NextResponse.json({ error: 'Failed to create dictionary entry' }, { status: 500 })\n }\n}\n\nconst dictionaryEntriesGetDoc: OpenApiMethodDoc = {\n summary: 'List dictionary entries',\n description: 'Returns entries for the specified dictionary ordered by its configured entry sort mode.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Dictionary entries.', schema: dictionaryEntryListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid parameters', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to load dictionary entries', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryEntriesPostDoc: OpenApiMethodDoc = {\n summary: 'Create dictionary entry',\n description: 'Creates a new entry in the specified dictionary.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: createEntryDocSchema,\n description: 'Entry value, label, and optional presentation metadata.',\n },\n responses: [\n { status: 201, description: 'Dictionary entry created.', schema: dictionaryEntryResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to create dictionary entry', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Dictionary entries collection',\n pathParams: dictionaryIdParamsSchema,\n methods: {\n GET: dictionaryEntriesGetDoc,\n POST: dictionaryEntriesPostDoc,\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext, resolveDictionaryActorId } from '@open-mercato/core/modules/dictionaries/api/context'\nimport { createDictionaryEntrySchema } from '@open-mercato/core/modules/dictionaries/data/validators'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n createDictionaryEntrySchema as createEntryDocSchema,\n dictionaryEntryListResponseSchema,\n dictionaryEntryResponseSchema,\n dictionaryIdParamsSchema,\n dictionariesErrorSchema,\n dictionariesTag,\n} from '../../openapi'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n buildCollectionTags,\n buildRecordTag,\n isCrudCacheEnabled,\n resolveCrudCache,\n} from '@open-mercato/shared/lib/crud/cache'\nimport {\n resolveDictionaryEntrySortMode,\n sortDictionaryEntries,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('dictionaries').child({ component: 'entries-api' })\n\nconst paramsSchema = z.object({ dictionaryId: z.string().uuid() })\n\nconst DICTIONARY_ENTRY_RESOURCE = 'dictionaries.entry'\nconst DICTIONARY_DEFINITION_RESOURCE = 'dictionaries.dictionary'\nconst DICTIONARY_ENTRIES_TTL_MS = 5 * 60_000\n\nfunction buildEntriesCacheKey(params: {\n dictionaryId: string\n organizationId: string | null\n sortMode: string\n}): string {\n return `dictionaries:entries:${params.dictionaryId}:org=${params.organizationId ?? 'null'}:sort=${params.sortMode}`\n}\n\nasync function loadDictionary(\n context: Awaited<ReturnType<typeof resolveDictionariesRouteContext>>,\n id: string,\n options: { allowInherited?: boolean } = {},\n) {\n const { allowInherited = false } = options\n if (!allowInherited && !context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const baseFilter = {\n id,\n tenantId: context.tenantId,\n deletedAt: null,\n }\n const filter = allowInherited\n ? {\n ...baseFilter,\n ...(context.readableOrganizationIds.length\n ? { organizationId: { $in: context.readableOrganizationIds } }\n : {}),\n }\n : {\n ...baseFilter,\n organizationId: context.organizationId,\n }\n const dictionary = await context.em.findOne(Dictionary, filter)\n if (!dictionary) {\n throw new CrudHttpError(404, { error: context.translate('dictionaries.errors.not_found', 'Dictionary not found') })\n }\n return dictionary\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dictionaries.view'] },\n POST: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nexport async function GET(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n if (!context.auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const dictionary = await loadDictionary(context, dictionaryId, { allowInherited: true })\n const dictionaryTenantId = dictionary.tenantId\n const dictionaryOrgId = dictionary.organizationId ?? null\n const sortMode = resolveDictionaryEntrySortMode(dictionary.entrySortMode)\n\n // Dictionary CF selects hit this on every CrudForm open, so cache the\n // decrypted+sorted options payload. The entry writes flow through the\n // command bus (resourceKind dictionaries.entry), which already flushes the\n // matching collection tag post-commit \u2014 no new invalidation wiring needed.\n const cache = isCrudCacheEnabled() ? resolveCrudCache(context.container) : null\n const cacheKey = cache\n ? buildEntriesCacheKey({ dictionaryId, organizationId: dictionaryOrgId, sortMode })\n : null\n\n if (cache && cacheKey) {\n const cached = await runWithCacheTenant(dictionaryTenantId, () => cache.get(cacheKey))\n if (cached) {\n return NextResponse.json(cached)\n }\n }\n\n const entries = await findWithDecryption(\n context.em,\n DictionaryEntry,\n {\n dictionary,\n organizationId: dictionary.organizationId,\n tenantId: dictionary.tenantId,\n },\n {},\n { tenantId: dictionary.tenantId, organizationId: dictionary.organizationId },\n )\n const sortedEntries = sortDictionaryEntries(entries, sortMode)\n\n const payload = {\n items: sortedEntries.map((entry) => ({\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color,\n icon: entry.icon,\n position: entry.position ?? 0,\n isDefault: entry.isDefault ?? false,\n createdAt: entry.createdAt,\n updatedAt: entry.updatedAt,\n })),\n }\n\n if (cache && cacheKey) {\n try {\n await runWithCacheTenant(dictionaryTenantId, () =>\n cache.set(cacheKey, payload, {\n ttl: DICTIONARY_ENTRIES_TTL_MS,\n tags: [\n ...buildCollectionTags(DICTIONARY_ENTRY_RESOURCE, dictionaryTenantId, [dictionaryOrgId]),\n buildRecordTag(DICTIONARY_DEFINITION_RESOURCE, dictionaryTenantId, dictionaryId),\n ],\n }),\n )\n } catch {}\n }\n\n return NextResponse.json(payload)\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Invalid input', details: err.issues }, { status: 400 })\n }\n logger.error('Failed to load dictionary entries', { err })\n return NextResponse.json({ error: 'Failed to load dictionary entries' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n if (!context.auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const payload = createDictionaryEntrySchema.parse(await req.json().catch(() => ({})))\n const guardUserId = resolveDictionaryActorId(context.auth)\n const guardResult = await validateCrudMutationGuard(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: DICTIONARY_ENTRY_RESOURCE,\n resourceId: dictionaryId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: payload,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n // These nested routes do not use makeCrudRoute, so we invoke the command bus directly.\n const commandBus = (context.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute('dictionaries.entries.create', {\n input: { ...payload, dictionaryId },\n ctx: context.ctx,\n })\n const createResult = (result ?? {}) as { entryId?: string | null }\n const createdEntryId = typeof createResult.entryId === 'string' ? createResult.entryId : null\n if (!createdEntryId) {\n throw new CrudHttpError(500, { error: context.translate('dictionaries.errors.entry_create_failed', 'Failed to create dictionary entry') })\n }\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: DICTIONARY_ENTRY_RESOURCE,\n resourceId: createdEntryId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n const entry = await findOneWithDecryption(\n context.em.fork(),\n DictionaryEntry,\n createdEntryId,\n { populate: ['dictionary'] },\n { tenantId: context.auth.tenantId ?? null, organizationId: context.auth.orgId ?? null },\n )\n if (!entry) {\n throw new CrudHttpError(500, { error: context.translate('dictionaries.errors.entry_create_failed', 'Failed to create dictionary entry') })\n }\n const response = NextResponse.json({\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color,\n icon: entry.icon,\n position: entry.position ?? 0,\n isDefault: entry.isDefault ?? false,\n createdAt: entry.createdAt,\n updatedAt: entry.updatedAt,\n }, { status: 201 })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'dictionaries.entry',\n resourceId: createdEntryId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Invalid input', details: err.issues }, { status: 400 })\n }\n logger.error('Failed to create dictionary entry', { err })\n return NextResponse.json({ error: 'Failed to create dictionary entry' }, { status: 500 })\n }\n}\n\nconst dictionaryEntriesGetDoc: OpenApiMethodDoc = {\n summary: 'List dictionary entries',\n description: 'Returns entries for the specified dictionary ordered by its configured entry sort mode.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Dictionary entries.', schema: dictionaryEntryListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid parameters', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to load dictionary entries', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryEntriesPostDoc: OpenApiMethodDoc = {\n summary: 'Create dictionary entry',\n description: 'Creates a new entry in the specified dictionary.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: createEntryDocSchema,\n description: 'Entry value, label, and optional presentation metadata.',\n },\n responses: [\n { status: 201, description: 'Dictionary entry created.', schema: dictionaryEntryResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to create dictionary entry', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Dictionary entries collection',\n pathParams: dictionaryIdParamsSchema,\n methods: {\n GET: dictionaryEntriesGetDoc,\n POST: dictionaryEntriesPostDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -117,6 +117,9 @@ async function POST(req, ctx) {
|
|
|
117
117
|
if (isCrudHttpError(err)) {
|
|
118
118
|
return NextResponse.json(err.body, { status: err.status });
|
|
119
119
|
}
|
|
120
|
+
if (err instanceof z.ZodError) {
|
|
121
|
+
return NextResponse.json({ error: "Invalid input", details: err.issues }, { status: 400 });
|
|
122
|
+
}
|
|
120
123
|
logger.error("Failed to set default entry", { err });
|
|
121
124
|
return NextResponse.json({ error: "Failed to set default entry" }, { status: 500 });
|
|
122
125
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/dictionaries/api/%5BdictionaryId%5D/entries/set-default/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext } from '@open-mercato/core/modules/dictionaries/api/context'\nimport {\n setDefaultDictionaryEntryCommandSchema,\n setDefaultDictionaryEntrySchema,\n type SetDefaultDictionaryEntryCommandInput,\n} from '@open-mercato/core/modules/dictionaries/data/validators'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport {\n dictionaryIdParamsSchema,\n dictionariesErrorSchema,\n dictionariesOkSchema,\n dictionariesTag,\n setDefaultEntryRequestSchema,\n} from '../../../openapi'\nimport { resolveDictionaryActorId } from '../../../context'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('dictionaries').child({ component: 'entries-api' })\n\nconst paramsSchema = z.object({ dictionaryId: z.string().uuid() })\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nexport async function POST(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n if (!context.auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n\n if (!context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const dictionaryEm = context.em.fork()\n const dictionary = await findOneWithDecryption(\n dictionaryEm,\n Dictionary,\n {\n id: dictionaryId,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n deletedAt: null,\n },\n undefined,\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n if (!dictionary) {\n throw new CrudHttpError(404, { error: context.translate('dictionaries.errors.not_found', 'Dictionary not found') })\n }\n\n const payload = setDefaultDictionaryEntrySchema.parse(await readJsonSafe(req, {}))\n const guardUserId = resolveDictionaryActorId(context.auth)\n const guardResult = await validateCrudMutationGuard(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionaryId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: payload,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandInput = setDefaultDictionaryEntryCommandSchema.parse({\n dictionaryId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n entryId: payload.entryId,\n } satisfies SetDefaultDictionaryEntryCommandInput)\n\n const commandBus = context.container.resolve('commandBus') as CommandBus\n const { logEntry } = await commandBus.execute<SetDefaultDictionaryEntryCommandInput, { dictionaryId: string; entryId: string; clearedIds: string[] }>(\n 'dictionaries.entries.set_default',\n {\n input: commandInput,\n ctx: context.ctx,\n },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionaryId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const response = NextResponse.json({ ok: true })\n if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'dictionaries.dictionary',\n resourceId: logEntry.resourceId ?? dictionaryId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : new Date().toISOString(),\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('Failed to set default entry', { err })\n return NextResponse.json({ error: 'Failed to set default entry' }, { status: 500 })\n }\n}\n\nconst setDefaultPostDoc: OpenApiMethodDoc = {\n summary: 'Set default dictionary entry',\n description: 'Marks the specified entry as the default for this dictionary, clearing any previous default.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: setDefaultEntryRequestSchema,\n description: 'ID of the entry to set as default.',\n },\n responses: [\n { status: 200, description: 'Default entry set.', schema: dictionariesOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary or entry not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to set default entry', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Set default dictionary entry',\n pathParams: dictionaryIdParamsSchema,\n methods: {\n POST: setDefaultPostDoc,\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext } from '@open-mercato/core/modules/dictionaries/api/context'\nimport {\n setDefaultDictionaryEntryCommandSchema,\n setDefaultDictionaryEntrySchema,\n type SetDefaultDictionaryEntryCommandInput,\n} from '@open-mercato/core/modules/dictionaries/data/validators'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport {\n dictionaryIdParamsSchema,\n dictionariesErrorSchema,\n dictionariesOkSchema,\n dictionariesTag,\n setDefaultEntryRequestSchema,\n} from '../../../openapi'\nimport { resolveDictionaryActorId } from '../../../context'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('dictionaries').child({ component: 'entries-api' })\n\nconst paramsSchema = z.object({ dictionaryId: z.string().uuid() })\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nexport async function POST(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n if (!context.auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n\n if (!context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const dictionaryEm = context.em.fork()\n const dictionary = await findOneWithDecryption(\n dictionaryEm,\n Dictionary,\n {\n id: dictionaryId,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n deletedAt: null,\n },\n undefined,\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n if (!dictionary) {\n throw new CrudHttpError(404, { error: context.translate('dictionaries.errors.not_found', 'Dictionary not found') })\n }\n\n const payload = setDefaultDictionaryEntrySchema.parse(await readJsonSafe(req, {}))\n const guardUserId = resolveDictionaryActorId(context.auth)\n const guardResult = await validateCrudMutationGuard(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionaryId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: payload,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandInput = setDefaultDictionaryEntryCommandSchema.parse({\n dictionaryId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n entryId: payload.entryId,\n } satisfies SetDefaultDictionaryEntryCommandInput)\n\n const commandBus = context.container.resolve('commandBus') as CommandBus\n const { logEntry } = await commandBus.execute<SetDefaultDictionaryEntryCommandInput, { dictionaryId: string; entryId: string; clearedIds: string[] }>(\n 'dictionaries.entries.set_default',\n {\n input: commandInput,\n ctx: context.ctx,\n },\n )\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionaryId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const response = NextResponse.json({ ok: true })\n if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'dictionaries.dictionary',\n resourceId: logEntry.resourceId ?? dictionaryId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : new Date().toISOString(),\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Invalid input', details: err.issues }, { status: 400 })\n }\n logger.error('Failed to set default entry', { err })\n return NextResponse.json({ error: 'Failed to set default entry' }, { status: 500 })\n }\n}\n\nconst setDefaultPostDoc: OpenApiMethodDoc = {\n summary: 'Set default dictionary entry',\n description: 'Marks the specified entry as the default for this dictionary, clearing any previous default.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: setDefaultEntryRequestSchema,\n description: 'ID of the entry to set as default.',\n },\n responses: [\n { status: 200, description: 'Default entry set.', schema: dictionariesOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary or entry not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to set default entry', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Set default dictionary entry',\n pathParams: dictionaryIdParamsSchema,\n methods: {\n POST: setDefaultPostDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -364,99 +364,114 @@ function DictionaryEntriesEditor({ dictionaryId, dictionaryName, readOnly = fals
|
|
|
364
364
|
] }) }),
|
|
365
365
|
/* @__PURE__ */ jsx(TableBody, { children: tableContent })
|
|
366
366
|
] }) }),
|
|
367
|
-
/* @__PURE__ */ jsx(Dialog, { open: dialogOpen, onOpenChange: (open) => !open ? closeDialog() : void 0, children: /* @__PURE__ */ jsxs(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
/* @__PURE__ */ jsx(
|
|
376
|
-
Input,
|
|
377
|
-
{
|
|
378
|
-
type: "text",
|
|
379
|
-
value: formState.value,
|
|
380
|
-
onChange: (event) => {
|
|
381
|
-
const nextValue = event.target.value;
|
|
382
|
-
setFormState((prev) => ({ ...prev, value: nextValue }));
|
|
383
|
-
if (errors.value) {
|
|
384
|
-
setErrors((prev) => ({ ...prev, value: void 0 }));
|
|
385
|
-
}
|
|
386
|
-
},
|
|
387
|
-
"aria-invalid": errors.value ? "true" : "false",
|
|
388
|
-
"aria-describedby": "dictionary-entry-value-error"
|
|
389
|
-
}
|
|
390
|
-
),
|
|
391
|
-
errors.value ? /* @__PURE__ */ jsx("p", { id: "dictionary-entry-value-error", className: "text-xs text-destructive", children: errors.value }) : null
|
|
392
|
-
] }),
|
|
393
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
394
|
-
/* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: t("dictionaries.config.entries.dialog.labelLabel", "Label") }),
|
|
395
|
-
/* @__PURE__ */ jsx(
|
|
396
|
-
Input,
|
|
397
|
-
{
|
|
398
|
-
type: "text",
|
|
399
|
-
value: formState.label,
|
|
400
|
-
onChange: (event) => setFormState((prev) => ({ ...prev, label: event.target.value })),
|
|
401
|
-
placeholder: t("dictionaries.config.entries.dialog.labelPlaceholder", "Display name shown in UI")
|
|
402
|
-
}
|
|
403
|
-
)
|
|
404
|
-
] }),
|
|
405
|
-
/* @__PURE__ */ jsx(
|
|
406
|
-
AppearanceSelector,
|
|
407
|
-
{
|
|
408
|
-
icon: appearance.icon,
|
|
409
|
-
color: appearance.color,
|
|
410
|
-
previewLabel: formState.label.trim() || formState.value.trim(),
|
|
411
|
-
onIconChange: (next) => {
|
|
412
|
-
appearance.setIcon(next);
|
|
413
|
-
setFormState((prev) => ({ ...prev, icon: next }));
|
|
414
|
-
},
|
|
415
|
-
onColorChange: (next) => {
|
|
416
|
-
appearance.setColor(next);
|
|
417
|
-
setFormState((prev) => ({ ...prev, color: next }));
|
|
418
|
-
},
|
|
419
|
-
labels: {
|
|
420
|
-
colorLabel: t("dictionaries.config.entries.dialog.colorLabel", "Color"),
|
|
421
|
-
colorHelp: t("dictionaries.config.entries.dialog.colorHelp", "Pick a highlight color for this entry."),
|
|
422
|
-
colorClearLabel: t("dictionaries.config.entries.dialog.colorClear", "Remove color"),
|
|
423
|
-
iconLabel: t("dictionaries.config.entries.dialog.iconLabel", "Icon or emoji"),
|
|
424
|
-
iconPlaceholder: t("dictionaries.config.entries.dialog.iconPlaceholder", "Type an emoji or icon token."),
|
|
425
|
-
iconPickerTriggerLabel: t("dictionaries.config.entries.dialog.iconBrowse", "Browse icons and emoji"),
|
|
426
|
-
iconSearchPlaceholder: t("dictionaries.config.entries.dialog.iconSearchPlaceholder", "Search icons or emojis\u2026"),
|
|
427
|
-
iconSearchEmptyLabel: t("dictionaries.config.entries.dialog.iconSearchEmpty", "No icons match your search."),
|
|
428
|
-
iconSuggestionsLabel: t("dictionaries.config.entries.dialog.iconSuggestions", "Suggestions"),
|
|
429
|
-
iconClearLabel: t("dictionaries.config.entries.dialog.iconClear", "Remove icon"),
|
|
430
|
-
previewEmptyLabel: t("dictionaries.config.entries.dialog.previewEmpty", "No appearance selected")
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
)
|
|
434
|
-
] }),
|
|
435
|
-
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
436
|
-
/* @__PURE__ */ jsx(
|
|
437
|
-
Button,
|
|
438
|
-
{
|
|
439
|
-
type: "button",
|
|
440
|
-
variant: "ghost",
|
|
441
|
-
onClick: closeDialog,
|
|
442
|
-
disabled: isSaving,
|
|
443
|
-
children: t("dictionaries.config.entries.dialog.cancel", "Cancel")
|
|
444
|
-
}
|
|
445
|
-
),
|
|
446
|
-
/* @__PURE__ */ jsxs(
|
|
447
|
-
Button,
|
|
448
|
-
{
|
|
449
|
-
type: "button",
|
|
450
|
-
onClick: handleSave,
|
|
451
|
-
disabled: isSaving,
|
|
452
|
-
children: [
|
|
453
|
-
isSaving ? /* @__PURE__ */ jsx(Spinner, { className: "mr-2 h-4 w-4" }) : null,
|
|
454
|
-
t("dictionaries.config.entries.dialog.save", "Save")
|
|
455
|
-
]
|
|
367
|
+
/* @__PURE__ */ jsx(Dialog, { open: dialogOpen, onOpenChange: (open) => !open ? closeDialog() : void 0, children: /* @__PURE__ */ jsxs(
|
|
368
|
+
DialogContent,
|
|
369
|
+
{
|
|
370
|
+
"aria-modal": "true",
|
|
371
|
+
onKeyDown: (event) => {
|
|
372
|
+
if ((event.metaKey || event.ctrlKey) && event.key === "Enter" && !isSaving) {
|
|
373
|
+
event.preventDefault();
|
|
374
|
+
void handleSave();
|
|
456
375
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
376
|
+
},
|
|
377
|
+
children: [
|
|
378
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: formState.id ? t("dictionaries.config.entries.dialog.editTitle", "Edit dictionary entry") : t("dictionaries.config.entries.dialog.addTitle", "Add dictionary entry") }) }),
|
|
379
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
380
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
381
|
+
/* @__PURE__ */ jsxs("label", { htmlFor: "dictionary-entry-value", className: "text-sm font-medium", children: [
|
|
382
|
+
t("dictionaries.config.entries.dialog.valueLabel", "Value"),
|
|
383
|
+
/* @__PURE__ */ jsx("span", { className: "ml-1 text-destructive", children: "*" })
|
|
384
|
+
] }),
|
|
385
|
+
/* @__PURE__ */ jsx(
|
|
386
|
+
Input,
|
|
387
|
+
{
|
|
388
|
+
id: "dictionary-entry-value",
|
|
389
|
+
type: "text",
|
|
390
|
+
value: formState.value,
|
|
391
|
+
onChange: (event) => {
|
|
392
|
+
const nextValue = event.target.value;
|
|
393
|
+
setFormState((prev) => ({ ...prev, value: nextValue }));
|
|
394
|
+
if (errors.value) {
|
|
395
|
+
setErrors((prev) => ({ ...prev, value: void 0 }));
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
"aria-required": "true",
|
|
399
|
+
"aria-invalid": errors.value ? "true" : "false",
|
|
400
|
+
"aria-describedby": errors.value ? "dictionary-entry-value-error" : void 0
|
|
401
|
+
}
|
|
402
|
+
),
|
|
403
|
+
errors.value ? /* @__PURE__ */ jsx("p", { id: "dictionary-entry-value-error", className: "text-xs text-destructive", children: errors.value }) : null
|
|
404
|
+
] }),
|
|
405
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
406
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "dictionary-entry-label", className: "text-sm font-medium", children: t("dictionaries.config.entries.dialog.labelLabel", "Label") }),
|
|
407
|
+
/* @__PURE__ */ jsx(
|
|
408
|
+
Input,
|
|
409
|
+
{
|
|
410
|
+
id: "dictionary-entry-label",
|
|
411
|
+
type: "text",
|
|
412
|
+
value: formState.label,
|
|
413
|
+
onChange: (event) => setFormState((prev) => ({ ...prev, label: event.target.value })),
|
|
414
|
+
placeholder: t("dictionaries.config.entries.dialog.labelPlaceholder", "Display name shown in UI")
|
|
415
|
+
}
|
|
416
|
+
)
|
|
417
|
+
] }),
|
|
418
|
+
/* @__PURE__ */ jsx(
|
|
419
|
+
AppearanceSelector,
|
|
420
|
+
{
|
|
421
|
+
icon: appearance.icon,
|
|
422
|
+
color: appearance.color,
|
|
423
|
+
previewLabel: formState.label.trim() || formState.value.trim(),
|
|
424
|
+
onIconChange: (next) => {
|
|
425
|
+
appearance.setIcon(next);
|
|
426
|
+
setFormState((prev) => ({ ...prev, icon: next }));
|
|
427
|
+
},
|
|
428
|
+
onColorChange: (next) => {
|
|
429
|
+
appearance.setColor(next);
|
|
430
|
+
setFormState((prev) => ({ ...prev, color: next }));
|
|
431
|
+
},
|
|
432
|
+
labels: {
|
|
433
|
+
colorLabel: t("dictionaries.config.entries.dialog.colorLabel", "Color"),
|
|
434
|
+
colorHelp: t("dictionaries.config.entries.dialog.colorHelp", "Pick a highlight color for this entry."),
|
|
435
|
+
colorClearLabel: t("dictionaries.config.entries.dialog.colorClear", "Remove color"),
|
|
436
|
+
iconLabel: t("dictionaries.config.entries.dialog.iconLabel", "Icon or emoji"),
|
|
437
|
+
iconPlaceholder: t("dictionaries.config.entries.dialog.iconPlaceholder", "Type an emoji or icon token."),
|
|
438
|
+
iconPickerTriggerLabel: t("dictionaries.config.entries.dialog.iconBrowse", "Browse icons and emoji"),
|
|
439
|
+
iconSearchPlaceholder: t("dictionaries.config.entries.dialog.iconSearchPlaceholder", "Search icons or emojis\u2026"),
|
|
440
|
+
iconSearchEmptyLabel: t("dictionaries.config.entries.dialog.iconSearchEmpty", "No icons match your search."),
|
|
441
|
+
iconSuggestionsLabel: t("dictionaries.config.entries.dialog.iconSuggestions", "Suggestions"),
|
|
442
|
+
iconClearLabel: t("dictionaries.config.entries.dialog.iconClear", "Remove icon"),
|
|
443
|
+
previewEmptyLabel: t("dictionaries.config.entries.dialog.previewEmpty", "No appearance selected")
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
)
|
|
447
|
+
] }),
|
|
448
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
449
|
+
/* @__PURE__ */ jsx(
|
|
450
|
+
Button,
|
|
451
|
+
{
|
|
452
|
+
type: "button",
|
|
453
|
+
variant: "ghost",
|
|
454
|
+
onClick: closeDialog,
|
|
455
|
+
disabled: isSaving,
|
|
456
|
+
children: t("dictionaries.config.entries.dialog.cancel", "Cancel")
|
|
457
|
+
}
|
|
458
|
+
),
|
|
459
|
+
/* @__PURE__ */ jsxs(
|
|
460
|
+
Button,
|
|
461
|
+
{
|
|
462
|
+
type: "button",
|
|
463
|
+
onClick: handleSave,
|
|
464
|
+
disabled: isSaving,
|
|
465
|
+
children: [
|
|
466
|
+
isSaving ? /* @__PURE__ */ jsx(Spinner, { className: "mr-2 h-4 w-4" }) : null,
|
|
467
|
+
t("dictionaries.config.entries.dialog.save", "Save")
|
|
468
|
+
]
|
|
469
|
+
}
|
|
470
|
+
)
|
|
471
|
+
] })
|
|
472
|
+
]
|
|
473
|
+
}
|
|
474
|
+
) }),
|
|
460
475
|
/* @__PURE__ */ jsx(Dialog, { open: !!translateEntry, onOpenChange: (open) => {
|
|
461
476
|
if (!open) setTranslateEntry(null);
|
|
462
477
|
}, children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-2xl max-h-[80vh] overflow-y-auto", children: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/dictionaries/components/DictionaryEntriesEditor.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Plus, Pencil, Trash2, RefreshCw, Languages } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@open-mercato/ui/primitives/table'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\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 { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { AppearanceSelector, useAppearanceState } from './AppearanceSelector'\nimport { DictionaryValue } from './dictionaryAppearance'\nimport {\n invalidateDictionaryEntries,\n useDictionaryEntries,\n} from './hooks/useDictionaryEntries'\nimport type { DictionaryEntryRecord } from './hooks/useDictionaryEntries'\nimport {\n DialogDescription,\n} from '@open-mercato/ui/primitives/dialog'\nimport { TranslationManager } from '@open-mercato/core/modules/translations/components/TranslationManager'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('dictionaries').child({ component: 'DictionaryEntriesEditor' })\n\ntype Entry = DictionaryEntryRecord\n\ntype DictionaryEntriesEditorProps = {\n dictionaryId: string\n dictionaryName: string\n readOnly?: boolean\n}\n\ntype FormState = {\n id?: string | null\n value: string\n label: string\n color: string | null\n icon: string | null\n updatedAt?: string | null\n}\n\nexport function DictionaryEntriesEditor({ dictionaryId, dictionaryName, readOnly = false }: DictionaryEntriesEditorProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const readOnlyMessage = t('dictionaries.config.entries.readOnly', 'Inherited dictionaries are managed at the parent organization.')\n const queryClient = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n const dictionaryQuery = useDictionaryEntries(dictionaryId, scopeVersion)\n const entries = React.useMemo<Entry[]>(() => dictionaryQuery.data?.fullEntries ?? [], [dictionaryQuery.data?.fullEntries])\n const dictionaryMap = dictionaryQuery.data?.map ?? {}\n const isInitialLoading = dictionaryQuery.isLoading\n const loadError = dictionaryQuery.isError\n ? t('dictionaries.config.entries.error.load', 'Failed to load dictionary entries.')\n : null\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [isDeleting, setIsDeleting] = React.useState(false)\n const [isSaving, setIsSaving] = React.useState(false)\n const [formState, setFormState] = React.useState<FormState>(() => ({\n value: '',\n label: '',\n color: null,\n icon: null,\n }))\n const [errors, setErrors] = React.useState<{ value?: string; label?: string }>({})\n const [translateEntry, setTranslateEntry] = React.useState<Entry | null>(null)\n const appearance = useAppearanceState(formState.icon, formState.color)\n const mutationContextId = React.useMemo(\n () => `dictionaries:dictionary_entry:${dictionaryId}`,\n [dictionaryId],\n )\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 resetForm = React.useCallback(() => {\n setFormState({ value: '', label: '', color: null, icon: null })\n appearance.setColor(null)\n appearance.setIcon(null)\n setErrors({})\n }, [appearance])\n\n const openDialog = React.useCallback(\n (entry?: Entry) => {\n if (readOnly) {\n flash(readOnlyMessage, 'info')\n return\n }\n if (entry) {\n setFormState({\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color ?? null,\n icon: entry.icon ?? null,\n updatedAt: entry.updatedAt ?? null,\n })\n appearance.setColor(entry.color ?? null)\n appearance.setIcon(entry.icon ?? null)\n } else {\n resetForm()\n }\n setErrors({})\n setDialogOpen(true)\n },\n [appearance, readOnly, readOnlyMessage, resetForm],\n )\n\n const closeDialog = React.useCallback(() => {\n setDialogOpen(false)\n resetForm()\n setErrors({})\n }, [resetForm])\n\n const handleSave = React.useCallback(async () => {\n if (readOnly) {\n flash(readOnlyMessage, 'info')\n return\n }\n const trimmedValue = formState.value.trim()\n const trimmedLabel = formState.label.trim()\n const nextErrors: { value?: string } = {}\n if (!trimmedValue) {\n nextErrors.value = t('dictionaries.config.entries.error.required', 'Value is required.')\n }\n if (nextErrors.value) {\n setErrors(nextErrors)\n return\n }\n setErrors({})\n setIsSaving(true)\n try {\n const payload = {\n value: trimmedValue,\n label: trimmedLabel || trimmedValue,\n color: appearance.color,\n icon: appearance.icon,\n }\n if (formState.id) {\n const entryId = formState.id\n const expectedUpdatedAt = formState.updatedAt\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(\n buildOptimisticLockHeader(expectedUpdatedAt),\n async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/dictionaries/${dictionaryId}/entries/${entryId}`,\n {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n )\n if (!call.ok) {\n throw Object.assign(\n new Error(typeof call.result?.error === 'string' ? call.result.error : 'Failed to save dictionary entry'),\n { status: call.status, ...(call.result && typeof call.result === 'object' ? call.result : {}) },\n )\n }\n return call\n },\n ),\n context: {\n formId: mutationContextId,\n resourceKind: 'dictionaries.dictionary_entry',\n resourceId: entryId,\n retryLastMutation,\n },\n mutationPayload: payload,\n })\n flash(t('dictionaries.config.entries.success.update', 'Dictionary entry updated.'), 'success')\n } else {\n await runMutation({\n operation: async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/dictionaries/${dictionaryId}/entries`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n )\n if (!call.ok) {\n throw new Error(\n typeof call.result?.error === 'string' ? call.result.error : 'Failed to create dictionary entry',\n )\n }\n return call\n },\n context: {\n formId: mutationContextId,\n resourceKind: 'dictionaries.dictionary_entry',\n retryLastMutation,\n },\n mutationPayload: payload,\n })\n flash(t('dictionaries.config.entries.success.create', 'Dictionary entry created.'), 'success')\n }\n await invalidateDictionaryEntries(queryClient, dictionaryId)\n setDialogOpen(false)\n setFormState({ value: '', label: '', color: null, icon: null })\n appearance.setColor(null)\n appearance.setIcon(null)\n setErrors({})\n } catch (err) {\n if (surfaceRecordConflict(err, t)) {\n return\n }\n logger.error('Failed to save dictionary entry', { err })\n flash(t('dictionaries.config.entries.error.save', 'Failed to save dictionary entry.'), 'error')\n } finally {\n setIsSaving(false)\n }\n }, [appearance, dictionaryId, formState.id, formState.label, formState.updatedAt, formState.value, mutationContextId, queryClient, readOnly, readOnlyMessage, runMutation, retryLastMutation, t])\n\n const handleDelete = React.useCallback(\n async (entry: Entry) => {\n if (readOnly) {\n flash(readOnlyMessage, 'info')\n return\n }\n if (!entry.id) return\n const confirmDelete = await confirm({\n title: t('dictionaries.config.entries.delete.confirm', 'Delete \"{{value}}\"?', { value: entry.label || entry.value }),\n variant: 'destructive',\n })\n if (!confirmDelete) return\n setIsDeleting(true)\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(\n buildOptimisticLockHeader(entry.updatedAt),\n async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/dictionaries/${dictionaryId}/entries/${entry.id}`,\n { method: 'DELETE' },\n )\n if (!call.ok) {\n throw Object.assign(\n new Error(\n typeof call.result?.error === 'string' ? call.result.error : 'Failed to delete dictionary entry',\n ),\n { status: call.status, ...(call.result && typeof call.result === 'object' ? call.result : {}) },\n )\n }\n return call\n },\n ),\n context: {\n formId: mutationContextId,\n resourceKind: 'dictionaries.dictionary_entry',\n resourceId: entry.id,\n retryLastMutation,\n },\n mutationPayload: { id: entry.id },\n })\n await invalidateDictionaryEntries(queryClient, dictionaryId)\n flash(t('dictionaries.config.entries.success.delete', 'Dictionary entry deleted.'), 'success')\n } catch (err) {\n // Route a concurrent-edit 409 through the single conflict surface (matching\n // the entry save path); fall back to a flash for any other delete failure.\n if (surfaceRecordConflict(err, t)) {\n return\n }\n logger.error('Failed to delete dictionary entry', { err })\n flash(t('dictionaries.config.entries.error.delete', 'Failed to delete dictionary entry.'), 'error')\n } finally {\n setIsDeleting(false)\n }\n },\n [confirm, dictionaryId, mutationContextId, queryClient, readOnly, readOnlyMessage, runMutation, retryLastMutation, t],\n )\n\n const tableContent = React.useMemo(() => {\n if (isInitialLoading) {\n return (\n <TableRow>\n <TableCell colSpan={4} className=\"py-8 text-center text-sm text-muted-foreground\">\n <Spinner className=\"mx-auto mb-2 h-5 w-5\" />\n {t('dictionaries.config.entries.loading', 'Loading entries\u2026')}\n </TableCell>\n </TableRow>\n )\n }\n if (loadError) {\n return (\n <TableRow>\n <TableCell colSpan={4} className=\"py-6 text-center text-sm text-destructive\">\n {loadError}\n </TableCell>\n </TableRow>\n )\n }\n if (!entries.length) {\n return (\n <TableRow>\n <TableCell colSpan={4} className=\"py-6 text-center text-sm text-muted-foreground\">\n {t('dictionaries.config.entries.empty', 'No entries yet.')}\n </TableCell>\n </TableRow>\n )\n }\n return entries.map((entry) => (\n <TableRow key={entry.id}>\n <TableCell className=\"font-medium\">{entry.value}</TableCell>\n <TableCell>{entry.label}</TableCell>\n <TableCell>\n <DictionaryValue\n value={entry.value}\n map={dictionaryMap}\n fallback={<span className=\"text-sm text-muted-foreground\">{t('dictionaries.config.entries.appearance.none', 'None')}</span>}\n />\n </TableCell>\n <TableCell className=\"flex items-center gap-2\">\n {readOnly ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('dictionaries.config.entries.readOnlyActions', 'Managed in parent organization')}\n </span>\n ) : (\n <>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => openDialog(entry)}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setTranslateEntry(entry)}\n title={t('dictionaries.config.entries.actions.translate', 'Translate')}\n >\n <Languages className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => handleDelete(entry)}\n disabled={isDeleting}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </>\n )}\n </TableCell>\n </TableRow>\n ))\n }, [dictionaryMap, entries, handleDelete, isDeleting, isInitialLoading, loadError, openDialog, readOnly, t])\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold\">{dictionaryName}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {readOnly\n ? readOnlyMessage\n : t('dictionaries.config.entries.subtitle', 'Manage reusable values and appearance for this dictionary.')}\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n dictionaryQuery.refetch().catch(() => {})\n }}\n >\n <RefreshCw className=\"mr-2 h-4 w-4\" />\n {t('dictionaries.config.entries.actions.refresh', 'Refresh')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => openDialog()}\n disabled={readOnly}\n title={readOnly ? readOnlyMessage : undefined}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('dictionaries.config.entries.actions.add', 'Add entry')}\n </Button>\n </div>\n </div>\n\n <div className=\"overflow-hidden rounded-md border\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead className=\"w-48\">{t('dictionaries.config.entries.columns.value', 'Value')}</TableHead>\n <TableHead className=\"w-48\">{t('dictionaries.config.entries.columns.label', 'Label')}</TableHead>\n <TableHead>{t('dictionaries.config.entries.columns.appearance', 'Appearance')}</TableHead>\n <TableHead className=\"w-32 text-right\">{t('dictionaries.config.entries.columns.actions', 'Actions')}</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>{tableContent}</TableBody>\n </Table>\n </div>\n\n <Dialog open={dialogOpen} onOpenChange={(open) => (!open ? closeDialog() : undefined)}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {formState.id\n ? t('dictionaries.config.entries.dialog.editTitle', 'Edit dictionary entry')\n : t('dictionaries.config.entries.dialog.addTitle', 'Add dictionary entry')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {t('dictionaries.config.entries.dialog.valueLabel', 'Value')}\n <span className=\"ml-1 text-destructive\">*</span>\n </label>\n <Input\n type=\"text\"\n value={formState.value}\n onChange={(event) => {\n const nextValue = event.target.value\n setFormState((prev) => ({ ...prev, value: nextValue }))\n if (errors.value) {\n setErrors((prev) => ({ ...prev, value: undefined }))\n }\n }}\n aria-invalid={errors.value ? 'true' : 'false'}\n aria-describedby=\"dictionary-entry-value-error\"\n />\n {errors.value ? (\n <p id=\"dictionary-entry-value-error\" className=\"text-xs text-destructive\">\n {errors.value}\n </p>\n ) : null}\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {t('dictionaries.config.entries.dialog.labelLabel', 'Label')}\n </label>\n <Input\n type=\"text\"\n value={formState.label}\n onChange={(event) => setFormState((prev) => ({ ...prev, label: event.target.value }))}\n placeholder={t('dictionaries.config.entries.dialog.labelPlaceholder', 'Display name shown in UI')}\n />\n </div>\n <AppearanceSelector\n icon={appearance.icon}\n color={appearance.color}\n previewLabel={formState.label.trim() || formState.value.trim()}\n onIconChange={(next) => {\n appearance.setIcon(next)\n setFormState((prev) => ({ ...prev, icon: next }))\n }}\n onColorChange={(next) => {\n appearance.setColor(next)\n setFormState((prev) => ({ ...prev, color: next }))\n }}\n labels={{\n colorLabel: t('dictionaries.config.entries.dialog.colorLabel', 'Color'),\n colorHelp: t('dictionaries.config.entries.dialog.colorHelp', 'Pick a highlight color for this entry.'),\n colorClearLabel: t('dictionaries.config.entries.dialog.colorClear', 'Remove color'),\n iconLabel: t('dictionaries.config.entries.dialog.iconLabel', 'Icon or emoji'),\n iconPlaceholder: t('dictionaries.config.entries.dialog.iconPlaceholder', 'Type an emoji or icon token.'),\n iconPickerTriggerLabel: t('dictionaries.config.entries.dialog.iconBrowse', 'Browse icons and emoji'),\n iconSearchPlaceholder: t('dictionaries.config.entries.dialog.iconSearchPlaceholder', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('dictionaries.config.entries.dialog.iconSearchEmpty', 'No icons match your search.'),\n iconSuggestionsLabel: t('dictionaries.config.entries.dialog.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('dictionaries.config.entries.dialog.iconClear', 'Remove icon'),\n previewEmptyLabel: t('dictionaries.config.entries.dialog.previewEmpty', 'No appearance selected'),\n }}\n />\n </div>\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={closeDialog}\n disabled={isSaving}\n >\n {t('dictionaries.config.entries.dialog.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={handleSave}\n disabled={isSaving}\n >\n {isSaving ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('dictionaries.config.entries.dialog.save', 'Save')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n <Dialog open={!!translateEntry} onOpenChange={(open) => { if (!open) setTranslateEntry(null) }}>\n <DialogContent className=\"max-w-2xl max-h-[80vh] overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{t('translations.manager.translateEntry', 'Translate: {{label}}', { label: translateEntry?.label ?? '' })}</DialogTitle>\n <DialogDescription>\n {t('translations.manager.translateEntryDescription', 'Manage translations for this dictionary entry.')}\n </DialogDescription>\n </DialogHeader>\n {translateEntry?.id && (\n <TranslationManager\n mode=\"embedded\"\n entityType=\"dictionaries:dictionary_entry\"\n recordId={translateEntry.id}\n baseValues={{ label: translateEntry.label }}\n translatableFields={['label']}\n />\n )}\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Plus, Pencil, Trash2, RefreshCw, Languages } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@open-mercato/ui/primitives/table'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\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 { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { AppearanceSelector, useAppearanceState } from './AppearanceSelector'\nimport { DictionaryValue } from './dictionaryAppearance'\nimport {\n invalidateDictionaryEntries,\n useDictionaryEntries,\n} from './hooks/useDictionaryEntries'\nimport type { DictionaryEntryRecord } from './hooks/useDictionaryEntries'\nimport {\n DialogDescription,\n} from '@open-mercato/ui/primitives/dialog'\nimport { TranslationManager } from '@open-mercato/core/modules/translations/components/TranslationManager'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('dictionaries').child({ component: 'DictionaryEntriesEditor' })\n\ntype Entry = DictionaryEntryRecord\n\ntype DictionaryEntriesEditorProps = {\n dictionaryId: string\n dictionaryName: string\n readOnly?: boolean\n}\n\ntype FormState = {\n id?: string | null\n value: string\n label: string\n color: string | null\n icon: string | null\n updatedAt?: string | null\n}\n\nexport function DictionaryEntriesEditor({ dictionaryId, dictionaryName, readOnly = false }: DictionaryEntriesEditorProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const readOnlyMessage = t('dictionaries.config.entries.readOnly', 'Inherited dictionaries are managed at the parent organization.')\n const queryClient = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n const dictionaryQuery = useDictionaryEntries(dictionaryId, scopeVersion)\n const entries = React.useMemo<Entry[]>(() => dictionaryQuery.data?.fullEntries ?? [], [dictionaryQuery.data?.fullEntries])\n const dictionaryMap = dictionaryQuery.data?.map ?? {}\n const isInitialLoading = dictionaryQuery.isLoading\n const loadError = dictionaryQuery.isError\n ? t('dictionaries.config.entries.error.load', 'Failed to load dictionary entries.')\n : null\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [isDeleting, setIsDeleting] = React.useState(false)\n const [isSaving, setIsSaving] = React.useState(false)\n const [formState, setFormState] = React.useState<FormState>(() => ({\n value: '',\n label: '',\n color: null,\n icon: null,\n }))\n const [errors, setErrors] = React.useState<{ value?: string; label?: string }>({})\n const [translateEntry, setTranslateEntry] = React.useState<Entry | null>(null)\n const appearance = useAppearanceState(formState.icon, formState.color)\n const mutationContextId = React.useMemo(\n () => `dictionaries:dictionary_entry:${dictionaryId}`,\n [dictionaryId],\n )\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 resetForm = React.useCallback(() => {\n setFormState({ value: '', label: '', color: null, icon: null })\n appearance.setColor(null)\n appearance.setIcon(null)\n setErrors({})\n }, [appearance])\n\n const openDialog = React.useCallback(\n (entry?: Entry) => {\n if (readOnly) {\n flash(readOnlyMessage, 'info')\n return\n }\n if (entry) {\n setFormState({\n id: entry.id,\n value: entry.value,\n label: entry.label,\n color: entry.color ?? null,\n icon: entry.icon ?? null,\n updatedAt: entry.updatedAt ?? null,\n })\n appearance.setColor(entry.color ?? null)\n appearance.setIcon(entry.icon ?? null)\n } else {\n resetForm()\n }\n setErrors({})\n setDialogOpen(true)\n },\n [appearance, readOnly, readOnlyMessage, resetForm],\n )\n\n const closeDialog = React.useCallback(() => {\n setDialogOpen(false)\n resetForm()\n setErrors({})\n }, [resetForm])\n\n const handleSave = React.useCallback(async () => {\n if (readOnly) {\n flash(readOnlyMessage, 'info')\n return\n }\n const trimmedValue = formState.value.trim()\n const trimmedLabel = formState.label.trim()\n const nextErrors: { value?: string } = {}\n if (!trimmedValue) {\n nextErrors.value = t('dictionaries.config.entries.error.required', 'Value is required.')\n }\n if (nextErrors.value) {\n setErrors(nextErrors)\n return\n }\n setErrors({})\n setIsSaving(true)\n try {\n const payload = {\n value: trimmedValue,\n label: trimmedLabel || trimmedValue,\n color: appearance.color,\n icon: appearance.icon,\n }\n if (formState.id) {\n const entryId = formState.id\n const expectedUpdatedAt = formState.updatedAt\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(\n buildOptimisticLockHeader(expectedUpdatedAt),\n async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/dictionaries/${dictionaryId}/entries/${entryId}`,\n {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n )\n if (!call.ok) {\n throw Object.assign(\n new Error(typeof call.result?.error === 'string' ? call.result.error : 'Failed to save dictionary entry'),\n { status: call.status, ...(call.result && typeof call.result === 'object' ? call.result : {}) },\n )\n }\n return call\n },\n ),\n context: {\n formId: mutationContextId,\n resourceKind: 'dictionaries.dictionary_entry',\n resourceId: entryId,\n retryLastMutation,\n },\n mutationPayload: payload,\n })\n flash(t('dictionaries.config.entries.success.update', 'Dictionary entry updated.'), 'success')\n } else {\n await runMutation({\n operation: async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/dictionaries/${dictionaryId}/entries`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n )\n if (!call.ok) {\n throw new Error(\n typeof call.result?.error === 'string' ? call.result.error : 'Failed to create dictionary entry',\n )\n }\n return call\n },\n context: {\n formId: mutationContextId,\n resourceKind: 'dictionaries.dictionary_entry',\n retryLastMutation,\n },\n mutationPayload: payload,\n })\n flash(t('dictionaries.config.entries.success.create', 'Dictionary entry created.'), 'success')\n }\n await invalidateDictionaryEntries(queryClient, dictionaryId)\n setDialogOpen(false)\n setFormState({ value: '', label: '', color: null, icon: null })\n appearance.setColor(null)\n appearance.setIcon(null)\n setErrors({})\n } catch (err) {\n if (surfaceRecordConflict(err, t)) {\n return\n }\n logger.error('Failed to save dictionary entry', { err })\n flash(t('dictionaries.config.entries.error.save', 'Failed to save dictionary entry.'), 'error')\n } finally {\n setIsSaving(false)\n }\n }, [appearance, dictionaryId, formState.id, formState.label, formState.updatedAt, formState.value, mutationContextId, queryClient, readOnly, readOnlyMessage, runMutation, retryLastMutation, t])\n\n const handleDelete = React.useCallback(\n async (entry: Entry) => {\n if (readOnly) {\n flash(readOnlyMessage, 'info')\n return\n }\n if (!entry.id) return\n const confirmDelete = await confirm({\n title: t('dictionaries.config.entries.delete.confirm', 'Delete \"{{value}}\"?', { value: entry.label || entry.value }),\n variant: 'destructive',\n })\n if (!confirmDelete) return\n setIsDeleting(true)\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(\n buildOptimisticLockHeader(entry.updatedAt),\n async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/dictionaries/${dictionaryId}/entries/${entry.id}`,\n { method: 'DELETE' },\n )\n if (!call.ok) {\n throw Object.assign(\n new Error(\n typeof call.result?.error === 'string' ? call.result.error : 'Failed to delete dictionary entry',\n ),\n { status: call.status, ...(call.result && typeof call.result === 'object' ? call.result : {}) },\n )\n }\n return call\n },\n ),\n context: {\n formId: mutationContextId,\n resourceKind: 'dictionaries.dictionary_entry',\n resourceId: entry.id,\n retryLastMutation,\n },\n mutationPayload: { id: entry.id },\n })\n await invalidateDictionaryEntries(queryClient, dictionaryId)\n flash(t('dictionaries.config.entries.success.delete', 'Dictionary entry deleted.'), 'success')\n } catch (err) {\n // Route a concurrent-edit 409 through the single conflict surface (matching\n // the entry save path); fall back to a flash for any other delete failure.\n if (surfaceRecordConflict(err, t)) {\n return\n }\n logger.error('Failed to delete dictionary entry', { err })\n flash(t('dictionaries.config.entries.error.delete', 'Failed to delete dictionary entry.'), 'error')\n } finally {\n setIsDeleting(false)\n }\n },\n [confirm, dictionaryId, mutationContextId, queryClient, readOnly, readOnlyMessage, runMutation, retryLastMutation, t],\n )\n\n const tableContent = React.useMemo(() => {\n if (isInitialLoading) {\n return (\n <TableRow>\n <TableCell colSpan={4} className=\"py-8 text-center text-sm text-muted-foreground\">\n <Spinner className=\"mx-auto mb-2 h-5 w-5\" />\n {t('dictionaries.config.entries.loading', 'Loading entries\u2026')}\n </TableCell>\n </TableRow>\n )\n }\n if (loadError) {\n return (\n <TableRow>\n <TableCell colSpan={4} className=\"py-6 text-center text-sm text-destructive\">\n {loadError}\n </TableCell>\n </TableRow>\n )\n }\n if (!entries.length) {\n return (\n <TableRow>\n <TableCell colSpan={4} className=\"py-6 text-center text-sm text-muted-foreground\">\n {t('dictionaries.config.entries.empty', 'No entries yet.')}\n </TableCell>\n </TableRow>\n )\n }\n return entries.map((entry) => (\n <TableRow key={entry.id}>\n <TableCell className=\"font-medium\">{entry.value}</TableCell>\n <TableCell>{entry.label}</TableCell>\n <TableCell>\n <DictionaryValue\n value={entry.value}\n map={dictionaryMap}\n fallback={<span className=\"text-sm text-muted-foreground\">{t('dictionaries.config.entries.appearance.none', 'None')}</span>}\n />\n </TableCell>\n <TableCell className=\"flex items-center gap-2\">\n {readOnly ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('dictionaries.config.entries.readOnlyActions', 'Managed in parent organization')}\n </span>\n ) : (\n <>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => openDialog(entry)}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setTranslateEntry(entry)}\n title={t('dictionaries.config.entries.actions.translate', 'Translate')}\n >\n <Languages className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => handleDelete(entry)}\n disabled={isDeleting}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </>\n )}\n </TableCell>\n </TableRow>\n ))\n }, [dictionaryMap, entries, handleDelete, isDeleting, isInitialLoading, loadError, openDialog, readOnly, t])\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold\">{dictionaryName}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {readOnly\n ? readOnlyMessage\n : t('dictionaries.config.entries.subtitle', 'Manage reusable values and appearance for this dictionary.')}\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n dictionaryQuery.refetch().catch(() => {})\n }}\n >\n <RefreshCw className=\"mr-2 h-4 w-4\" />\n {t('dictionaries.config.entries.actions.refresh', 'Refresh')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => openDialog()}\n disabled={readOnly}\n title={readOnly ? readOnlyMessage : undefined}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('dictionaries.config.entries.actions.add', 'Add entry')}\n </Button>\n </div>\n </div>\n\n <div className=\"overflow-hidden rounded-md border\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead className=\"w-48\">{t('dictionaries.config.entries.columns.value', 'Value')}</TableHead>\n <TableHead className=\"w-48\">{t('dictionaries.config.entries.columns.label', 'Label')}</TableHead>\n <TableHead>{t('dictionaries.config.entries.columns.appearance', 'Appearance')}</TableHead>\n <TableHead className=\"w-32 text-right\">{t('dictionaries.config.entries.columns.actions', 'Actions')}</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>{tableContent}</TableBody>\n </Table>\n </div>\n\n <Dialog open={dialogOpen} onOpenChange={(open) => (!open ? closeDialog() : undefined)}>\n <DialogContent\n aria-modal=\"true\"\n onKeyDown={(event) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter' && !isSaving) {\n event.preventDefault()\n void handleSave()\n }\n }}\n >\n <DialogHeader>\n <DialogTitle>\n {formState.id\n ? t('dictionaries.config.entries.dialog.editTitle', 'Edit dictionary entry')\n : t('dictionaries.config.entries.dialog.addTitle', 'Add dictionary entry')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label htmlFor=\"dictionary-entry-value\" className=\"text-sm font-medium\">\n {t('dictionaries.config.entries.dialog.valueLabel', 'Value')}\n <span className=\"ml-1 text-destructive\">*</span>\n </label>\n <Input\n id=\"dictionary-entry-value\"\n type=\"text\"\n value={formState.value}\n onChange={(event) => {\n const nextValue = event.target.value\n setFormState((prev) => ({ ...prev, value: nextValue }))\n if (errors.value) {\n setErrors((prev) => ({ ...prev, value: undefined }))\n }\n }}\n aria-required=\"true\"\n aria-invalid={errors.value ? 'true' : 'false'}\n aria-describedby={errors.value ? 'dictionary-entry-value-error' : undefined}\n />\n {errors.value ? (\n <p id=\"dictionary-entry-value-error\" className=\"text-xs text-destructive\">\n {errors.value}\n </p>\n ) : null}\n </div>\n <div className=\"space-y-2\">\n <label htmlFor=\"dictionary-entry-label\" className=\"text-sm font-medium\">\n {t('dictionaries.config.entries.dialog.labelLabel', 'Label')}\n </label>\n <Input\n id=\"dictionary-entry-label\"\n type=\"text\"\n value={formState.label}\n onChange={(event) => setFormState((prev) => ({ ...prev, label: event.target.value }))}\n placeholder={t('dictionaries.config.entries.dialog.labelPlaceholder', 'Display name shown in UI')}\n />\n </div>\n <AppearanceSelector\n icon={appearance.icon}\n color={appearance.color}\n previewLabel={formState.label.trim() || formState.value.trim()}\n onIconChange={(next) => {\n appearance.setIcon(next)\n setFormState((prev) => ({ ...prev, icon: next }))\n }}\n onColorChange={(next) => {\n appearance.setColor(next)\n setFormState((prev) => ({ ...prev, color: next }))\n }}\n labels={{\n colorLabel: t('dictionaries.config.entries.dialog.colorLabel', 'Color'),\n colorHelp: t('dictionaries.config.entries.dialog.colorHelp', 'Pick a highlight color for this entry.'),\n colorClearLabel: t('dictionaries.config.entries.dialog.colorClear', 'Remove color'),\n iconLabel: t('dictionaries.config.entries.dialog.iconLabel', 'Icon or emoji'),\n iconPlaceholder: t('dictionaries.config.entries.dialog.iconPlaceholder', 'Type an emoji or icon token.'),\n iconPickerTriggerLabel: t('dictionaries.config.entries.dialog.iconBrowse', 'Browse icons and emoji'),\n iconSearchPlaceholder: t('dictionaries.config.entries.dialog.iconSearchPlaceholder', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('dictionaries.config.entries.dialog.iconSearchEmpty', 'No icons match your search.'),\n iconSuggestionsLabel: t('dictionaries.config.entries.dialog.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('dictionaries.config.entries.dialog.iconClear', 'Remove icon'),\n previewEmptyLabel: t('dictionaries.config.entries.dialog.previewEmpty', 'No appearance selected'),\n }}\n />\n </div>\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={closeDialog}\n disabled={isSaving}\n >\n {t('dictionaries.config.entries.dialog.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={handleSave}\n disabled={isSaving}\n >\n {isSaving ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('dictionaries.config.entries.dialog.save', 'Save')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n <Dialog open={!!translateEntry} onOpenChange={(open) => { if (!open) setTranslateEntry(null) }}>\n <DialogContent className=\"max-w-2xl max-h-[80vh] overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{t('translations.manager.translateEntry', 'Translate: {{label}}', { label: translateEntry?.label ?? '' })}</DialogTitle>\n <DialogDescription>\n {t('translations.manager.translateEntryDescription', 'Manage translations for this dictionary entry.')}\n </DialogDescription>\n </DialogHeader>\n {translateEntry?.id && (\n <TranslationManager\n mode=\"embedded\"\n entityType=\"dictionaries:dictionary_entry\"\n recordId={translateEntry.id}\n baseValues={{ label: translateEntry.label }}\n translatableFields={['label']}\n />\n )}\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -91,6 +91,9 @@ const crud = makeCrudRoute({
|
|
|
91
91
|
entity: FeatureToggle,
|
|
92
92
|
idField: "id",
|
|
93
93
|
orgField: null,
|
|
94
|
+
// The query engine requires the caller's tenant context even when the
|
|
95
|
+
// underlying entity is global; the list below explicitly disables its
|
|
96
|
+
// automatic tenant predicate.
|
|
94
97
|
tenantField: "tenantId",
|
|
95
98
|
softDeleteField: "deletedAt"
|
|
96
99
|
},
|
|
@@ -98,6 +101,9 @@ const crud = makeCrudRoute({
|
|
|
98
101
|
list: {
|
|
99
102
|
schema: listQuerySchema,
|
|
100
103
|
entityId: E.feature_toggles.feature_toggle,
|
|
104
|
+
// FeatureToggle rows and their query-index projections are global
|
|
105
|
+
// (null/null scope), so filtering them by the actor's tenant hides them.
|
|
106
|
+
omitAutomaticTenantOrgScope: true,
|
|
101
107
|
fields: listFields,
|
|
102
108
|
sortFieldMap: {
|
|
103
109
|
id: "id",
|