@open-mercato/core 0.10.1-develop.7799.1.d9cd0c8e41 → 0.10.1-develop.7801.1.52db3dc0dd

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/customers/api/activities/route.js +20 -26
  3. package/dist/modules/customers/api/activities/route.js.map +2 -2
  4. package/dist/modules/customers/api/interactions/route.js +9 -19
  5. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  6. package/dist/modules/customers/api/people/[id]/route.js +4 -1
  7. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  8. package/dist/modules/customers/commands/interactions.js +26 -3
  9. package/dist/modules/customers/commands/interactions.js.map +2 -2
  10. package/dist/modules/customers/lib/interactionReadModel.js +5 -19
  11. package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
  12. package/dist/modules/customers/lib/interactionUsers.js +56 -0
  13. package/dist/modules/customers/lib/interactionUsers.js.map +7 -0
  14. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +21 -21
  15. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  16. package/package.json +7 -7
  17. package/src/modules/customers/api/activities/route.ts +23 -26
  18. package/src/modules/customers/api/interactions/route.ts +9 -21
  19. package/src/modules/customers/api/people/[id]/route.ts +4 -1
  20. package/src/modules/customers/commands/interactions.ts +33 -3
  21. package/src/modules/customers/lib/interactionReadModel.ts +5 -22
  22. package/src/modules/customers/lib/interactionUsers.ts +79 -0
  23. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +21 -24
@@ -3,7 +3,7 @@ import { loadCustomFieldValues } from "@open-mercato/shared/lib/crud/custom-fiel
3
3
  import { normalizeCustomFieldResponse } from "@open-mercato/shared/lib/custom-fields/normalize";
4
4
  import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
5
5
  import { CustomerDeal, CustomerEntity } from "../data/entities.js";
6
- import { User } from "@open-mercato/core/modules/auth/data/entities";
6
+ import { interactionUserKey, loadInteractionAuthors } from "./interactionUsers.js";
7
7
  import {
8
8
  CUSTOMER_INTERACTION_ENTITY_ID
9
9
  } from "./interactionCompatibility.js";
@@ -69,11 +69,6 @@ async function hydrateCanonicalInteractions({
69
69
  enrich = false
70
70
  }) {
71
71
  if (interactions.length === 0) return [];
72
- const authorIds = Array.from(
73
- new Set(
74
- interactions.map((interaction) => typeof interaction.authorUserId === "string" ? interaction.authorUserId : null).filter((value) => !!value)
75
- )
76
- );
77
72
  const dealIds = Array.from(
78
73
  new Set(
79
74
  interactions.map((interaction) => typeof interaction.dealId === "string" ? interaction.dealId : null).filter((value) => !!value)
@@ -81,8 +76,8 @@ async function hydrateCanonicalInteractions({
81
76
  );
82
77
  const tenantId = auth.tenantId ?? null;
83
78
  const organizationId = selectedOrganizationId ?? null;
84
- const [users, deals, customFieldValues] = await Promise.all([
85
- authorIds.length > 0 ? findWithDecryption(em, User, { id: { $in: authorIds } }, void 0, { tenantId, organizationId }) : Promise.resolve([]),
79
+ const [userMap, deals, customFieldValues] = await Promise.all([
80
+ loadInteractionAuthors(em, interactions),
86
81
  dealIds.length > 0 ? findWithDecryption(em, CustomerDeal, { id: { $in: dealIds } }, void 0, { tenantId, organizationId }) : Promise.resolve([]),
87
82
  loadCustomFieldValues({
88
83
  em,
@@ -93,15 +88,6 @@ async function hydrateCanonicalInteractions({
93
88
  tenantFallbacks: [auth.tenantId].filter((value) => !!value)
94
89
  })
95
90
  ]);
96
- const userMap = new Map(
97
- users.map((user) => [
98
- user.id,
99
- {
100
- name: user.name ?? null,
101
- email: user.email ?? null
102
- }
103
- ])
104
- );
105
91
  const dealMap = new Map(deals.map((deal) => [deal.id, deal.title]));
106
92
  const baseItems = interactions.map((interaction) => {
107
93
  const entityId = typeof interaction.entity === "string" ? interaction.entity : interaction.entity.id;
@@ -136,8 +122,8 @@ async function hydrateCanonicalInteractions({
136
122
  tenantId: interaction.tenantId,
137
123
  createdAt: interaction.createdAt.toISOString(),
138
124
  updatedAt: interaction.updatedAt.toISOString(),
139
- authorName: interaction.authorUserId ? userMap.get(interaction.authorUserId)?.name ?? null : null,
140
- authorEmail: interaction.authorUserId ? userMap.get(interaction.authorUserId)?.email ?? null : null,
125
+ authorName: interaction.authorUserId ? userMap.get(interactionUserKey(interaction, interaction.authorUserId))?.name ?? null : null,
126
+ authorEmail: interaction.authorUserId ? userMap.get(interactionUserKey(interaction, interaction.authorUserId))?.email ?? null : null,
141
127
  dealTitle: interaction.dealId ? dealMap.get(interaction.dealId) ?? null : null,
142
128
  customValues: normalizeInteractionCustomValues(customFieldValues[interaction.id])
143
129
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customers/lib/interactionReadModel.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { applyResponseEnrichers } from '@open-mercato/shared/lib/crud/enricher-runner'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { normalizeCustomFieldResponse } from '@open-mercato/shared/lib/custom-fields/normalize'\nimport type { EnricherContext } from '@open-mercato/shared/lib/crud/response-enricher'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CustomerDeal, CustomerEntity, CustomerInteraction } from '../data/entities'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport {\n CUSTOMER_INTERACTION_ENTITY_ID,\n type InteractionRecord,\n} from './interactionCompatibility'\n\ntype ContainerLike = {\n resolve: (name: string) => unknown\n}\n\ntype AuthLike = {\n tenantId: string | null\n orgId: string | null\n sub?: string | null\n userId?: string | null\n keyId?: string | null\n}\n\ntype RbacServiceLike = {\n getGrantedFeatures?: (\n userId: string,\n input: { tenantId: string | null; organizationId: string | null },\n ) => Promise<string[]>\n}\n\ntype HydrateCanonicalInteractionsInput = {\n em: EntityManager\n container: ContainerLike\n auth: AuthLike\n selectedOrganizationId: string | null\n interactions: CustomerInteraction[]\n enrich?: boolean\n}\n\ntype CustomerSummary = {\n id: string\n displayName: string | null\n kind: string | null\n}\n\nfunction resolveActorId(auth: AuthLike): string {\n if (typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub\n if (typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId\n if (typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId\n return 'system'\n}\n\nfunction mergeAdditiveRecord<T extends Record<string, unknown>>(base: T, candidate: T | undefined): T {\n if (!candidate) return base\n const additions = Object.fromEntries(\n Object.entries(candidate).filter(([key]) => !(key in base)),\n ) as Partial<T>\n return {\n ...base,\n ...additions,\n }\n}\n\nfunction normalizeInteractionCustomValues(values: Record<string, unknown> | null | undefined): Record<string, unknown> | null {\n return normalizeCustomFieldResponse(values) ?? null\n}\n\nasync function resolveUserFeatures(\n container: ContainerLike,\n userId: string,\n tenantId: string | null,\n organizationId: string | null,\n): Promise<string[] | undefined> {\n try {\n const rbac = container.resolve('rbacService') as RbacServiceLike | undefined\n if (!rbac?.getGrantedFeatures) return undefined\n return await rbac.getGrantedFeatures(userId, { tenantId, organizationId })\n } catch {\n return undefined\n }\n}\n\nexport async function buildCustomersInteractionEnricherContext(\n container: ContainerLike,\n auth: AuthLike,\n organizationId: string | null,\n): Promise<EnricherContext> {\n const userId = resolveActorId(auth)\n return {\n organizationId: organizationId ?? '',\n tenantId: auth.tenantId ?? '',\n userId,\n em: container.resolve('em'),\n container,\n userFeatures: await resolveUserFeatures(container, userId, auth.tenantId, organizationId),\n }\n}\n\nexport async function loadCustomerSummaries(\n em: EntityManager,\n entityIds: string[],\n tenantId?: string | null,\n organizationId?: string | null,\n): Promise<Map<string, CustomerSummary>> {\n if (!entityIds.length) return new Map()\n const entities = await findWithDecryption(em, CustomerEntity, { id: { $in: entityIds } }, undefined, { tenantId, organizationId })\n return new Map(\n entities.map((entity) => [\n entity.id,\n {\n id: entity.id,\n displayName: entity.displayName ?? null,\n kind: entity.kind ?? null,\n },\n ]),\n )\n}\n\nexport async function hydrateCanonicalInteractions({\n em,\n container,\n auth,\n selectedOrganizationId,\n interactions,\n enrich = false,\n}: HydrateCanonicalInteractionsInput): Promise<InteractionRecord[]> {\n if (interactions.length === 0) return []\n\n const authorIds = Array.from(\n new Set(\n interactions\n .map((interaction) =>\n typeof interaction.authorUserId === 'string' ? interaction.authorUserId : null)\n .filter((value): value is string => !!value),\n ),\n )\n const dealIds = Array.from(\n new Set(\n interactions\n .map((interaction) => (typeof interaction.dealId === 'string' ? interaction.dealId : null))\n .filter((value): value is string => !!value),\n ),\n )\n\n const tenantId = auth.tenantId ?? null\n const organizationId = selectedOrganizationId ?? null\n const [users, deals, customFieldValues] = await Promise.all([\n authorIds.length > 0 ? findWithDecryption(em, User, { id: { $in: authorIds } }, undefined, { tenantId, organizationId }) : Promise.resolve([]),\n dealIds.length > 0 ? findWithDecryption(em, CustomerDeal, { id: { $in: dealIds } }, undefined, { tenantId, organizationId }) : Promise.resolve([]),\n loadCustomFieldValues({\n em,\n entityId: CUSTOMER_INTERACTION_ENTITY_ID,\n recordIds: interactions.map((interaction) => interaction.id),\n tenantIdByRecord: Object.fromEntries(interactions.map((interaction) => [interaction.id, interaction.tenantId])),\n organizationIdByRecord: Object.fromEntries(interactions.map((interaction) => [interaction.id, interaction.organizationId])),\n tenantFallbacks: [auth.tenantId].filter((value): value is string => !!value),\n }),\n ])\n\n const userMap = new Map(\n users.map((user) => [\n user.id,\n {\n name: user.name ?? null,\n email: user.email ?? null,\n },\n ]),\n )\n const dealMap = new Map(deals.map((deal) => [deal.id, deal.title]))\n\n const baseItems: InteractionRecord[] = interactions.map((interaction) => {\n const entityId = typeof interaction.entity === 'string' ? interaction.entity : interaction.entity.id\n return {\n id: interaction.id,\n entityId,\n dealId: interaction.dealId ?? null,\n interactionType: interaction.interactionType,\n title: interaction.title ?? null,\n body: interaction.body ?? null,\n status: interaction.status,\n scheduledAt: interaction.scheduledAt ? interaction.scheduledAt.toISOString() : null,\n occurredAt: interaction.occurredAt ? interaction.occurredAt.toISOString() : null,\n priority: interaction.priority ?? null,\n authorUserId: interaction.authorUserId ?? null,\n ownerUserId: interaction.ownerUserId ?? null,\n appearanceIcon: interaction.appearanceIcon ?? null,\n appearanceColor: interaction.appearanceColor ?? null,\n source: interaction.source ?? null,\n duration: interaction.durationMinutes ?? null,\n timezone: interaction.timezone ?? null,\n location: interaction.location ?? null,\n allDay: interaction.allDay ?? null,\n recurrenceRule: interaction.recurrenceRule ?? null,\n recurrenceEnd: interaction.recurrenceEnd ? interaction.recurrenceEnd.toISOString() : null,\n participants: interaction.participants ?? null,\n reminderMinutes: interaction.reminderMinutes ?? null,\n visibility: interaction.visibility ?? null,\n linkedEntities: interaction.linkedEntities ?? null,\n guestPermissions: interaction.guestPermissions ?? null,\n organizationId: interaction.organizationId,\n tenantId: interaction.tenantId,\n createdAt: interaction.createdAt.toISOString(),\n updatedAt: interaction.updatedAt.toISOString(),\n authorName: interaction.authorUserId ? userMap.get(interaction.authorUserId)?.name ?? null : null,\n authorEmail: interaction.authorUserId ? userMap.get(interaction.authorUserId)?.email ?? null : null,\n dealTitle: interaction.dealId ? dealMap.get(interaction.dealId) ?? null : null,\n customValues: normalizeInteractionCustomValues(customFieldValues[interaction.id]),\n }\n })\n\n if (!enrich) return baseItems\n\n const enricherContext = await buildCustomersInteractionEnricherContext(\n container,\n auth,\n selectedOrganizationId,\n )\n const enriched = await applyResponseEnrichers(baseItems, 'customers.interaction', enricherContext)\n return baseItems.map((item, index) => mergeAdditiveRecord(item, enriched.items[index]))\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { applyResponseEnrichers } from '@open-mercato/shared/lib/crud/enricher-runner'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { normalizeCustomFieldResponse } from '@open-mercato/shared/lib/custom-fields/normalize'\nimport type { EnricherContext } from '@open-mercato/shared/lib/crud/response-enricher'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CustomerDeal, CustomerEntity, CustomerInteraction } from '../data/entities'\nimport { interactionUserKey, loadInteractionAuthors } from './interactionUsers'\nimport {\n CUSTOMER_INTERACTION_ENTITY_ID,\n type InteractionRecord,\n} from './interactionCompatibility'\n\ntype ContainerLike = {\n resolve: (name: string) => unknown\n}\n\ntype AuthLike = {\n tenantId: string | null\n orgId: string | null\n sub?: string | null\n userId?: string | null\n keyId?: string | null\n}\n\ntype RbacServiceLike = {\n getGrantedFeatures?: (\n userId: string,\n input: { tenantId: string | null; organizationId: string | null },\n ) => Promise<string[]>\n}\n\ntype HydrateCanonicalInteractionsInput = {\n em: EntityManager\n container: ContainerLike\n auth: AuthLike\n selectedOrganizationId: string | null\n interactions: CustomerInteraction[]\n enrich?: boolean\n}\n\ntype CustomerSummary = {\n id: string\n displayName: string | null\n kind: string | null\n}\n\nfunction resolveActorId(auth: AuthLike): string {\n if (typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub\n if (typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId\n if (typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId\n return 'system'\n}\n\nfunction mergeAdditiveRecord<T extends Record<string, unknown>>(base: T, candidate: T | undefined): T {\n if (!candidate) return base\n const additions = Object.fromEntries(\n Object.entries(candidate).filter(([key]) => !(key in base)),\n ) as Partial<T>\n return {\n ...base,\n ...additions,\n }\n}\n\nfunction normalizeInteractionCustomValues(values: Record<string, unknown> | null | undefined): Record<string, unknown> | null {\n return normalizeCustomFieldResponse(values) ?? null\n}\n\nasync function resolveUserFeatures(\n container: ContainerLike,\n userId: string,\n tenantId: string | null,\n organizationId: string | null,\n): Promise<string[] | undefined> {\n try {\n const rbac = container.resolve('rbacService') as RbacServiceLike | undefined\n if (!rbac?.getGrantedFeatures) return undefined\n return await rbac.getGrantedFeatures(userId, { tenantId, organizationId })\n } catch {\n return undefined\n }\n}\n\nexport async function buildCustomersInteractionEnricherContext(\n container: ContainerLike,\n auth: AuthLike,\n organizationId: string | null,\n): Promise<EnricherContext> {\n const userId = resolveActorId(auth)\n return {\n organizationId: organizationId ?? '',\n tenantId: auth.tenantId ?? '',\n userId,\n em: container.resolve('em'),\n container,\n userFeatures: await resolveUserFeatures(container, userId, auth.tenantId, organizationId),\n }\n}\n\nexport async function loadCustomerSummaries(\n em: EntityManager,\n entityIds: string[],\n tenantId?: string | null,\n organizationId?: string | null,\n): Promise<Map<string, CustomerSummary>> {\n if (!entityIds.length) return new Map()\n const entities = await findWithDecryption(em, CustomerEntity, { id: { $in: entityIds } }, undefined, { tenantId, organizationId })\n return new Map(\n entities.map((entity) => [\n entity.id,\n {\n id: entity.id,\n displayName: entity.displayName ?? null,\n kind: entity.kind ?? null,\n },\n ]),\n )\n}\n\nexport async function hydrateCanonicalInteractions({\n em,\n container,\n auth,\n selectedOrganizationId,\n interactions,\n enrich = false,\n}: HydrateCanonicalInteractionsInput): Promise<InteractionRecord[]> {\n if (interactions.length === 0) return []\n\n const dealIds = Array.from(\n new Set(\n interactions\n .map((interaction) => (typeof interaction.dealId === 'string' ? interaction.dealId : null))\n .filter((value): value is string => !!value),\n ),\n )\n\n const tenantId = auth.tenantId ?? null\n const organizationId = selectedOrganizationId ?? null\n const [userMap, deals, customFieldValues] = await Promise.all([\n loadInteractionAuthors(em, interactions),\n dealIds.length > 0 ? findWithDecryption(em, CustomerDeal, { id: { $in: dealIds } }, undefined, { tenantId, organizationId }) : Promise.resolve([]),\n loadCustomFieldValues({\n em,\n entityId: CUSTOMER_INTERACTION_ENTITY_ID,\n recordIds: interactions.map((interaction) => interaction.id),\n tenantIdByRecord: Object.fromEntries(interactions.map((interaction) => [interaction.id, interaction.tenantId])),\n organizationIdByRecord: Object.fromEntries(interactions.map((interaction) => [interaction.id, interaction.organizationId])),\n tenantFallbacks: [auth.tenantId].filter((value): value is string => !!value),\n }),\n ])\n\n const dealMap = new Map(deals.map((deal) => [deal.id, deal.title]))\n\n const baseItems: InteractionRecord[] = interactions.map((interaction) => {\n const entityId = typeof interaction.entity === 'string' ? interaction.entity : interaction.entity.id\n return {\n id: interaction.id,\n entityId,\n dealId: interaction.dealId ?? null,\n interactionType: interaction.interactionType,\n title: interaction.title ?? null,\n body: interaction.body ?? null,\n status: interaction.status,\n scheduledAt: interaction.scheduledAt ? interaction.scheduledAt.toISOString() : null,\n occurredAt: interaction.occurredAt ? interaction.occurredAt.toISOString() : null,\n priority: interaction.priority ?? null,\n authorUserId: interaction.authorUserId ?? null,\n ownerUserId: interaction.ownerUserId ?? null,\n appearanceIcon: interaction.appearanceIcon ?? null,\n appearanceColor: interaction.appearanceColor ?? null,\n source: interaction.source ?? null,\n duration: interaction.durationMinutes ?? null,\n timezone: interaction.timezone ?? null,\n location: interaction.location ?? null,\n allDay: interaction.allDay ?? null,\n recurrenceRule: interaction.recurrenceRule ?? null,\n recurrenceEnd: interaction.recurrenceEnd ? interaction.recurrenceEnd.toISOString() : null,\n participants: interaction.participants ?? null,\n reminderMinutes: interaction.reminderMinutes ?? null,\n visibility: interaction.visibility ?? null,\n linkedEntities: interaction.linkedEntities ?? null,\n guestPermissions: interaction.guestPermissions ?? null,\n organizationId: interaction.organizationId,\n tenantId: interaction.tenantId,\n createdAt: interaction.createdAt.toISOString(),\n updatedAt: interaction.updatedAt.toISOString(),\n authorName: interaction.authorUserId ? userMap.get(interactionUserKey(interaction, interaction.authorUserId))?.name ?? null : null,\n authorEmail: interaction.authorUserId ? userMap.get(interactionUserKey(interaction, interaction.authorUserId))?.email ?? null : null,\n dealTitle: interaction.dealId ? dealMap.get(interaction.dealId) ?? null : null,\n customValues: normalizeInteractionCustomValues(customFieldValues[interaction.id]),\n }\n })\n\n if (!enrich) return baseItems\n\n const enricherContext = await buildCustomersInteractionEnricherContext(\n container,\n auth,\n selectedOrganizationId,\n )\n const enriched = await applyResponseEnrichers(baseItems, 'customers.interaction', enricherContext)\n return baseItems.map((item, index) => mergeAdditiveRecord(item, enriched.items[index]))\n}\n"],
5
+ "mappings": "AACA,SAAS,8BAA8B;AACvC,SAAS,6BAA6B;AACtC,SAAS,oCAAoC;AAE7C,SAAS,0BAA0B;AACnC,SAAS,cAAc,sBAA2C;AAClE,SAAS,oBAAoB,8BAA8B;AAC3D;AAAA,EACE;AAAA,OAEK;AAoCP,SAAS,eAAe,MAAwB;AAC9C,MAAI,OAAO,KAAK,QAAQ,YAAY,KAAK,IAAI,KAAK,EAAE,SAAS,EAAG,QAAO,KAAK;AAC5E,MAAI,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,KAAK,EAAE,SAAS,EAAG,QAAO,KAAK;AAClF,MAAI,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,KAAK,EAAE,SAAS,EAAG,QAAO,KAAK;AAChF,SAAO;AACT;AAEA,SAAS,oBAAuD,MAAS,WAA6B;AACpG,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,YAAY,OAAO;AAAA,IACvB,OAAO,QAAQ,SAAS,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,EAAE,OAAO,KAAK;AAAA,EAC5D;AACA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AACF;AAEA,SAAS,iCAAiC,QAAoF;AAC5H,SAAO,6BAA6B,MAAM,KAAK;AACjD;AAEA,eAAe,oBACb,WACA,QACA,UACA,gBAC+B;AAC/B,MAAI;AACF,UAAM,OAAO,UAAU,QAAQ,aAAa;AAC5C,QAAI,CAAC,MAAM,mBAAoB,QAAO;AACtC,WAAO,MAAM,KAAK,mBAAmB,QAAQ,EAAE,UAAU,eAAe,CAAC;AAAA,EAC3E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,yCACpB,WACA,MACA,gBAC0B;AAC1B,QAAM,SAAS,eAAe,IAAI;AAClC,SAAO;AAAA,IACL,gBAAgB,kBAAkB;AAAA,IAClC,UAAU,KAAK,YAAY;AAAA,IAC3B;AAAA,IACA,IAAI,UAAU,QAAQ,IAAI;AAAA,IAC1B;AAAA,IACA,cAAc,MAAM,oBAAoB,WAAW,QAAQ,KAAK,UAAU,cAAc;AAAA,EAC1F;AACF;AAEA,eAAsB,sBACpB,IACA,WACA,UACA,gBACuC;AACvC,MAAI,CAAC,UAAU,OAAQ,QAAO,oBAAI,IAAI;AACtC,QAAM,WAAW,MAAM,mBAAmB,IAAI,gBAAgB,EAAE,IAAI,EAAE,KAAK,UAAU,EAAE,GAAG,QAAW,EAAE,UAAU,eAAe,CAAC;AACjI,SAAO,IAAI;AAAA,IACT,SAAS,IAAI,CAAC,WAAW;AAAA,MACvB,OAAO;AAAA,MACP;AAAA,QACE,IAAI,OAAO;AAAA,QACX,aAAa,OAAO,eAAe;AAAA,QACnC,MAAM,OAAO,QAAQ;AAAA,MACvB;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEA,eAAsB,6BAA6B;AAAA,EACjD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS;AACX,GAAoE;AAClE,MAAI,aAAa,WAAW,EAAG,QAAO,CAAC;AAEvC,QAAM,UAAU,MAAM;AAAA,IACpB,IAAI;AAAA,MACF,aACG,IAAI,CAAC,gBAAiB,OAAO,YAAY,WAAW,WAAW,YAAY,SAAS,IAAK,EACzF,OAAO,CAAC,UAA2B,CAAC,CAAC,KAAK;AAAA,IAC/C;AAAA,EACF;AAEA,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,iBAAiB,0BAA0B;AACjD,QAAM,CAAC,SAAS,OAAO,iBAAiB,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC5D,uBAAuB,IAAI,YAAY;AAAA,IACvC,QAAQ,SAAS,IAAI,mBAAmB,IAAI,cAAc,EAAE,IAAI,EAAE,KAAK,QAAQ,EAAE,GAAG,QAAW,EAAE,UAAU,eAAe,CAAC,IAAI,QAAQ,QAAQ,CAAC,CAAC;AAAA,IACjJ,sBAAsB;AAAA,MACpB;AAAA,MACA,UAAU;AAAA,MACV,WAAW,aAAa,IAAI,CAAC,gBAAgB,YAAY,EAAE;AAAA,MAC3D,kBAAkB,OAAO,YAAY,aAAa,IAAI,CAAC,gBAAgB,CAAC,YAAY,IAAI,YAAY,QAAQ,CAAC,CAAC;AAAA,MAC9G,wBAAwB,OAAO,YAAY,aAAa,IAAI,CAAC,gBAAgB,CAAC,YAAY,IAAI,YAAY,cAAc,CAAC,CAAC;AAAA,MAC1H,iBAAiB,CAAC,KAAK,QAAQ,EAAE,OAAO,CAAC,UAA2B,CAAC,CAAC,KAAK;AAAA,IAC7E,CAAC;AAAA,EACH,CAAC;AAED,QAAM,UAAU,IAAI,IAAI,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC;AAElE,QAAM,YAAiC,aAAa,IAAI,CAAC,gBAAgB;AACvE,UAAM,WAAW,OAAO,YAAY,WAAW,WAAW,YAAY,SAAS,YAAY,OAAO;AAClG,WAAO;AAAA,MACL,IAAI,YAAY;AAAA,MAChB;AAAA,MACA,QAAQ,YAAY,UAAU;AAAA,MAC9B,iBAAiB,YAAY;AAAA,MAC7B,OAAO,YAAY,SAAS;AAAA,MAC5B,MAAM,YAAY,QAAQ;AAAA,MAC1B,QAAQ,YAAY;AAAA,MACpB,aAAa,YAAY,cAAc,YAAY,YAAY,YAAY,IAAI;AAAA,MAC/E,YAAY,YAAY,aAAa,YAAY,WAAW,YAAY,IAAI;AAAA,MAC5E,UAAU,YAAY,YAAY;AAAA,MAClC,cAAc,YAAY,gBAAgB;AAAA,MAC1C,aAAa,YAAY,eAAe;AAAA,MACxC,gBAAgB,YAAY,kBAAkB;AAAA,MAC9C,iBAAiB,YAAY,mBAAmB;AAAA,MAChD,QAAQ,YAAY,UAAU;AAAA,MAC9B,UAAU,YAAY,mBAAmB;AAAA,MACzC,UAAU,YAAY,YAAY;AAAA,MAClC,UAAU,YAAY,YAAY;AAAA,MAClC,QAAQ,YAAY,UAAU;AAAA,MAC9B,gBAAgB,YAAY,kBAAkB;AAAA,MAC9C,eAAe,YAAY,gBAAgB,YAAY,cAAc,YAAY,IAAI;AAAA,MACrF,cAAc,YAAY,gBAAgB;AAAA,MAC1C,iBAAiB,YAAY,mBAAmB;AAAA,MAChD,YAAY,YAAY,cAAc;AAAA,MACtC,gBAAgB,YAAY,kBAAkB;AAAA,MAC9C,kBAAkB,YAAY,oBAAoB;AAAA,MAClD,gBAAgB,YAAY;AAAA,MAC5B,UAAU,YAAY;AAAA,MACtB,WAAW,YAAY,UAAU,YAAY;AAAA,MAC7C,WAAW,YAAY,UAAU,YAAY;AAAA,MAC7C,YAAY,YAAY,eAAe,QAAQ,IAAI,mBAAmB,aAAa,YAAY,YAAY,CAAC,GAAG,QAAQ,OAAO;AAAA,MAC9H,aAAa,YAAY,eAAe,QAAQ,IAAI,mBAAmB,aAAa,YAAY,YAAY,CAAC,GAAG,SAAS,OAAO;AAAA,MAChI,WAAW,YAAY,SAAS,QAAQ,IAAI,YAAY,MAAM,KAAK,OAAO;AAAA,MAC1E,cAAc,iCAAiC,kBAAkB,YAAY,EAAE,CAAC;AAAA,IAClF;AAAA,EACF,CAAC;AAED,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,kBAAkB,MAAM;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,WAAW,MAAM,uBAAuB,WAAW,yBAAyB,eAAe;AACjG,SAAO,UAAU,IAAI,CAAC,MAAM,UAAU,oBAAoB,MAAM,SAAS,MAAM,KAAK,CAAC,CAAC;AACxF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,56 @@
1
+ import { User } from "@open-mercato/core/modules/auth/data/entities";
2
+ import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
3
+ import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
5
+ function interactionUserKey(scope, userId) {
6
+ return `${scope.tenantId ?? ""}:${scope.organizationId ?? ""}:${userId}`;
7
+ }
8
+ async function findInteractionUserInScope(em, userId, scope, options = {}) {
9
+ if (!scope.tenantId || !scope.organizationId) return null;
10
+ return findOneWithDecryption(em, User, {
11
+ id: userId,
12
+ tenantId: scope.tenantId,
13
+ ...options.includeDeleted ? {} : { deletedAt: null }
14
+ }, void 0, scope);
15
+ }
16
+ async function requireInteractionUsersInScope(em, scope, references, previousReferences = {}) {
17
+ for (const field of ["authorUserId", "ownerUserId"]) {
18
+ const userId = references[field];
19
+ if (!userId) continue;
20
+ if (await findInteractionUserInScope(em, userId, scope, { includeDeleted: userId === previousReferences[field] })) continue;
21
+ const { translate } = await resolveTranslations();
22
+ throw new CrudHttpError(400, { error: translate("auth.users.form.errors.notFound", "User not found") });
23
+ }
24
+ }
25
+ async function loadInteractionAuthors(em, references) {
26
+ const groups = /* @__PURE__ */ new Map();
27
+ for (const reference of references) {
28
+ if (!reference.tenantId || !reference.organizationId || !reference.authorUserId) continue;
29
+ const key = interactionUserKey(reference, "");
30
+ let group = groups.get(key);
31
+ if (!group) {
32
+ group = { tenantId: reference.tenantId, organizationId: reference.organizationId, userIds: /* @__PURE__ */ new Set() };
33
+ groups.set(key, group);
34
+ }
35
+ group.userIds.add(reference.authorUserId);
36
+ }
37
+ const profiles = /* @__PURE__ */ new Map();
38
+ await Promise.all(Array.from(groups.values(), async ({ tenantId, organizationId, userIds }) => {
39
+ const scope = { tenantId, organizationId };
40
+ const users = await findWithDecryption(em, User, {
41
+ id: { $in: Array.from(userIds) },
42
+ tenantId
43
+ }, void 0, scope);
44
+ for (const user of users) {
45
+ profiles.set(interactionUserKey(scope, user.id), { name: user.name ?? null, email: user.email ?? null });
46
+ }
47
+ }));
48
+ return profiles;
49
+ }
50
+ export {
51
+ findInteractionUserInScope,
52
+ interactionUserKey,
53
+ loadInteractionAuthors,
54
+ requireInteractionUsersInScope
55
+ };
56
+ //# sourceMappingURL=interactionUsers.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customers/lib/interactionUsers.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\n\ntype InteractionUserScope = {\n tenantId?: string | null\n organizationId?: string | null\n}\n\ntype InteractionUserReferences = {\n authorUserId?: string | null\n ownerUserId?: string | null\n}\n\ntype AuthorProfile = { name: string | null; email: string | null }\n\nexport function interactionUserKey(scope: InteractionUserScope, userId: string): string {\n return `${scope.tenantId ?? ''}:${scope.organizationId ?? ''}:${userId}`\n}\n\nexport async function findInteractionUserInScope(\n em: EntityManager,\n userId: string,\n scope: InteractionUserScope,\n options: { includeDeleted?: boolean } = {},\n): Promise<User | null> {\n if (!scope.tenantId || !scope.organizationId) return null\n return findOneWithDecryption(em, User, {\n id: userId,\n tenantId: scope.tenantId,\n ...(options.includeDeleted ? {} : { deletedAt: null }),\n }, undefined, scope)\n}\n\nexport async function requireInteractionUsersInScope(\n em: EntityManager,\n scope: InteractionUserScope,\n references: InteractionUserReferences,\n previousReferences: InteractionUserReferences = {},\n): Promise<void> {\n for (const field of ['authorUserId', 'ownerUserId'] as const) {\n const userId = references[field]\n if (!userId) continue\n if (await findInteractionUserInScope(em, userId, scope, { includeDeleted: userId === previousReferences[field] })) continue\n const { translate } = await resolveTranslations()\n throw new CrudHttpError(400, { error: translate('auth.users.form.errors.notFound', 'User not found') })\n }\n}\n\nexport async function loadInteractionAuthors(\n em: EntityManager,\n references: Array<InteractionUserScope & { authorUserId?: string | null }>,\n): Promise<Map<string, AuthorProfile>> {\n const groups = new Map<string, { tenantId: string; organizationId: string; userIds: Set<string> }>()\n for (const reference of references) {\n if (!reference.tenantId || !reference.organizationId || !reference.authorUserId) continue\n const key = interactionUserKey(reference, '')\n let group = groups.get(key)\n if (!group) {\n group = { tenantId: reference.tenantId, organizationId: reference.organizationId, userIds: new Set() }\n groups.set(key, group)\n }\n group.userIds.add(reference.authorUserId)\n }\n const profiles = new Map<string, AuthorProfile>()\n await Promise.all(Array.from(groups.values(), async ({ tenantId, organizationId, userIds }) => {\n const scope = { tenantId, organizationId }\n const users = await findWithDecryption(em, User, {\n id: { $in: Array.from(userIds) },\n tenantId,\n }, undefined, scope)\n for (const user of users) {\n profiles.set(interactionUserKey(scope, user.id), { name: user.name ?? null, email: user.email ?? null })\n }\n }))\n return profiles\n}\n"],
5
+ "mappings": "AACA,SAAS,YAAY;AACrB,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,2BAA2B;AAc7B,SAAS,mBAAmB,OAA6B,QAAwB;AACtF,SAAO,GAAG,MAAM,YAAY,EAAE,IAAI,MAAM,kBAAkB,EAAE,IAAI,MAAM;AACxE;AAEA,eAAsB,2BACpB,IACA,QACA,OACA,UAAwC,CAAC,GACnB;AACtB,MAAI,CAAC,MAAM,YAAY,CAAC,MAAM,eAAgB,QAAO;AACrD,SAAO,sBAAsB,IAAI,MAAM;AAAA,IACrC,IAAI;AAAA,IACJ,UAAU,MAAM;AAAA,IAChB,GAAI,QAAQ,iBAAiB,CAAC,IAAI,EAAE,WAAW,KAAK;AAAA,EACtD,GAAG,QAAW,KAAK;AACrB;AAEA,eAAsB,+BACpB,IACA,OACA,YACA,qBAAgD,CAAC,GAClC;AACf,aAAW,SAAS,CAAC,gBAAgB,aAAa,GAAY;AAC5D,UAAM,SAAS,WAAW,KAAK;AAC/B,QAAI,CAAC,OAAQ;AACb,QAAI,MAAM,2BAA2B,IAAI,QAAQ,OAAO,EAAE,gBAAgB,WAAW,mBAAmB,KAAK,EAAE,CAAC,EAAG;AACnH,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,mCAAmC,gBAAgB,EAAE,CAAC;AAAA,EACxG;AACF;AAEA,eAAsB,uBACpB,IACA,YACqC;AACrC,QAAM,SAAS,oBAAI,IAAgF;AACnG,aAAW,aAAa,YAAY;AAClC,QAAI,CAAC,UAAU,YAAY,CAAC,UAAU,kBAAkB,CAAC,UAAU,aAAc;AACjF,UAAM,MAAM,mBAAmB,WAAW,EAAE;AAC5C,QAAI,QAAQ,OAAO,IAAI,GAAG;AAC1B,QAAI,CAAC,OAAO;AACV,cAAQ,EAAE,UAAU,UAAU,UAAU,gBAAgB,UAAU,gBAAgB,SAAS,oBAAI,IAAI,EAAE;AACrG,aAAO,IAAI,KAAK,KAAK;AAAA,IACvB;AACA,UAAM,QAAQ,IAAI,UAAU,YAAY;AAAA,EAC1C;AACA,QAAM,WAAW,oBAAI,IAA2B;AAChD,QAAM,QAAQ,IAAI,MAAM,KAAK,OAAO,OAAO,GAAG,OAAO,EAAE,UAAU,gBAAgB,QAAQ,MAAM;AAC7F,UAAM,QAAQ,EAAE,UAAU,eAAe;AACzC,UAAM,QAAQ,MAAM,mBAAmB,IAAI,MAAM;AAAA,MAC/C,IAAI,EAAE,KAAK,MAAM,KAAK,OAAO,EAAE;AAAA,MAC/B;AAAA,IACF,GAAG,QAAW,KAAK;AACnB,eAAW,QAAQ,OAAO;AACxB,eAAS,IAAI,mBAAmB,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,QAAQ,MAAM,OAAO,KAAK,SAAS,KAAK,CAAC;AAAA,IACzG;AAAA,EACF,CAAC,CAAC;AACF,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -56,7 +56,6 @@ function RecordsPageInner({ params }) {
56
56
  const [pageSize, setPageSize] = React.useState(50);
57
57
  const [search, setSearch] = React.useState("");
58
58
  const [filterValues, setFilterValues] = React.useState({});
59
- const [columns, setColumns] = React.useState([]);
60
59
  const [rawData, setRawData] = React.useState([]);
61
60
  const [total, setTotal] = React.useState(0);
62
61
  const [totalPages, setTotalPages] = React.useState(1);
@@ -69,10 +68,30 @@ function RecordsPageInner({ params }) {
69
68
  contextId: deleteMutationContextId,
70
69
  blockedMessage: t("ui.forms.flash.saveBlocked", "Save blocked by validation")
71
70
  });
72
- const { data: cfDefs = [] } = useCustomFieldDefs(entityId, {
71
+ const { data: customFieldDefs } = useCustomFieldDefs(entityId, {
73
72
  enabled: Boolean(entityId),
74
73
  keyExtras: [scopeVersion]
75
74
  });
75
+ const cfDefs = React.useMemo(() => customFieldDefs ?? [], [customFieldDefs]);
76
+ const columns = React.useMemo(() => {
77
+ const visibleDefs = filterCustomFieldDefs(cfDefs, "list");
78
+ const maxVisible = 10;
79
+ const cols = visibleDefs.map((d, idx) => ({
80
+ accessorKey: d.key,
81
+ header: d.label || d.key,
82
+ meta: { priority: idx < 4 ? 1 : idx < 6 ? 2 : idx < 8 ? 3 : idx < maxVisible ? 4 : 5 },
83
+ cell: ({ getValue }) => {
84
+ const v = getValue();
85
+ if (d.kind === "relation") {
86
+ return /* @__PURE__ */ jsx(RelationCell, { fieldKey: d.key, value: v, fallbackText: normalizeCell(v) });
87
+ }
88
+ return /* @__PURE__ */ jsx("span", { className: "inline-block max-w-60 truncate align-top", title: normalizeCell(v), children: normalizeCell(v) });
89
+ }
90
+ }));
91
+ const hasIdCol = cols.some((c) => c.accessorKey === "id" || c.id === "id");
92
+ if (!hasIdCol) cols.unshift({ accessorKey: "id", header: "ID", meta: { hidden: true, priority: 6 } });
93
+ return cols;
94
+ }, [cfDefs]);
76
95
  const relationDefinitions = React.useMemo(
77
96
  () => filterCustomFieldDefs(cfDefs, "list").filter(
78
97
  (definition) => definition.kind === "relation"
@@ -147,25 +166,6 @@ function RecordsPageInner({ params }) {
147
166
  cancelled = true;
148
167
  };
149
168
  }, [entityId, page, pageSize, sorting, filterValues, scopeVersion, search, searchableFields]);
150
- React.useEffect(() => {
151
- const visibleDefs = filterCustomFieldDefs(cfDefs, "list");
152
- const maxVisible = 10;
153
- const cols = visibleDefs.map((d, idx) => ({
154
- accessorKey: d.key,
155
- header: d.label || d.key,
156
- meta: { priority: idx < 4 ? 1 : idx < 6 ? 2 : idx < 8 ? 3 : idx < maxVisible ? 4 : 5 },
157
- cell: ({ getValue }) => {
158
- const v = getValue();
159
- if (d.kind === "relation") {
160
- return /* @__PURE__ */ jsx(RelationCell, { fieldKey: d.key, value: v, fallbackText: normalizeCell(v) });
161
- }
162
- return /* @__PURE__ */ jsx("span", { className: "inline-block max-w-60 truncate align-top", title: normalizeCell(v), children: normalizeCell(v) });
163
- }
164
- }));
165
- const hasIdCol = cols.some((c) => c.accessorKey === "id" || c.id === "id");
166
- if (!hasIdCol) cols.unshift({ accessorKey: "id", header: "ID", meta: { hidden: true, priority: 6 } });
167
- setColumns(cols);
168
- }, [cfDefs]);
169
169
  const data = rawData;
170
170
  const viewExportColumns = React.useMemo(() => {
171
171
  return (columns || []).map((col) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/entities/backend/entities/user/%5BentityId%5D/records/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { resolveExtensionPointPattern } from '@open-mercato/shared/modules/widgets/extension-points'\nimport { extensionPoints } from '@open-mercato/core/modules/entities/extension-points'\nimport { useSearchParams } from 'next/navigation'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { filterCustomFieldDefs, useCustomFieldDefs, type CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable, type DataTableExportFormat } from '@open-mercato/ui/backend/DataTable'\nimport type { PreparedExport } from '@open-mercato/shared/lib/crud/exporters'\nimport type { FilterDef } from '@open-mercato/ui/backend/FilterBar'\nimport { ContextHelp } from '@open-mercato/ui/backend/ContextHelp'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport Link from 'next/link'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { useRecordsEntityGuard } from '@open-mercato/core/modules/entities/components/useRecordsEntityGuard'\nimport {\n RelationCell,\n RelationDisplaysProvider,\n useRecordListRelationDisplays,\n} from '@open-mercato/core/modules/entities/components/RecordListRelationCell'\n\ntype RecordsResponse = {\n items: any[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n totalIsCapped?: boolean\n}\n\nfunction toCsvUrl(base: string, params: URLSearchParams) {\n // Build a relative URL to avoid SSR/CSR origin mismatch hydration issues\n const p = new URLSearchParams(params)\n p.set('format', 'csv')\n const qs = p.toString()\n return qs ? `${base}?${qs}` : base\n}\n\nfunction normalizeCell(v: any): string {\n if (Array.isArray(v)) return v.filter((x) => x != null && x !== '').join(', ')\n if (v === true) return 'Yes'\n if (v === false) return 'No'\n if (v == null) return ''\n if (v instanceof Date) return v.toISOString()\n return String(v)\n}\n\nexport default function RecordsPage({ params }: { params: { entityId?: string } }) {\n const t = useT()\n const entityId = decodeURIComponent(params?.entityId || '')\n const guard = useRecordsEntityGuard(entityId)\n if (guard !== 'allowed') {\n return (\n <Page>\n <PageBody>\n {guard === 'blocked' ? (\n <ErrorMessage label={t('entities.userEntities.records.errors.systemEntity', 'This entity is system-managed. Records are available for custom entities only.')} />\n ) : (\n <LoadingMessage label={t('entities.userEntities.records.loading', 'Loading records...')} />\n )}\n </PageBody>\n </Page>\n )\n }\n return <RecordsPageInner params={params} />\n}\n\nfunction RecordsPageInner({ params }: { params: { entityId?: string } }) {\n const t = useT()\n const entityId = decodeURIComponent(params?.entityId || '')\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'id', desc: false }])\n const [page, setPage] = React.useState(1)\n const [pageSize, setPageSize] = React.useState(50)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<Record<string, any>>({})\n const [columns, setColumns] = React.useState<ColumnDef<any>[]>([])\n const [rawData, setRawData] = React.useState<any[]>([])\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [totalIsCapped, setTotalIsCapped] = React.useState(false)\n const [loading, setLoading] = React.useState(false)\n const scopeVersion = useOrganizationScopeVersion()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const deleteMutationContextId = `entities.user.records.${entityId}:single-delete`\n const { runMutation: runDeleteMutation, retryLastMutation: retryDeleteMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: deleteMutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const { data: cfDefs = [] } = useCustomFieldDefs(entityId, {\n enabled: Boolean(entityId),\n keyExtras: [scopeVersion],\n })\n const relationDefinitions = React.useMemo(\n () => filterCustomFieldDefs(cfDefs, 'list').filter(\n (definition): definition is CustomFieldDefDto => definition.kind === 'relation',\n ),\n [cfDefs],\n )\n const relationDisplaysByField = useRecordListRelationDisplays(relationDefinitions, rawData)\n\n // Fields searched server-side: every visible column plus the base `id`.\n const searchableFields = React.useMemo(() => {\n const fields = (columns || [])\n .map((col) => (col as any).accessorKey)\n .filter((key): key is string => typeof key === 'string' && key.length > 0)\n return Array.from(new Set(['id', ...fields]))\n }, [columns])\n\n // Fetch records whenever paging/sorting/filters/search change. Search is applied\n // server-side (before pagination) so totals and exports stay consistent (#3229).\n React.useEffect(() => {\n let cancelled = false\n const run = async () => {\n setLoading(true)\n try {\n const params = new URLSearchParams()\n params.set('entityId', entityId)\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n const trimmedSearch = search.trim()\n if (trimmedSearch) {\n params.set('search', trimmedSearch)\n if (searchableFields.length) params.set('searchFields', searchableFields.join(','))\n }\n const s = sorting?.[0]\n if (s?.id) {\n params.set('sortField', String(s.id))\n params.set('sortDir', s.desc ? 'desc' : 'asc')\n }\n // Flatten filter values into query params\n for (const [k, v] of Object.entries(filterValues)) {\n if (v == null) continue\n if (Array.isArray(v)) {\n if (v.length) params.set(k, v.join(','))\n } else if (typeof v === 'object') {\n // dateRange-like shapes are not supported generically here; skip\n } else {\n params.set(k, String(v))\n }\n }\n const j = await readApiResultOrThrow<RecordsResponse>(\n `/api/entities/records?${params.toString()}`,\n undefined,\n {\n errorMessage: 'Failed to load records',\n fallback: {\n items: [],\n total: 0,\n page,\n pageSize,\n totalPages: 1,\n },\n },\n )\n if (!cancelled) {\n setRawData(j.items || [])\n setTotal(j.total)\n setTotalPages(j.totalPages)\n setTotalIsCapped(j?.totalIsCapped === true)\n }\n } catch (e) {\n if (!cancelled) {\n setRawData([])\n setTotal(0)\n setTotalPages(1)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n if (entityId) run()\n return () => { cancelled = true }\n }, [entityId, page, pageSize, sorting, filterValues, scopeVersion, search, searchableFields])\n\n // Build columns from custom field definitions only (no data round-trip)\n React.useEffect(() => {\n const visibleDefs = filterCustomFieldDefs(cfDefs, 'list') as any\n const maxVisible = 10\n const cols: ColumnDef<any>[] = visibleDefs.map((d: any, idx: number) => ({\n accessorKey: d.key,\n header: d.label || d.key,\n meta: { priority: idx < 4 ? 1 : idx < 6 ? 2 : idx < 8 ? 3 : idx < maxVisible ? 4 : 5 },\n cell: ({ getValue }: { getValue: () => unknown }) => {\n const v = getValue() as any\n if (d.kind === 'relation') {\n return <RelationCell fieldKey={d.key} value={v} fallbackText={normalizeCell(v)} />\n }\n return <span className=\"inline-block max-w-60 truncate align-top\" title={normalizeCell(v)}>{normalizeCell(v)}</span>\n },\n }))\n // Ensure hidden 'id' column exists for sorting/state\n const hasIdCol = cols.some((c) => (c as any).accessorKey === 'id' || (c as any).id === 'id')\n if (!hasIdCol) cols.unshift({ accessorKey: 'id', header: 'ID', meta: { hidden: true, priority: 6 } } as any)\n setColumns(cols)\n }, [cfDefs])\n\n // Search is server-side (see fetch effect); render the fetched page as-is so\n // pagination totals and exports stay consistent with the active search (#3229).\n const data = rawData\n\n const viewExportColumns = React.useMemo(() => {\n return (columns || [])\n .map((col) => {\n const accessorKey = (col as any).accessorKey\n if (!accessorKey || typeof accessorKey !== 'string') return null\n if ((col as any).meta?.hidden) return null\n const header = typeof col.header === 'string'\n ? col.header\n : accessorKey.startsWith('cf_')\n ? accessorKey.slice(3)\n : accessorKey\n return { field: accessorKey, header }\n })\n .filter((col): col is { field: string; header: string } => !!col)\n }, [columns])\n\n const buildFullExportUrl = React.useCallback((format: DataTableExportFormat) => {\n const qp = new URLSearchParams({\n entityId,\n format,\n exportScope: 'full',\n all: 'true',\n })\n const sort = sorting?.[0]\n if (sort?.id) {\n qp.set('sortField', String(sort.id))\n qp.set('sortDir', sort.desc ? 'desc' : 'asc')\n }\n const trimmedSearch = search.trim()\n if (trimmedSearch) {\n qp.set('search', trimmedSearch)\n if (searchableFields.length) qp.set('searchFields', searchableFields.join(','))\n }\n return `/api/entities/records?${qp.toString()}`\n }, [entityId, sorting, search, searchableFields])\n\n const exportConfig = React.useMemo(() => {\n const safeEntityId = entityId.replace(/[^a-z0-9_-]/gi, '_') || 'records'\n return {\n view: {\n description: 'Exports the current list respecting filters and column visibility.',\n prepare: async (): Promise<{ prepared: PreparedExport; filename: string }> => {\n const rowsForExport = data.map((row) => {\n const out: Record<string, unknown> = {}\n for (const col of viewExportColumns) {\n out[col.field] = (row as Record<string, unknown>)[col.field]\n }\n return out\n })\n const prepared: PreparedExport = {\n columns: viewExportColumns.map((col) => ({ field: col.field, header: col.header })),\n rows: rowsForExport,\n }\n return { prepared, filename: `${safeEntityId}_view` }\n },\n },\n full: {\n description: 'Exports raw records with every field and custom field included.',\n getUrl: (format: DataTableExportFormat) => buildFullExportUrl(format),\n filename: () => `${safeEntityId}_full`,\n },\n }\n }, [buildFullExportUrl, data, entityId, viewExportColumns])\n\n const hasAnyFormFields = React.useMemo(() => filterCustomFieldDefs(cfDefs, 'form').length > 0, [cfDefs])\n const actions = (\n <>\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href={`/backend/entities/user/${encodeURIComponent(entityId)}`}>\n Edit Entity Definition\n </Link>\n </Button>\n {hasAnyFormFields && (\n <Button asChild>\n <Link href={`/backend/entities/user/${encodeURIComponent(entityId)}/records/create`}>\n Create\n </Link>\n </Button>\n )}\n </>\n )\n\n // Ensure filters are visible even if no custom fields are marked filterable\n const baseFilters: FilterDef[] = React.useMemo(() => ([\n { id: 'id', label: 'ID', type: 'text' },\n ]), [])\n\n return (\n <Page>\n <PageBody>\n <ContextHelp bulb title=\"API: Manage Records via cURL\" className=\"mb-4\">\n <p className=\"mb-2\">\n Interact with this custom entity via the backend API using cURL. Use API keys for machine-to-machine access\u2014mint one from the{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/user-guide/api-keys\">\n Managing API keys guide\n </a>{' '}\n or the{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/cli/api-keys\">\n API keys CLI documentation\n </a>{' '}\n before running these calls.\n </p>\n <div className=\"space-y-2\">\n <div>\n <div className=\"font-medium mb-1\">1) Configure environment variables</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`export BASE_URL=\"http://localhost:3000/api\"\nexport API_KEY=\"<paste API key secret here>\" # scoped with entities.features\nexport ENTITY_ID=\"${entityId}\"\nexport RECORD_ID=\"<record uuid>\"`}</code></pre>\n <p className=\"text-muted-foreground mt-1\">\n Need a new key? Follow the{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/user-guide/api-keys\">\n Managing API keys\n </a>{' '}\n walkthrough or mint one via{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/cli/api-keys\">\n mercato api_keys add\n </a>\n .\n </p>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">2) List records</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -H \"X-Api-Key: $API_KEY\" \\\n \"$BASE_URL/entities/records?entityId=$ENTITY_ID\" | jq`}</code></pre>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">3) Read a single record (by id)</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -H \"X-Api-Key: $API_KEY\" \\\n \"$BASE_URL/entities/records?entityId=$ENTITY_ID&id=$RECORD_ID\" | jq`}</code></pre>\n <p className=\"text-muted-foreground mt-1\">Note: Response is a list; filter by <code>id</code> to get a single item.</p>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">4) Create a record</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -X POST \\\n -H \"X-Api-Key: $API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\\\"entityId\\\\\": \\\\\"$ENTITY_ID\\\\\",\n \\\\\"values\\\\\": {\n \\\\\"field_one\\\\\": \\\\\"Example\\\\\",\n \\\\\"field_two\\\\\": 123\n }\n }\" \\\n \"$BASE_URL/entities/records\" | jq`}</code></pre>\n <p className=\"text-muted-foreground mt-1\">For custom entities, send field keys without the <code>cf_</code> prefix. The API normalizes this server-side.</p>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">5) Update a record</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -X PUT \\\n -H \"X-Api-Key: $API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\\\"entityId\\\\\": \\\\\"$ENTITY_ID\\\\\",\n \\\\\"recordId\\\\\": \\\\\"$RECORD_ID\\\\\",\n \\\\\"values\\\\\": {\n \\\\\"field_one\\\\\": \\\\\"Updated\\\\\"\n }\n }\" \\\n \"$BASE_URL/entities/records\" | jq`}</code></pre>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">6) Delete a record</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -X DELETE \\\n -H \"X-Api-Key: $API_KEY\" \\\n \"$BASE_URL/entities/records?entityId=$ENTITY_ID&recordId=$RECORD_ID\" | jq`}</code></pre>\n </div>\n\n <div className=\"text-muted-foreground\">\n Security notes:\n <ul className=\"list-disc pl-5 mt-1 space-y-1\">\n <li>All endpoints require a valid API key. Keys inherit tenant, organization, and feature scope.</li>\n <li>Rotate keys regularly and delete unused ones in the admin UI.</li>\n <li>Store the secret in a secure vault; anyone with the header can act within the key&apos;s permissions.</li>\n </ul>\n </div>\n </div>\n </ContextHelp>\n <RelationDisplaysProvider displaysByField={relationDisplaysByField}>\n <DataTable\n stickyActionsColumn\n title={`Records: ${entityId}`}\n titleHeadingLevel={1}\n entityId={entityId}\n actions={actions}\n columns={columns}\n data={data}\n perspective={{\n tableId: resolveExtensionPointPattern(extensionPoints.hosts.userRecordsTable.pattern, { entityId }),\n }}\n exporter={exportConfig}\n filters={baseFilters}\n filterValues={filterValues}\n rowActions={(row) => (\n <RowActions\n items={[\n { id: 'edit', label: 'Edit', href: `/backend/entities/user/${encodeURIComponent(entityId)}/records/${encodeURIComponent(String((row as any).id))}` },\n { id: 'delete', label: 'Delete', destructive: true, onSelect: async () => {\n const recordId = String((row as any).id)\n try {\n const confirmed = await confirm({\n title: 'Delete this record?',\n variant: 'destructive',\n })\n if (!confirmed) return\n await runDeleteMutation({\n operation: async () => {\n const deleteCall = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader((row as any).updatedAt),\n () => apiCall(\n `/api/entities/records?entityId=${encodeURIComponent(entityId)}&recordId=${encodeURIComponent(recordId)}`,\n { method: 'DELETE' },\n ),\n )\n if (!deleteCall.ok) {\n await raiseCrudError(deleteCall.response, 'Failed to delete record')\n }\n },\n context: {\n formId: deleteMutationContextId,\n resourceKind: 'entities.record',\n resourceId: recordId,\n retryLastMutation: retryDeleteMutation,\n },\n })\n const j = await readApiResultOrThrow<RecordsResponse>(\n `/api/entities/records?entityId=${encodeURIComponent(entityId)}&page=${page}&pageSize=${pageSize}`,\n undefined,\n {\n errorMessage: 'Failed to reload records',\n fallback: { items: [], total: 0, page, pageSize, totalPages: 1 },\n },\n )\n setRawData(j.items || [])\n setTotal(j.total || 0)\n setTotalPages(j.totalPages || 1)\n flash('Record has been removed', 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Failed to delete record'\n flash(message, 'error')\n }\n } },\n ]}\n />\n )}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n searchValue={search}\n onSearchChange={(v) => { setSearch(v); setPage(1) }}\n onFiltersApply={(vals) => { setFilterValues(vals); setPage(1) }}\n onFiltersClear={() => { setFilterValues({}); setPage(1) }}\n pagination={{ page, pageSize, total, totalPages, totalIsCapped, onPageChange: setPage }}\n isLoading={loading}\n />\n </RelationDisplaysProvider>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { resolveExtensionPointPattern } from '@open-mercato/shared/modules/widgets/extension-points'\nimport { extensionPoints } from '@open-mercato/core/modules/entities/extension-points'\nimport { useSearchParams } from 'next/navigation'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { filterCustomFieldDefs, useCustomFieldDefs, type CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable, type DataTableExportFormat } from '@open-mercato/ui/backend/DataTable'\nimport type { PreparedExport } from '@open-mercato/shared/lib/crud/exporters'\nimport type { FilterDef } from '@open-mercato/ui/backend/FilterBar'\nimport { ContextHelp } from '@open-mercato/ui/backend/ContextHelp'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport Link from 'next/link'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { useRecordsEntityGuard } from '@open-mercato/core/modules/entities/components/useRecordsEntityGuard'\nimport {\n RelationCell,\n RelationDisplaysProvider,\n useRecordListRelationDisplays,\n} from '@open-mercato/core/modules/entities/components/RecordListRelationCell'\n\ntype RecordsResponse = {\n items: any[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n totalIsCapped?: boolean\n}\n\nfunction toCsvUrl(base: string, params: URLSearchParams) {\n // Build a relative URL to avoid SSR/CSR origin mismatch hydration issues\n const p = new URLSearchParams(params)\n p.set('format', 'csv')\n const qs = p.toString()\n return qs ? `${base}?${qs}` : base\n}\n\nfunction normalizeCell(v: any): string {\n if (Array.isArray(v)) return v.filter((x) => x != null && x !== '').join(', ')\n if (v === true) return 'Yes'\n if (v === false) return 'No'\n if (v == null) return ''\n if (v instanceof Date) return v.toISOString()\n return String(v)\n}\n\nexport default function RecordsPage({ params }: { params: { entityId?: string } }) {\n const t = useT()\n const entityId = decodeURIComponent(params?.entityId || '')\n const guard = useRecordsEntityGuard(entityId)\n if (guard !== 'allowed') {\n return (\n <Page>\n <PageBody>\n {guard === 'blocked' ? (\n <ErrorMessage label={t('entities.userEntities.records.errors.systemEntity', 'This entity is system-managed. Records are available for custom entities only.')} />\n ) : (\n <LoadingMessage label={t('entities.userEntities.records.loading', 'Loading records...')} />\n )}\n </PageBody>\n </Page>\n )\n }\n return <RecordsPageInner params={params} />\n}\n\nfunction RecordsPageInner({ params }: { params: { entityId?: string } }) {\n const t = useT()\n const entityId = decodeURIComponent(params?.entityId || '')\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'id', desc: false }])\n const [page, setPage] = React.useState(1)\n const [pageSize, setPageSize] = React.useState(50)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<Record<string, any>>({})\n const [rawData, setRawData] = React.useState<any[]>([])\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [totalIsCapped, setTotalIsCapped] = React.useState(false)\n const [loading, setLoading] = React.useState(false)\n const scopeVersion = useOrganizationScopeVersion()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const deleteMutationContextId = `entities.user.records.${entityId}:single-delete`\n const { runMutation: runDeleteMutation, retryLastMutation: retryDeleteMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: deleteMutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const { data: customFieldDefs } = useCustomFieldDefs(entityId, {\n enabled: Boolean(entityId),\n keyExtras: [scopeVersion],\n })\n const cfDefs = React.useMemo(() => customFieldDefs ?? [], [customFieldDefs])\n const columns = React.useMemo<ColumnDef<any>[]>(() => {\n const visibleDefs = filterCustomFieldDefs(cfDefs, 'list') as any\n const maxVisible = 10\n const cols: ColumnDef<any>[] = visibleDefs.map((d: any, idx: number) => ({\n accessorKey: d.key,\n header: d.label || d.key,\n meta: { priority: idx < 4 ? 1 : idx < 6 ? 2 : idx < 8 ? 3 : idx < maxVisible ? 4 : 5 },\n cell: ({ getValue }: { getValue: () => unknown }) => {\n const v = getValue() as any\n if (d.kind === 'relation') {\n return <RelationCell fieldKey={d.key} value={v} fallbackText={normalizeCell(v)} />\n }\n return <span className=\"inline-block max-w-60 truncate align-top\" title={normalizeCell(v)}>{normalizeCell(v)}</span>\n },\n }))\n const hasIdCol = cols.some((c) => (c as any).accessorKey === 'id' || (c as any).id === 'id')\n if (!hasIdCol) cols.unshift({ accessorKey: 'id', header: 'ID', meta: { hidden: true, priority: 6 } } as any)\n return cols\n }, [cfDefs])\n const relationDefinitions = React.useMemo(\n () => filterCustomFieldDefs(cfDefs, 'list').filter(\n (definition): definition is CustomFieldDefDto => definition.kind === 'relation',\n ),\n [cfDefs],\n )\n const relationDisplaysByField = useRecordListRelationDisplays(relationDefinitions, rawData)\n\n // Fields searched server-side: every visible column plus the base `id`.\n const searchableFields = React.useMemo(() => {\n const fields = (columns || [])\n .map((col) => (col as any).accessorKey)\n .filter((key): key is string => typeof key === 'string' && key.length > 0)\n return Array.from(new Set(['id', ...fields]))\n }, [columns])\n\n // Fetch records whenever paging/sorting/filters/search change. Search is applied\n // server-side (before pagination) so totals and exports stay consistent (#3229).\n React.useEffect(() => {\n let cancelled = false\n const run = async () => {\n setLoading(true)\n try {\n const params = new URLSearchParams()\n params.set('entityId', entityId)\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n const trimmedSearch = search.trim()\n if (trimmedSearch) {\n params.set('search', trimmedSearch)\n if (searchableFields.length) params.set('searchFields', searchableFields.join(','))\n }\n const s = sorting?.[0]\n if (s?.id) {\n params.set('sortField', String(s.id))\n params.set('sortDir', s.desc ? 'desc' : 'asc')\n }\n // Flatten filter values into query params\n for (const [k, v] of Object.entries(filterValues)) {\n if (v == null) continue\n if (Array.isArray(v)) {\n if (v.length) params.set(k, v.join(','))\n } else if (typeof v === 'object') {\n // dateRange-like shapes are not supported generically here; skip\n } else {\n params.set(k, String(v))\n }\n }\n const j = await readApiResultOrThrow<RecordsResponse>(\n `/api/entities/records?${params.toString()}`,\n undefined,\n {\n errorMessage: 'Failed to load records',\n fallback: {\n items: [],\n total: 0,\n page,\n pageSize,\n totalPages: 1,\n },\n },\n )\n if (!cancelled) {\n setRawData(j.items || [])\n setTotal(j.total)\n setTotalPages(j.totalPages)\n setTotalIsCapped(j?.totalIsCapped === true)\n }\n } catch (e) {\n if (!cancelled) {\n setRawData([])\n setTotal(0)\n setTotalPages(1)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n if (entityId) run()\n return () => { cancelled = true }\n }, [entityId, page, pageSize, sorting, filterValues, scopeVersion, search, searchableFields])\n\n // Search is server-side (see fetch effect); render the fetched page as-is so\n // pagination totals and exports stay consistent with the active search (#3229).\n const data = rawData\n\n const viewExportColumns = React.useMemo(() => {\n return (columns || [])\n .map((col) => {\n const accessorKey = (col as any).accessorKey\n if (!accessorKey || typeof accessorKey !== 'string') return null\n if ((col as any).meta?.hidden) return null\n const header = typeof col.header === 'string'\n ? col.header\n : accessorKey.startsWith('cf_')\n ? accessorKey.slice(3)\n : accessorKey\n return { field: accessorKey, header }\n })\n .filter((col): col is { field: string; header: string } => !!col)\n }, [columns])\n\n const buildFullExportUrl = React.useCallback((format: DataTableExportFormat) => {\n const qp = new URLSearchParams({\n entityId,\n format,\n exportScope: 'full',\n all: 'true',\n })\n const sort = sorting?.[0]\n if (sort?.id) {\n qp.set('sortField', String(sort.id))\n qp.set('sortDir', sort.desc ? 'desc' : 'asc')\n }\n const trimmedSearch = search.trim()\n if (trimmedSearch) {\n qp.set('search', trimmedSearch)\n if (searchableFields.length) qp.set('searchFields', searchableFields.join(','))\n }\n return `/api/entities/records?${qp.toString()}`\n }, [entityId, sorting, search, searchableFields])\n\n const exportConfig = React.useMemo(() => {\n const safeEntityId = entityId.replace(/[^a-z0-9_-]/gi, '_') || 'records'\n return {\n view: {\n description: 'Exports the current list respecting filters and column visibility.',\n prepare: async (): Promise<{ prepared: PreparedExport; filename: string }> => {\n const rowsForExport = data.map((row) => {\n const out: Record<string, unknown> = {}\n for (const col of viewExportColumns) {\n out[col.field] = (row as Record<string, unknown>)[col.field]\n }\n return out\n })\n const prepared: PreparedExport = {\n columns: viewExportColumns.map((col) => ({ field: col.field, header: col.header })),\n rows: rowsForExport,\n }\n return { prepared, filename: `${safeEntityId}_view` }\n },\n },\n full: {\n description: 'Exports raw records with every field and custom field included.',\n getUrl: (format: DataTableExportFormat) => buildFullExportUrl(format),\n filename: () => `${safeEntityId}_full`,\n },\n }\n }, [buildFullExportUrl, data, entityId, viewExportColumns])\n\n const hasAnyFormFields = React.useMemo(() => filterCustomFieldDefs(cfDefs, 'form').length > 0, [cfDefs])\n const actions = (\n <>\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href={`/backend/entities/user/${encodeURIComponent(entityId)}`}>\n Edit Entity Definition\n </Link>\n </Button>\n {hasAnyFormFields && (\n <Button asChild>\n <Link href={`/backend/entities/user/${encodeURIComponent(entityId)}/records/create`}>\n Create\n </Link>\n </Button>\n )}\n </>\n )\n\n // Ensure filters are visible even if no custom fields are marked filterable\n const baseFilters: FilterDef[] = React.useMemo(() => ([\n { id: 'id', label: 'ID', type: 'text' },\n ]), [])\n\n return (\n <Page>\n <PageBody>\n <ContextHelp bulb title=\"API: Manage Records via cURL\" className=\"mb-4\">\n <p className=\"mb-2\">\n Interact with this custom entity via the backend API using cURL. Use API keys for machine-to-machine access\u2014mint one from the{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/user-guide/api-keys\">\n Managing API keys guide\n </a>{' '}\n or the{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/cli/api-keys\">\n API keys CLI documentation\n </a>{' '}\n before running these calls.\n </p>\n <div className=\"space-y-2\">\n <div>\n <div className=\"font-medium mb-1\">1) Configure environment variables</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`export BASE_URL=\"http://localhost:3000/api\"\nexport API_KEY=\"<paste API key secret here>\" # scoped with entities.features\nexport ENTITY_ID=\"${entityId}\"\nexport RECORD_ID=\"<record uuid>\"`}</code></pre>\n <p className=\"text-muted-foreground mt-1\">\n Need a new key? Follow the{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/user-guide/api-keys\">\n Managing API keys\n </a>{' '}\n walkthrough or mint one via{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/cli/api-keys\">\n mercato api_keys add\n </a>\n .\n </p>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">2) List records</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -H \"X-Api-Key: $API_KEY\" \\\n \"$BASE_URL/entities/records?entityId=$ENTITY_ID\" | jq`}</code></pre>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">3) Read a single record (by id)</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -H \"X-Api-Key: $API_KEY\" \\\n \"$BASE_URL/entities/records?entityId=$ENTITY_ID&id=$RECORD_ID\" | jq`}</code></pre>\n <p className=\"text-muted-foreground mt-1\">Note: Response is a list; filter by <code>id</code> to get a single item.</p>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">4) Create a record</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -X POST \\\n -H \"X-Api-Key: $API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\\\"entityId\\\\\": \\\\\"$ENTITY_ID\\\\\",\n \\\\\"values\\\\\": {\n \\\\\"field_one\\\\\": \\\\\"Example\\\\\",\n \\\\\"field_two\\\\\": 123\n }\n }\" \\\n \"$BASE_URL/entities/records\" | jq`}</code></pre>\n <p className=\"text-muted-foreground mt-1\">For custom entities, send field keys without the <code>cf_</code> prefix. The API normalizes this server-side.</p>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">5) Update a record</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -X PUT \\\n -H \"X-Api-Key: $API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\\\"entityId\\\\\": \\\\\"$ENTITY_ID\\\\\",\n \\\\\"recordId\\\\\": \\\\\"$RECORD_ID\\\\\",\n \\\\\"values\\\\\": {\n \\\\\"field_one\\\\\": \\\\\"Updated\\\\\"\n }\n }\" \\\n \"$BASE_URL/entities/records\" | jq`}</code></pre>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">6) Delete a record</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -X DELETE \\\n -H \"X-Api-Key: $API_KEY\" \\\n \"$BASE_URL/entities/records?entityId=$ENTITY_ID&recordId=$RECORD_ID\" | jq`}</code></pre>\n </div>\n\n <div className=\"text-muted-foreground\">\n Security notes:\n <ul className=\"list-disc pl-5 mt-1 space-y-1\">\n <li>All endpoints require a valid API key. Keys inherit tenant, organization, and feature scope.</li>\n <li>Rotate keys regularly and delete unused ones in the admin UI.</li>\n <li>Store the secret in a secure vault; anyone with the header can act within the key&apos;s permissions.</li>\n </ul>\n </div>\n </div>\n </ContextHelp>\n <RelationDisplaysProvider displaysByField={relationDisplaysByField}>\n <DataTable\n stickyActionsColumn\n title={`Records: ${entityId}`}\n titleHeadingLevel={1}\n entityId={entityId}\n actions={actions}\n columns={columns}\n data={data}\n perspective={{\n tableId: resolveExtensionPointPattern(extensionPoints.hosts.userRecordsTable.pattern, { entityId }),\n }}\n exporter={exportConfig}\n filters={baseFilters}\n filterValues={filterValues}\n rowActions={(row) => (\n <RowActions\n items={[\n { id: 'edit', label: 'Edit', href: `/backend/entities/user/${encodeURIComponent(entityId)}/records/${encodeURIComponent(String((row as any).id))}` },\n { id: 'delete', label: 'Delete', destructive: true, onSelect: async () => {\n const recordId = String((row as any).id)\n try {\n const confirmed = await confirm({\n title: 'Delete this record?',\n variant: 'destructive',\n })\n if (!confirmed) return\n await runDeleteMutation({\n operation: async () => {\n const deleteCall = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader((row as any).updatedAt),\n () => apiCall(\n `/api/entities/records?entityId=${encodeURIComponent(entityId)}&recordId=${encodeURIComponent(recordId)}`,\n { method: 'DELETE' },\n ),\n )\n if (!deleteCall.ok) {\n await raiseCrudError(deleteCall.response, 'Failed to delete record')\n }\n },\n context: {\n formId: deleteMutationContextId,\n resourceKind: 'entities.record',\n resourceId: recordId,\n retryLastMutation: retryDeleteMutation,\n },\n })\n const j = await readApiResultOrThrow<RecordsResponse>(\n `/api/entities/records?entityId=${encodeURIComponent(entityId)}&page=${page}&pageSize=${pageSize}`,\n undefined,\n {\n errorMessage: 'Failed to reload records',\n fallback: { items: [], total: 0, page, pageSize, totalPages: 1 },\n },\n )\n setRawData(j.items || [])\n setTotal(j.total || 0)\n setTotalPages(j.totalPages || 1)\n flash('Record has been removed', 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Failed to delete record'\n flash(message, 'error')\n }\n } },\n ]}\n />\n )}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n searchValue={search}\n onSearchChange={(v) => { setSearch(v); setPage(1) }}\n onFiltersApply={(vals) => { setFilterValues(vals); setPage(1) }}\n onFiltersClear={() => { setFilterValues({}); setPage(1) }}\n pagination={{ page, pageSize, total, totalPages, totalIsCapped, onPageChange: setPage }}\n isLoading={loading}\n />\n </RelationDisplaysProvider>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["params"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.10.1-develop.7799.1.d9cd0c8e41",
3
+ "version": "0.10.1-develop.7801.1.52db3dc0dd",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -266,16 +266,16 @@
266
266
  "zod": "^4.4.3"
267
267
  },
268
268
  "peerDependencies": {
269
- "@open-mercato/ai-assistant": "0.10.1-develop.7799.1.d9cd0c8e41",
270
- "@open-mercato/shared": "0.10.1-develop.7799.1.d9cd0c8e41",
271
- "@open-mercato/ui": "0.10.1-develop.7799.1.d9cd0c8e41",
269
+ "@open-mercato/ai-assistant": "0.10.1-develop.7801.1.52db3dc0dd",
270
+ "@open-mercato/shared": "0.10.1-develop.7801.1.52db3dc0dd",
271
+ "@open-mercato/ui": "0.10.1-develop.7801.1.52db3dc0dd",
272
272
  "react": "^19.0.0",
273
273
  "react-dom": "^19.0.0"
274
274
  },
275
275
  "devDependencies": {
276
- "@open-mercato/ai-assistant": "0.10.1-develop.7799.1.d9cd0c8e41",
277
- "@open-mercato/shared": "0.10.1-develop.7799.1.d9cd0c8e41",
278
- "@open-mercato/ui": "0.10.1-develop.7799.1.d9cd0c8e41",
276
+ "@open-mercato/ai-assistant": "0.10.1-develop.7801.1.52db3dc0dd",
277
+ "@open-mercato/shared": "0.10.1-develop.7801.1.52db3dc0dd",
278
+ "@open-mercato/ui": "0.10.1-develop.7801.1.52db3dc0dd",
279
279
  "@testing-library/dom": "^10.4.1",
280
280
  "@testing-library/jest-dom": "^7.0.1",
281
281
  "@testing-library/react": "^16.3.3",
@@ -17,7 +17,7 @@ import {
17
17
  import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
18
18
  import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
19
19
  import { CustomerActivity, CustomerDeal, CustomerInteraction } from '../../data/entities'
20
- import { User } from '@open-mercato/core/modules/auth/data/entities'
20
+ import { interactionUserKey, loadInteractionAuthors } from '../../lib/interactionUsers'
21
21
  import { activityCreateSchema, activityUpdateSchema } from '../../data/validators'
22
22
  import { createCustomersCrudOpenApi, createPagedListResponseSchema, defaultOkResponseSchema } from '../openapi'
23
23
  import {
@@ -212,16 +212,13 @@ export async function decorateActivityItems(
212
212
  em: EntityManager,
213
213
  items: ActivityItem[],
214
214
  decryptionScope?: { tenantId: string; organizationId: string },
215
+ authorScopesById?: ReadonlyMap<string, { tenantId: string; organizationId: string }>,
215
216
  ): Promise<ActivityItem[]> {
216
217
  if (items.length === 0) return items
217
218
 
218
- const authorIds = Array.from(
219
- new Set(
220
- items
221
- .map((item) => (typeof item.authorUserId === 'string' ? item.authorUserId : null))
222
- .filter((value): value is string => !!value),
223
- ),
224
- )
219
+ const resolveAuthorScope = (itemId: string) => authorScopesById
220
+ ? authorScopesById.get(itemId) ?? {}
221
+ : decryptionScope ?? {}
225
222
  const dealIds = Array.from(
226
223
  new Set(
227
224
  items
@@ -237,8 +234,11 @@ export async function decorateActivityItems(
237
234
  })
238
235
  }
239
236
 
240
- const [users, deals] = await Promise.all([
241
- authorIds.length > 0 ? em.find(User, { id: { $in: authorIds } }) : Promise.resolve([]),
237
+ const [userMap, deals] = await Promise.all([
238
+ loadInteractionAuthors(em, items.map((item) => ({
239
+ ...resolveAuthorScope(item.id),
240
+ authorUserId: item.authorUserId,
241
+ }))),
242
242
  dealIds.length > 0 && decryptionScope
243
243
  ? findWithDecryption(
244
244
  em,
@@ -250,24 +250,20 @@ export async function decorateActivityItems(
250
250
  : Promise.resolve([]),
251
251
  ])
252
252
 
253
- const userMap = new Map(
254
- users.map((user) => [
255
- user.id,
256
- {
257
- name: user.name ?? null,
258
- email: user.email ?? null,
259
- },
260
- ]),
261
- )
262
253
  const dealMap = new Map(deals.map((deal) => [deal.id, deal.title]))
263
254
 
264
- return items.map((item) => ({
265
- ...item,
266
- activityTypeLabel: item.activityType,
267
- authorName: item.authorUserId ? userMap.get(item.authorUserId)?.name ?? null : null,
268
- authorEmail: item.authorUserId ? userMap.get(item.authorUserId)?.email ?? null : null,
269
- dealTitle: item.dealId ? dealMap.get(item.dealId) ?? null : null,
270
- }))
255
+ return items.map((item) => {
256
+ const author = item.authorUserId
257
+ ? userMap.get(interactionUserKey(resolveAuthorScope(item.id), item.authorUserId))
258
+ : null
259
+ return {
260
+ ...item,
261
+ activityTypeLabel: item.activityType,
262
+ authorName: author?.name ?? null,
263
+ authorEmail: author?.email ?? null,
264
+ dealTitle: item.dealId ? dealMap.get(item.dealId) ?? null : null,
265
+ }
266
+ })
271
267
  }
272
268
 
273
269
  function mapLegacyActivity(activity: CustomerActivity): ActivityItem {
@@ -438,6 +434,7 @@ async function listLegacyActivities(
438
434
  em,
439
435
  rows.map(mapLegacyActivity),
440
436
  selectedOrganizationId ? { tenantId, organizationId: selectedOrganizationId } : undefined,
437
+ new Map(rows.map((row) => [row.id, { tenantId: row.tenantId, organizationId: row.organizationId }])),
441
438
  ),
442
439
  total,
443
440
  }
@@ -18,7 +18,7 @@ import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/d
18
18
  import { resolveOrganizationScopeFilter } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'
19
19
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
20
20
  import { CustomerDeal, CustomerInteraction } from '../../data/entities'
21
- import { User } from '@open-mercato/core/modules/auth/data/entities'
21
+ import { interactionUserKey, loadInteractionAuthors } from '../../lib/interactionUsers'
22
22
  import { interactionCreateSchema, interactionUpdateSchema } from '../../data/validators'
23
23
  import { parseScopedCommandInput } from '../utils'
24
24
  import {
@@ -614,13 +614,6 @@ export async function GET(req: Request) {
614
614
  hasMore = rows.length > query.limit
615
615
  }
616
616
 
617
- const authorIds = Array.from(
618
- new Set(
619
- pageRows
620
- .map((row) => (typeof row.author_user_id === 'string' ? row.author_user_id : null))
621
- .filter((value): value is string => !!value),
622
- ),
623
- )
624
617
  const dealIds = Array.from(
625
618
  new Set(
626
619
  pageRows
@@ -644,8 +637,12 @@ export async function GET(req: Request) {
644
637
  new Set(pageRows.map((row) => row.organization_id).filter((value): value is string => !!value)),
645
638
  )
646
639
 
647
- const [users, deals, customFieldValues, interactionRecords, encryptedFieldsByOrganization] = await Promise.all([
648
- authorIds.length > 0 ? findWithDecryption(em, User, { id: { $in: authorIds } }, undefined, { tenantId: auth.tenantId, organizationId: selectedOrganizationId }) : Promise.resolve([]),
640
+ const [userMap, deals, customFieldValues, interactionRecords, encryptedFieldsByOrganization] = await Promise.all([
641
+ loadInteractionAuthors(em, pageRows.map((row) => ({
642
+ tenantId: row.tenant_id,
643
+ organizationId: row.organization_id,
644
+ authorUserId: row.author_user_id,
645
+ }))),
649
646
  dealIds.length > 0 ? findWithDecryption(em, CustomerDeal, { id: { $in: dealIds } }, undefined, { tenantId: auth.tenantId, organizationId: selectedOrganizationId }) : Promise.resolve([]),
650
647
  interactionIds.length > 0
651
648
  ? loadCustomFieldValues({
@@ -673,15 +670,6 @@ export async function GET(req: Request) {
673
670
  })(),
674
671
  ])
675
672
 
676
- const userMap = new Map(
677
- users.map((user) => [
678
- user.id,
679
- {
680
- name: user.name ?? null,
681
- email: user.email ?? null,
682
- },
683
- ]),
684
- )
685
673
  const dealMap = new Map(
686
674
  deals.map((deal) => [deal.id, deal.title]),
687
675
  )
@@ -731,8 +719,8 @@ export async function GET(req: Request) {
731
719
  tenantId: row.tenant_id,
732
720
  createdAt: toIsoString(row.created_at) ?? new Date().toISOString(),
733
721
  updatedAt: toIsoString(row.updated_at) ?? new Date().toISOString(),
734
- authorName: row.author_user_id ? userMap.get(row.author_user_id)?.name ?? null : null,
735
- authorEmail: row.author_user_id ? userMap.get(row.author_user_id)?.email ?? null : null,
722
+ authorName: row.author_user_id ? userMap.get(interactionUserKey({ tenantId: row.tenant_id, organizationId: row.organization_id }, row.author_user_id))?.name ?? null : null,
723
+ authorEmail: row.author_user_id ? userMap.get(interactionUserKey({ tenantId: row.tenant_id, organizationId: row.organization_id }, row.author_user_id))?.email ?? null : null,
736
724
  dealTitle: row.deal_id ? dealMap.get(row.deal_id) ?? null : null,
737
725
  customValues: normalizeCustomFieldResponse(customFieldValues[row.id]) ?? null,
738
726
  }, interactionRecordMap.get(row.id), encryptedFieldsByOrganization.get(row.organization_id) ?? []))