@open-mercato/core 0.10.1-develop.7836.1.0ef0f453c6 → 0.10.1-develop.7841.1.af87ac614d
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/customer_accounts/api/admin/roles.js +25 -2
- package/dist/modules/customer_accounts/api/admin/roles.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users.js +24 -7
- package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js +77 -15
- package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +12 -1
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
- package/dist/modules/customer_accounts/data/validators.js +2 -1
- package/dist/modules/customer_accounts/data/validators.js.map +2 -2
- package/dist/modules/customer_accounts/lib/adminOrganizationScope.js +46 -0
- package/dist/modules/customer_accounts/lib/adminOrganizationScope.js.map +7 -0
- package/dist/modules/customers/api/customer-dictionary-entries/route.js +86 -0
- package/dist/modules/customers/api/customer-dictionary-entries/route.js.map +7 -0
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +29 -7
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/cli.js +28 -110
- package/dist/modules/customers/cli.js.map +3 -3
- package/dist/modules/customers/components/DictionarySettings.js +1 -1
- package/dist/modules/customers/components/DictionarySettings.js.map +1 -1
- package/dist/modules/customers/components/detail/ManageTagsDialog.js +1 -1
- package/dist/modules/customers/components/detail/ManageTagsDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/TaskDialog.js +14 -1
- package/dist/modules/customers/components/detail/TaskDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/TaskForm.js +1 -16
- package/dist/modules/customers/components/detail/TaskForm.js.map +2 -2
- package/dist/modules/customers/components/detail/TasksSection.js +38 -10
- package/dist/modules/customers/components/detail/TasksSection.js.map +2 -2
- package/dist/modules/customers/components/detail/hooks/useCustomerDictionary.js +12 -5
- package/dist/modules/customers/components/detail/hooks/useCustomerDictionary.js.map +2 -2
- package/dist/modules/customers/components/detail/hooks/useInteractions.js +3 -13
- package/dist/modules/customers/components/detail/hooks/useInteractions.js.map +2 -2
- package/dist/modules/customers/lib/dictionaryDefaults.js +134 -0
- package/dist/modules/customers/lib/dictionaryDefaults.js.map +7 -0
- package/dist/modules/customers/lib/dictionaryLabels.js +59 -0
- package/dist/modules/customers/lib/dictionaryLabels.js.map +7 -0
- package/dist/modules/customers/lib/interactionCompatibility.js +1 -0
- package/dist/modules/customers/lib/interactionCompatibility.js.map +2 -2
- package/dist/modules/customers/translations.js +9 -0
- package/dist/modules/customers/translations.js.map +7 -0
- package/dist/modules/eudr/index.js +13 -0
- package/dist/modules/eudr/index.js.map +2 -2
- package/dist/modules/staff/api/timesheets/access-requests/route.js +11 -5
- package/dist/modules/staff/api/timesheets/access-requests/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/route.js +19 -12
- package/dist/modules/staff/api/timesheets/time-projects/route.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js +21 -12
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +14 -9
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js +31 -10
- package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/projectAccessScope.js +64 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectAccessScope.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectAccessScopeHeaders.js +13 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectAccessScopeHeaders.js.map +7 -0
- package/dist/modules/sync_excel/index.js +11 -1
- package/dist/modules/sync_excel/index.js.map +2 -2
- package/dist/modules/translations/di.js +2 -0
- package/dist/modules/translations/di.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customer_accounts/api/admin/roles.ts +26 -2
- package/src/modules/customer_accounts/api/admin/users.ts +26 -7
- package/src/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.tsx +96 -13
- package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +9 -1
- package/src/modules/customer_accounts/data/validators.ts +1 -0
- package/src/modules/customer_accounts/i18n/de.json +10 -0
- package/src/modules/customer_accounts/i18n/en.json +10 -0
- package/src/modules/customer_accounts/i18n/es.json +10 -0
- package/src/modules/customer_accounts/i18n/ko.json +10 -0
- package/src/modules/customer_accounts/i18n/pl.json +10 -0
- package/src/modules/customer_accounts/lib/adminOrganizationScope.ts +71 -0
- package/src/modules/customers/api/customer-dictionary-entries/route.ts +82 -0
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +29 -6
- package/src/modules/customers/cli.ts +23 -124
- package/src/modules/customers/components/DictionarySettings.tsx +1 -1
- package/src/modules/customers/components/detail/ManageTagsDialog.tsx +1 -1
- package/src/modules/customers/components/detail/TaskDialog.tsx +15 -1
- package/src/modules/customers/components/detail/TaskForm.tsx +1 -17
- package/src/modules/customers/components/detail/TasksSection.tsx +44 -14
- package/src/modules/customers/components/detail/hooks/useCustomerDictionary.ts +14 -8
- package/src/modules/customers/components/detail/hooks/useInteractions.ts +3 -4
- package/src/modules/customers/i18n/de.json +81 -0
- package/src/modules/customers/i18n/en.json +81 -0
- package/src/modules/customers/i18n/es.json +81 -0
- package/src/modules/customers/i18n/ko.json +81 -0
- package/src/modules/customers/i18n/pl.json +81 -0
- package/src/modules/customers/lib/dictionaryDefaults.ts +137 -0
- package/src/modules/customers/lib/dictionaryLabels.ts +73 -0
- package/src/modules/customers/lib/interactionCompatibility.ts +1 -0
- package/src/modules/customers/translations.ts +5 -0
- package/src/modules/eudr/index.ts +13 -0
- package/src/modules/staff/api/timesheets/access-requests/route.ts +13 -14
- package/src/modules/staff/api/timesheets/time-projects/route.ts +22 -13
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.tsx +25 -14
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +14 -13
- package/src/modules/staff/lib/time-tracking-ui/NoProjectAccess.tsx +30 -8
- package/src/modules/staff/lib/time-tracking-ui/projectAccessScope.ts +102 -0
- package/src/modules/staff/lib/time-tracking-ui/projectAccessScopeHeaders.ts +15 -0
- package/src/modules/sync_excel/index.ts +10 -0
- package/src/modules/translations/di.ts +2 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/detail/hooks/useInteractions.ts"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { apiCallOrThrow, readApiResultOrThrow, 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 { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { InteractionSummary } from '../types'\n\ntype InteractionsListResponse = {\n items: InteractionSummary[]\n nextCursor?: string\n}\n\nexport type InteractionCreatePayload = {\n entityId: string\n interactionType: string\n title?: string | null\n body?: string | null\n status?: string\n scheduledAt?: string | null\n priority?: number | null\n customValues?: Record<string, unknown>\n ownerUserId?: string | null\n dealId?: string | null\n}\n\nexport type InteractionUpdatePayload = {\n interactionType?: string\n title?: string | null\n body?: string | null\n status?: string\n scheduledAt?: string | null\n occurredAt?: string | null\n priority?: number | null\n customValues?: Record<string, unknown>\n ownerUserId?: string | null\n dealId?: string | null\n}\n\nexport type UseInteractionsOptions = {\n entityId: string | null\n statusFilter?: string\n typeFilter?: string\n excludeTypeFilter?: string\n pageSize?: number\n}\n\nexport type UseInteractionsResult = {\n interactions: InteractionSummary[]\n isInitialLoading: boolean\n isLoadingMore: boolean\n isMutating: boolean\n hasMore: boolean\n loadMore: () => Promise<void>\n refresh: () => Promise<void>\n createInteraction: (payload: InteractionCreatePayload) => Promise<void>\n updateInteraction: (id: string, payload: InteractionUpdatePayload) => Promise<void>\n completeInteraction: (id: string) => Promise<void>\n cancelInteraction: (id: string) => Promise<void>\n deleteInteraction: (id: string) => Promise<void>\n pendingId: string | null\n totalCount: number\n error: string | null\n}\n\nfunction mergeUnique(existing: InteractionSummary[], incoming: InteractionSummary[]): InteractionSummary[] {\n if (!existing.length) return incoming\n if (!incoming.length) return existing\n const byId = new Map<string, InteractionSummary>()\n const result: InteractionSummary[] = []\n for (const item of existing) {\n byId.set(item.id, item)\n result.push(item)\n }\n for (const item of incoming) {\n if (byId.has(item.id)) {\n const index = result.findIndex((entry) => entry.id === item.id)\n if (index !== -1) result[index] = item\n } else {\n byId.set(item.id, item)\n result.push(item)\n }\n }\n return result\n}\n\nexport function useInteractions({\n entityId,\n statusFilter,\n typeFilter,\n excludeTypeFilter,\n pageSize = 25,\n}: UseInteractionsOptions): UseInteractionsResult {\n const t = useT()\n const [interactions, setInteractions] = React.useState<InteractionSummary[]>([])\n const [nextCursor, setNextCursor] = React.useState<string | null>(null)\n const [isInitialLoading, setIsInitialLoading] = React.useState<boolean>(() => Boolean(entityId))\n const [isLoadingMore, setIsLoadingMore] = React.useState(false)\n const [isMutating, setIsMutating] = React.useState(false)\n const [pendingId, setPendingId] = React.useState<string | null>(null)\n const [error, setError] = React.useState<string | null>(null)\n\n const mapResponse = React.useCallback((payload: InteractionsListResponse) => {\n const mapped = Array.isArray(payload.items) ? payload.items : []\n setNextCursor(typeof payload.nextCursor === 'string' && payload.nextCursor.trim().length > 0 ? payload.nextCursor : null)\n setError(null)\n return mapped\n }, [])\n\n const fetchPage = React.useCallback(\n async (cursor?: string | null): Promise<InteractionsListResponse> => {\n if (!entityId) {\n return { items: [], nextCursor: undefined }\n }\n const params = new URLSearchParams({\n limit: String(pageSize),\n entityId,\n sortField: 'scheduledAt',\n sortDir: 'asc',\n })\n if (cursor) params.set('cursor', cursor)\n if (statusFilter) params.set('status', statusFilter)\n if (typeFilter) params.set('interactionType', typeFilter)\n if (excludeTypeFilter) params.set('excludeInteractionType', excludeTypeFilter)\n\n return readApiResultOrThrow<InteractionsListResponse>(\n `/api/customers/interactions?${params.toString()}`,\n undefined,\n { errorMessage: t('customers.interactions.load.error', 'Failed to load interactions.') },\n )\n },\n [entityId, excludeTypeFilter, pageSize, statusFilter, typeFilter, t],\n )\n\n const refresh = React.useCallback(async () => {\n if (!entityId) {\n setInteractions([])\n setNextCursor(null)\n return\n }\n setIsInitialLoading(true)\n try {\n const payload = await fetchPage(null)\n const mapped = mapResponse(payload)\n setInteractions(mapped)\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customers.interactions.load.error', 'Failed to load interactions.')\n setError(message)\n throw err\n } finally {\n setIsInitialLoading(false)\n }\n }, [entityId, fetchPage, mapResponse, t])\n\n const loadMore = React.useCallback(async () => {\n if (!entityId) return\n if (isLoadingMore) return\n if (!nextCursor) return\n setIsLoadingMore(true)\n try {\n const payload = await fetchPage(nextCursor)\n const mapped = mapResponse(payload)\n setInteractions((prev) => mergeUnique(prev, mapped))\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customers.interactions.load.error', 'Failed to load interactions.')\n setError(message)\n throw err\n } finally {\n setIsLoadingMore(false)\n }\n }, [entityId, fetchPage, isLoadingMore, mapResponse, nextCursor, t])\n\n React.useEffect(() => {\n if (!entityId) {\n setInteractions([])\n setNextCursor(null)\n setError(null)\n setIsInitialLoading(false)\n return\n }\n setInteractions([])\n setNextCursor(null)\n setError(null)\n let cancelled = false\n setIsInitialLoading(true)\n fetchPage(null)\n .then((payload) => {\n if (cancelled) return\n const mapped = mapResponse(payload)\n setInteractions(mapped)\n })\n .catch((err) => {\n if (cancelled) return\n const message = err instanceof Error ? err.message : t('customers.interactions.load.error', 'Failed to load interactions.')\n setError(message)\n })\n .finally(() => {\n if (!cancelled) setIsInitialLoading(false)\n })\n return () => {\n cancelled = true\n }\n }, [entityId, excludeTypeFilter, statusFilter, typeFilter, fetchPage, mapResponse, t])\n\n const createInteraction = React.useCallback(\n async (payload: InteractionCreatePayload) => {\n if (!entityId) throw new Error('Interaction creation requires an entity id')\n setIsMutating(true)\n try {\n await apiCallOrThrow(\n '/api/customers/interactions',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n { errorMessage: t('customers.interactions.create.error', 'Failed to create interaction.') },\n )\n await refresh()\n } finally {\n setIsMutating(false)\n }\n },\n [entityId, refresh, t],\n )\n\n const updateInteraction = React.useCallback(\n async (id: string, payload: InteractionUpdatePayload) => {\n setIsMutating(true)\n // Send the optimistic-lock header (target's loaded `updatedAt`) so a stale\n // edit \u2014 including a task/activity edited from another tab \u2014 surfaces the\n // conflict bar instead of silently overwriting (#2055). A stale edit after\n // the record was deleted elsewhere maps to the same 409 via the server's\n // enforceRecordGoneIsConflict, instead of a bare \"Interaction not found\" 404.\n const target = interactions.find((entry) => entry.id === id)\n try {\n await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(target?.updatedAt ?? null),\n () => apiCallOrThrow(\n '/api/customers/interactions',\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id, ...payload }),\n },\n { errorMessage: t('customers.interactions.update.error', 'Failed to update interaction.') },\n ),\n )\n await refresh()\n } catch (err) {\n if (surfaceRecordConflict(err, t)) { await refresh(); return }\n throw err\n } finally {\n setIsMutating(false)\n }\n },\n [interactions, refresh, t],\n )\n\n const completeInteraction = React.useCallback(\n async (id: string) => {\n setIsMutating(true)\n setPendingId(id)\n const target = interactions.find((entry) => entry.id === id)\n try {\n await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(target?.updatedAt ?? null),\n () => apiCallOrThrow(\n '/api/customers/interactions/complete',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id }),\n },\n { errorMessage: t('customers.interactions.complete.error', 'Failed to complete interaction.') },\n ),\n )\n await refresh()\n } catch (err) {\n if (surfaceRecordConflict(err, t)) { await refresh(); return }\n throw err\n } finally {\n setPendingId(null)\n setIsMutating(false)\n }\n },\n [interactions, refresh, t],\n )\n\n const cancelInteraction = React.useCallback(\n async (id: string) => {\n setIsMutating(true)\n setPendingId(id)\n const target = interactions.find((entry) => entry.id === id)\n try {\n await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(target?.updatedAt ?? null),\n () => apiCallOrThrow(\n '/api/customers/interactions/cancel',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id }),\n },\n { errorMessage: t('customers.interactions.cancel.error', 'Failed to cancel interaction.') },\n ),\n )\n await refresh()\n } catch (err) {\n if (surfaceRecordConflict(err, t)) { await refresh(); return }\n throw err\n } finally {\n setPendingId(null)\n setIsMutating(false)\n }\n },\n [interactions, refresh, t],\n )\n\n const deleteInteraction = React.useCallback(\n async (id: string) => {\n setIsMutating(true)\n const target = interactions.find((entry) => entry.id === id)\n try {\n await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(target?.updatedAt ?? null),\n () =>\n apiCallOrThrow(\n `/api/customers/interactions?id=${encodeURIComponent(id)}`,\n {\n method: 'DELETE',\n },\n { errorMessage: t('customers.interactions.delete.error', 'Failed to delete interaction.') },\n ),\n )\n await refresh()\n } catch (err) {\n // A stale delete (the record was changed/deleted in another tab) surfaces\n // the unified conflict bar and re-syncs the list instead of a raw error (#2055).\n if (surfaceRecordConflict(err, t)) {\n await refresh()\n return\n }\n throw err\n } finally {\n setIsMutating(false)\n }\n },\n [interactions, refresh, t],\n )\n\n const hasMore = entityId != null && nextCursor != null\n\n return {\n interactions,\n isInitialLoading,\n isLoadingMore,\n isMutating,\n hasMore,\n loadMore,\n refresh,\n createInteraction,\n updateInteraction,\n completeInteraction,\n cancelInteraction,\n deleteInteraction,\n pendingId,\n totalCount: interactions.length,\n error,\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { apiCallOrThrow, readApiResultOrThrow, 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 { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { InteractionSummary } from '../types'\n\ntype InteractionsListResponse = {\n items: InteractionSummary[]\n nextCursor?: string\n}\n\nexport type InteractionCreatePayload = {\n entityId: string\n interactionType: string\n title?: string | null\n body?: string | null\n status?: string\n scheduledAt?: string | null\n priority?: number | null\n customValues?: Record<string, unknown>\n ownerUserId?: string | null\n dealId?: string | null\n}\n\nexport type InteractionUpdatePayload = {\n interactionType?: string\n title?: string | null\n body?: string | null\n status?: string\n scheduledAt?: string | null\n occurredAt?: string | null\n priority?: number | null\n customValues?: Record<string, unknown>\n ownerUserId?: string | null\n dealId?: string | null\n}\n\nexport type UseInteractionsOptions = {\n entityId: string | null\n statusFilter?: string\n typeFilter?: string\n excludeTypeFilter?: string\n pageSize?: number\n}\n\nexport type UseInteractionsResult = {\n interactions: InteractionSummary[]\n isInitialLoading: boolean\n isLoadingMore: boolean\n isMutating: boolean\n hasMore: boolean\n loadMore: () => Promise<void>\n refresh: () => Promise<void>\n createInteraction: (payload: InteractionCreatePayload) => Promise<void>\n updateInteraction: (id: string, payload: InteractionUpdatePayload) => Promise<void>\n completeInteraction: (id: string) => Promise<void>\n cancelInteraction: (id: string) => Promise<void>\n deleteInteraction: (id: string) => Promise<void>\n pendingId: string | null\n totalCount: number\n error: string | null\n}\n\nfunction mergeUnique(existing: InteractionSummary[], incoming: InteractionSummary[]): InteractionSummary[] {\n if (!existing.length) return incoming\n if (!incoming.length) return existing\n const byId = new Map<string, InteractionSummary>()\n const result: InteractionSummary[] = []\n for (const item of existing) {\n byId.set(item.id, item)\n result.push(item)\n }\n for (const item of incoming) {\n if (byId.has(item.id)) {\n const index = result.findIndex((entry) => entry.id === item.id)\n if (index !== -1) result[index] = item\n } else {\n byId.set(item.id, item)\n result.push(item)\n }\n }\n return result\n}\n\nexport function useInteractions({\n entityId,\n statusFilter,\n typeFilter,\n excludeTypeFilter,\n pageSize = 25,\n}: UseInteractionsOptions): UseInteractionsResult {\n const t = useT()\n const [interactions, setInteractions] = React.useState<InteractionSummary[]>([])\n const [nextCursor, setNextCursor] = React.useState<string | null>(null)\n const [isInitialLoading, setIsInitialLoading] = React.useState<boolean>(() => Boolean(entityId))\n const [isLoadingMore, setIsLoadingMore] = React.useState(false)\n const [isMutating, setIsMutating] = React.useState(false)\n const [pendingId, setPendingId] = React.useState<string | null>(null)\n const [error, setError] = React.useState<string | null>(null)\n\n const mapResponse = React.useCallback((payload: InteractionsListResponse) => {\n const mapped = Array.isArray(payload.items) ? payload.items : []\n setNextCursor(typeof payload.nextCursor === 'string' && payload.nextCursor.trim().length > 0 ? payload.nextCursor : null)\n setError(null)\n return mapped\n }, [])\n\n const fetchPage = React.useCallback(\n async (cursor?: string | null): Promise<InteractionsListResponse> => {\n if (!entityId) {\n return { items: [], nextCursor: undefined }\n }\n const params = new URLSearchParams({\n limit: String(pageSize),\n entityId,\n sortField: 'scheduledAt',\n sortDir: 'asc',\n })\n if (cursor) params.set('cursor', cursor)\n if (statusFilter) params.set('status', statusFilter)\n if (typeFilter) params.set('interactionType', typeFilter)\n if (excludeTypeFilter) params.set('excludeInteractionType', excludeTypeFilter)\n\n return readApiResultOrThrow<InteractionsListResponse>(\n `/api/customers/interactions?${params.toString()}`,\n undefined,\n { errorMessage: t('customers.interactions.load.error', 'Failed to load interactions.') },\n )\n },\n [entityId, excludeTypeFilter, pageSize, statusFilter, typeFilter, t],\n )\n\n const refresh = React.useCallback(async () => {\n if (!entityId) {\n setInteractions([])\n setNextCursor(null)\n return\n }\n setIsInitialLoading(true)\n try {\n const payload = await fetchPage(null)\n const mapped = mapResponse(payload)\n setInteractions(mapped)\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customers.interactions.load.error', 'Failed to load interactions.')\n setError(message)\n throw err\n } finally {\n setIsInitialLoading(false)\n }\n }, [entityId, fetchPage, mapResponse, t])\n\n const loadMore = React.useCallback(async () => {\n if (!entityId) return\n if (isLoadingMore) return\n if (!nextCursor) return\n setIsLoadingMore(true)\n try {\n const payload = await fetchPage(nextCursor)\n const mapped = mapResponse(payload)\n setInteractions((prev) => mergeUnique(prev, mapped))\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customers.interactions.load.error', 'Failed to load interactions.')\n setError(message)\n throw err\n } finally {\n setIsLoadingMore(false)\n }\n }, [entityId, fetchPage, isLoadingMore, mapResponse, nextCursor, t])\n\n React.useEffect(() => {\n if (!entityId) {\n setInteractions([])\n setNextCursor(null)\n setError(null)\n setIsInitialLoading(false)\n return\n }\n setInteractions([])\n setNextCursor(null)\n setError(null)\n let cancelled = false\n setIsInitialLoading(true)\n fetchPage(null)\n .then((payload) => {\n if (cancelled) return\n const mapped = mapResponse(payload)\n setInteractions(mapped)\n })\n .catch((err) => {\n if (cancelled) return\n const message = err instanceof Error ? err.message : t('customers.interactions.load.error', 'Failed to load interactions.')\n setError(message)\n })\n .finally(() => {\n if (!cancelled) setIsInitialLoading(false)\n })\n return () => {\n cancelled = true\n }\n }, [entityId, excludeTypeFilter, statusFilter, typeFilter, fetchPage, mapResponse, t])\n\n const createInteraction = React.useCallback(\n async (payload: InteractionCreatePayload) => {\n if (!entityId) throw new Error('Interaction creation requires an entity id')\n setIsMutating(true)\n try {\n await apiCallOrThrow(\n '/api/customers/interactions',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n { errorMessage: t('customers.interactions.create.error', 'Failed to create interaction.') },\n )\n await refresh()\n } finally {\n setIsMutating(false)\n }\n },\n [entityId, refresh, t],\n )\n\n const updateInteraction = React.useCallback(\n async (id: string, payload: InteractionUpdatePayload) => {\n setIsMutating(true)\n // Send the optimistic-lock header (target's loaded `updatedAt`) so a stale\n // edit \u2014 including a task/activity edited from another tab \u2014 surfaces the\n // conflict bar instead of silently overwriting (#2055). A stale edit after\n // the record was deleted elsewhere maps to the same 409 via the server's\n // enforceRecordGoneIsConflict, instead of a bare \"Interaction not found\" 404.\n const target = interactions.find((entry) => entry.id === id)\n try {\n await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(target?.updatedAt ?? null),\n () => apiCallOrThrow(\n '/api/customers/interactions',\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id, ...payload }),\n },\n { errorMessage: t('customers.interactions.update.error', 'Failed to update interaction.') },\n ),\n )\n await refresh()\n } catch (err) {\n if (surfaceRecordConflict(err, t)) await refresh()\n throw err\n } finally {\n setIsMutating(false)\n }\n },\n [interactions, refresh, t],\n )\n\n const completeInteraction = React.useCallback(\n async (id: string) => {\n setIsMutating(true)\n setPendingId(id)\n const target = interactions.find((entry) => entry.id === id)\n try {\n await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(target?.updatedAt ?? null),\n () => apiCallOrThrow(\n '/api/customers/interactions/complete',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id }),\n },\n { errorMessage: t('customers.interactions.complete.error', 'Failed to complete interaction.') },\n ),\n )\n await refresh()\n } catch (err) {\n if (surfaceRecordConflict(err, t)) await refresh()\n throw err\n } finally {\n setPendingId(null)\n setIsMutating(false)\n }\n },\n [interactions, refresh, t],\n )\n\n const cancelInteraction = React.useCallback(\n async (id: string) => {\n setIsMutating(true)\n setPendingId(id)\n const target = interactions.find((entry) => entry.id === id)\n try {\n await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(target?.updatedAt ?? null),\n () => apiCallOrThrow(\n '/api/customers/interactions/cancel',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id }),\n },\n { errorMessage: t('customers.interactions.cancel.error', 'Failed to cancel interaction.') },\n ),\n )\n await refresh()\n } catch (err) {\n if (surfaceRecordConflict(err, t)) await refresh()\n throw err\n } finally {\n setPendingId(null)\n setIsMutating(false)\n }\n },\n [interactions, refresh, t],\n )\n\n const deleteInteraction = React.useCallback(\n async (id: string) => {\n setIsMutating(true)\n const target = interactions.find((entry) => entry.id === id)\n try {\n await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(target?.updatedAt ?? null),\n () =>\n apiCallOrThrow(\n `/api/customers/interactions?id=${encodeURIComponent(id)}`,\n {\n method: 'DELETE',\n },\n { errorMessage: t('customers.interactions.delete.error', 'Failed to delete interaction.') },\n ),\n )\n await refresh()\n } catch (err) {\n // A stale delete (the record was changed/deleted in another tab) surfaces\n // the unified conflict bar and re-syncs the list instead of a raw error (#2055).\n if (surfaceRecordConflict(err, t)) {\n await refresh()\n }\n throw err\n } finally {\n setIsMutating(false)\n }\n },\n [interactions, refresh, t],\n )\n\n const hasMore = entityId != null && nextCursor != null\n\n return {\n interactions,\n isInitialLoading,\n isLoadingMore,\n isMutating,\n hasMore,\n loadMore,\n refresh,\n createInteraction,\n updateInteraction,\n completeInteraction,\n cancelInteraction,\n deleteInteraction,\n pendingId,\n totalCount: interactions.length,\n error,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const DEAL_STATUS_DEFAULTS = [
|
|
2
|
+
{ value: "open", label: "Open", color: "#2563eb", icon: "lucide:circle" },
|
|
3
|
+
{ value: "closed", label: "Closed", color: "#6b7280", icon: "lucide:check-circle" },
|
|
4
|
+
{ value: "win", label: "Win", color: "#22c55e", icon: "lucide:trophy" },
|
|
5
|
+
{ value: "lost", label: "Lost", color: "#ef4444", icon: "lucide:flag" },
|
|
6
|
+
{ value: "in_progress", label: "In progress", color: "#f59e0b", icon: "lucide:activity" }
|
|
7
|
+
];
|
|
8
|
+
const PIPELINE_STAGE_DEFAULTS = [
|
|
9
|
+
{ value: "opportunity", label: "Opportunity", color: "#38bdf8", icon: "lucide:target" },
|
|
10
|
+
{ value: "marketing_qualified_lead", label: "Marketing Qualified Lead", color: "#a855f7", icon: "lucide:sparkles" },
|
|
11
|
+
{ value: "sales_qualified_lead", label: "Sales Qualified Lead", color: "#f97316", icon: "lucide:users" },
|
|
12
|
+
{ value: "offering", label: "Offering", color: "#22c55e", icon: "lucide:package" },
|
|
13
|
+
{ value: "negotiations", label: "Negotiations", color: "#facc15", icon: "lucide:handshake" },
|
|
14
|
+
{ value: "win", label: "Win", color: "#16a34a", icon: "lucide:award" },
|
|
15
|
+
{ value: "lost", label: "Lost", color: "#ef4444", icon: "lucide:flag" },
|
|
16
|
+
{ value: "stalled", label: "Stalled", color: "#6b7280", icon: "lucide:alert-circle" }
|
|
17
|
+
];
|
|
18
|
+
const ENTITY_STATUS_DEFAULTS = [
|
|
19
|
+
{ value: "active", label: "Active", color: "#22c55e", icon: "lucide:user-check" },
|
|
20
|
+
{ value: "inactive", label: "Inactive", color: "#94a3b8", icon: "lucide:pause-circle" },
|
|
21
|
+
{ value: "pending", label: "Pending", color: "#f59e0b", icon: "lucide:clock" },
|
|
22
|
+
{ value: "archived", label: "Archived", color: "#64748b", icon: "lucide:archive" }
|
|
23
|
+
];
|
|
24
|
+
const ENTITY_LIFECYCLE_STAGE_DEFAULTS = [
|
|
25
|
+
{ value: "lead", label: "Lead", color: "#3b82f6", icon: "lucide:sparkles" },
|
|
26
|
+
{ value: "prospect", label: "Prospect", color: "#8b5cf6", icon: "lucide:eye" },
|
|
27
|
+
{ value: "customer", label: "Customer", color: "#22c55e", icon: "lucide:handshake" },
|
|
28
|
+
{ value: "subscriber", label: "Subscriber", color: "#10b981", icon: "lucide:bell" },
|
|
29
|
+
{ value: "churned", label: "Churned", color: "#ef4444", icon: "lucide:user-x" },
|
|
30
|
+
{ value: "other", label: "Other", color: "#94a3b8", icon: "lucide:circle" }
|
|
31
|
+
];
|
|
32
|
+
const ENTITY_SOURCE_DEFAULTS = [
|
|
33
|
+
{ value: "linkedin", label: "LinkedIn", color: "#0a66c2", icon: "lucide:linkedin" },
|
|
34
|
+
{ value: "email", label: "Email", color: "#3b82f6", icon: "lucide:mail" },
|
|
35
|
+
{ value: "web_form", label: "Web form", color: "#22c55e", icon: "lucide:globe" },
|
|
36
|
+
{ value: "referral", label: "Referral", color: "#8b5cf6", icon: "lucide:users" },
|
|
37
|
+
{ value: "customer_referral", label: "Customer referral", color: "#22c55e", icon: "lucide:thumbs-up" },
|
|
38
|
+
{ value: "partner_referral", label: "Partner referral", color: "#3b82f6", icon: "lucide:handshake" },
|
|
39
|
+
{ value: "event", label: "Conference / Event", color: "#f59e0b", icon: "lucide:calendar" },
|
|
40
|
+
{ value: "cold_outreach", label: "Cold outreach", color: "#94a3b8", icon: "lucide:phone" },
|
|
41
|
+
{ value: "facebook", label: "Facebook", color: "#1877f2", icon: "lucide:facebook" },
|
|
42
|
+
{ value: "typeform", label: "Typeform", color: "#262627", icon: "lucide:file-text" },
|
|
43
|
+
{ value: "other", label: "Other", color: "#64748b", icon: "lucide:circle" }
|
|
44
|
+
];
|
|
45
|
+
const ADDRESS_TYPE_DEFAULTS = [
|
|
46
|
+
{ value: "office", label: "Office", color: "#3b82f6", icon: "lucide:building" },
|
|
47
|
+
{ value: "work", label: "Work", color: "#6366f1", icon: "lucide:briefcase" },
|
|
48
|
+
{ value: "billing", label: "Billing", color: "#f97316", icon: "lucide:wallet" },
|
|
49
|
+
{ value: "shipping", label: "Shipping", color: "#22c55e", icon: "lucide:truck" },
|
|
50
|
+
{ value: "home", label: "Home", color: "#10b981", icon: "lucide:map-pin" }
|
|
51
|
+
];
|
|
52
|
+
const ACTIVITY_TYPE_DEFAULTS = [
|
|
53
|
+
{ value: "call", label: "Call", color: "#2563eb", icon: "lucide:phone-call" },
|
|
54
|
+
{ value: "email", label: "Email", color: "#16a34a", icon: "lucide:mail" },
|
|
55
|
+
{ value: "event", label: "Event", color: "#6366f1", icon: "lucide:calendar" },
|
|
56
|
+
{ value: "meeting", label: "Meeting", color: "#f59e0b", icon: "lucide:users" },
|
|
57
|
+
{ value: "note", label: "Note", color: "#a855f7", icon: "lucide:notebook" },
|
|
58
|
+
{ value: "task", label: "Task", color: "#ef4444", icon: "lucide:check-square" }
|
|
59
|
+
];
|
|
60
|
+
const INTERACTION_STATUS_DEFAULTS = [
|
|
61
|
+
{ value: "planned", label: "Planned", color: "#2563eb", icon: "lucide:circle" },
|
|
62
|
+
{ value: "in_progress", label: "In progress", color: "#f59e0b", icon: "lucide:activity" },
|
|
63
|
+
{ value: "waiting", label: "Waiting / blocked", color: "#a855f7", icon: "lucide:pause-circle" },
|
|
64
|
+
{ value: "done", label: "Done", color: "#22c55e", icon: "lucide:check-circle" },
|
|
65
|
+
{ value: "canceled", label: "Canceled", color: "#6b7280", icon: "lucide:x-circle" }
|
|
66
|
+
];
|
|
67
|
+
const JOB_TITLE_DEFAULTS = [
|
|
68
|
+
{ value: "Director of Operations", label: "Director of Operations", color: "#f97316", icon: "lucide:settings" },
|
|
69
|
+
{ value: "VP of Partnerships", label: "VP of Partnerships", color: "#6366f1", icon: "lucide:users" },
|
|
70
|
+
{ value: "Founder & Principal", label: "Founder & Principal", color: "#ec4899", icon: "lucide:star" },
|
|
71
|
+
{ value: "Senior Project Manager", label: "Senior Project Manager", color: "#0ea5e9", icon: "lucide:clipboard-list" },
|
|
72
|
+
{ value: "Chief Revenue Officer", label: "Chief Revenue Officer", color: "#8b5cf6", icon: "lucide:bar-chart-3" },
|
|
73
|
+
{ value: "Director of Retail Partnerships", label: "Director of Retail Partnerships", color: "#f59e0b", icon: "lucide:shopping-bag" }
|
|
74
|
+
];
|
|
75
|
+
const INDUSTRY_DEFAULTS = [
|
|
76
|
+
{ value: "Renewable Energy", label: "Renewable Energy" },
|
|
77
|
+
{ value: "Software", label: "Software" },
|
|
78
|
+
{ value: "Interior Design", label: "Interior Design" },
|
|
79
|
+
{ value: "SaaS", label: "SaaS" },
|
|
80
|
+
{ value: "E-commerce", label: "E-commerce" },
|
|
81
|
+
{ value: "Healthcare", label: "Healthcare" },
|
|
82
|
+
{ value: "Manufacturing", label: "Manufacturing" },
|
|
83
|
+
{ value: "Logistics", label: "Logistics" },
|
|
84
|
+
{ value: "Financial Services", label: "Financial Services" },
|
|
85
|
+
{ value: "Retail", label: "Retail" },
|
|
86
|
+
{ value: "Hospitality", label: "Hospitality" },
|
|
87
|
+
{ value: "Energy", label: "Energy" },
|
|
88
|
+
{ value: "Media", label: "Media" }
|
|
89
|
+
];
|
|
90
|
+
const TEMPERATURE_DEFAULTS = [
|
|
91
|
+
{ value: "hot", label: "Hot", color: "#ef4444", icon: "lucide:flame" },
|
|
92
|
+
{ value: "high", label: "High", color: "#f59e0b", icon: "lucide:trending-up" },
|
|
93
|
+
{ value: "medium", label: "Medium", color: "#8b5cf6", icon: "lucide:sparkles" },
|
|
94
|
+
{ value: "low", label: "Low", color: "#64748b", icon: "lucide:clock" },
|
|
95
|
+
{ value: "cold", label: "Cold", color: "#94a3b8", icon: "lucide:snowflake" }
|
|
96
|
+
];
|
|
97
|
+
const PERSON_COMPANY_ROLE_DEFAULTS = [
|
|
98
|
+
{ value: "decision_maker", label: "Decision maker", color: "#f59e0b", icon: "lucide:crown" },
|
|
99
|
+
{ value: "influencer", label: "Influencer", color: "#8b5cf6", icon: "lucide:sparkles" },
|
|
100
|
+
{ value: "budget_holder", label: "Budget holder", color: "#3b82f6", icon: "lucide:wallet" },
|
|
101
|
+
{ value: "technical_evaluator", label: "Technical evaluator", color: "#22c55e", icon: "lucide:wrench" },
|
|
102
|
+
{ value: "primary_contact", label: "Primary contact", color: "#0ea5e9", icon: "lucide:star" },
|
|
103
|
+
{ value: "end_user", label: "End user", color: "#64748b", icon: "lucide:user" }
|
|
104
|
+
];
|
|
105
|
+
const CUSTOMER_DICTIONARY_DEFAULTS = {
|
|
106
|
+
status: ENTITY_STATUS_DEFAULTS,
|
|
107
|
+
source: ENTITY_SOURCE_DEFAULTS,
|
|
108
|
+
lifecycle_stage: ENTITY_LIFECYCLE_STAGE_DEFAULTS,
|
|
109
|
+
address_type: ADDRESS_TYPE_DEFAULTS,
|
|
110
|
+
activity_type: ACTIVITY_TYPE_DEFAULTS,
|
|
111
|
+
interaction_status: INTERACTION_STATUS_DEFAULTS,
|
|
112
|
+
job_title: JOB_TITLE_DEFAULTS,
|
|
113
|
+
deal_status: DEAL_STATUS_DEFAULTS,
|
|
114
|
+
pipeline_stage: PIPELINE_STAGE_DEFAULTS,
|
|
115
|
+
industry: INDUSTRY_DEFAULTS,
|
|
116
|
+
temperature: TEMPERATURE_DEFAULTS,
|
|
117
|
+
person_company_role: PERSON_COMPANY_ROLE_DEFAULTS
|
|
118
|
+
};
|
|
119
|
+
export {
|
|
120
|
+
ACTIVITY_TYPE_DEFAULTS,
|
|
121
|
+
ADDRESS_TYPE_DEFAULTS,
|
|
122
|
+
CUSTOMER_DICTIONARY_DEFAULTS,
|
|
123
|
+
DEAL_STATUS_DEFAULTS,
|
|
124
|
+
ENTITY_LIFECYCLE_STAGE_DEFAULTS,
|
|
125
|
+
ENTITY_SOURCE_DEFAULTS,
|
|
126
|
+
ENTITY_STATUS_DEFAULTS,
|
|
127
|
+
INDUSTRY_DEFAULTS,
|
|
128
|
+
INTERACTION_STATUS_DEFAULTS,
|
|
129
|
+
JOB_TITLE_DEFAULTS,
|
|
130
|
+
PERSON_COMPANY_ROLE_DEFAULTS,
|
|
131
|
+
PIPELINE_STAGE_DEFAULTS,
|
|
132
|
+
TEMPERATURE_DEFAULTS
|
|
133
|
+
};
|
|
134
|
+
//# sourceMappingURL=dictionaryDefaults.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/customers/lib/dictionaryDefaults.ts"],
|
|
4
|
+
"sourcesContent": ["type DictionaryDefault = {\n value: string\n label: string\n color?: string\n icon?: string\n}\n\nexport const DEAL_STATUS_DEFAULTS: DictionaryDefault[] = [\n { value: 'open', label: 'Open', color: '#2563eb', icon: 'lucide:circle' },\n { value: 'closed', label: 'Closed', color: '#6b7280', icon: 'lucide:check-circle' },\n { value: 'win', label: 'Win', color: '#22c55e', icon: 'lucide:trophy' },\n { value: 'lost', label: 'Lost', color: '#ef4444', icon: 'lucide:flag' },\n { value: 'in_progress', label: 'In progress', color: '#f59e0b', icon: 'lucide:activity' },\n]\n\nexport const PIPELINE_STAGE_DEFAULTS: DictionaryDefault[] = [\n { value: 'opportunity', label: 'Opportunity', color: '#38bdf8', icon: 'lucide:target' },\n { value: 'marketing_qualified_lead', label: 'Marketing Qualified Lead', color: '#a855f7', icon: 'lucide:sparkles' },\n { value: 'sales_qualified_lead', label: 'Sales Qualified Lead', color: '#f97316', icon: 'lucide:users' },\n { value: 'offering', label: 'Offering', color: '#22c55e', icon: 'lucide:package' },\n { value: 'negotiations', label: 'Negotiations', color: '#facc15', icon: 'lucide:handshake' },\n { value: 'win', label: 'Win', color: '#16a34a', icon: 'lucide:award' },\n { value: 'lost', label: 'Lost', color: '#ef4444', icon: 'lucide:flag' },\n { value: 'stalled', label: 'Stalled', color: '#6b7280', icon: 'lucide:alert-circle' },\n]\n\nexport const ENTITY_STATUS_DEFAULTS: DictionaryDefault[] = [\n { value: 'active', label: 'Active', color: '#22c55e', icon: 'lucide:user-check' },\n { value: 'inactive', label: 'Inactive', color: '#94a3b8', icon: 'lucide:pause-circle' },\n { value: 'pending', label: 'Pending', color: '#f59e0b', icon: 'lucide:clock' },\n { value: 'archived', label: 'Archived', color: '#64748b', icon: 'lucide:archive' },\n]\n\nexport const ENTITY_LIFECYCLE_STAGE_DEFAULTS: DictionaryDefault[] = [\n { value: 'lead', label: 'Lead', color: '#3b82f6', icon: 'lucide:sparkles' },\n { value: 'prospect', label: 'Prospect', color: '#8b5cf6', icon: 'lucide:eye' },\n { value: 'customer', label: 'Customer', color: '#22c55e', icon: 'lucide:handshake' },\n { value: 'subscriber', label: 'Subscriber', color: '#10b981', icon: 'lucide:bell' },\n { value: 'churned', label: 'Churned', color: '#ef4444', icon: 'lucide:user-x' },\n { value: 'other', label: 'Other', color: '#94a3b8', icon: 'lucide:circle' },\n]\n\nexport const ENTITY_SOURCE_DEFAULTS: DictionaryDefault[] = [\n { value: 'linkedin', label: 'LinkedIn', color: '#0a66c2', icon: 'lucide:linkedin' },\n { value: 'email', label: 'Email', color: '#3b82f6', icon: 'lucide:mail' },\n { value: 'web_form', label: 'Web form', color: '#22c55e', icon: 'lucide:globe' },\n { value: 'referral', label: 'Referral', color: '#8b5cf6', icon: 'lucide:users' },\n { value: 'customer_referral', label: 'Customer referral', color: '#22c55e', icon: 'lucide:thumbs-up' },\n { value: 'partner_referral', label: 'Partner referral', color: '#3b82f6', icon: 'lucide:handshake' },\n { value: 'event', label: 'Conference / Event', color: '#f59e0b', icon: 'lucide:calendar' },\n { value: 'cold_outreach', label: 'Cold outreach', color: '#94a3b8', icon: 'lucide:phone' },\n { value: 'facebook', label: 'Facebook', color: '#1877f2', icon: 'lucide:facebook' },\n { value: 'typeform', label: 'Typeform', color: '#262627', icon: 'lucide:file-text' },\n { value: 'other', label: 'Other', color: '#64748b', icon: 'lucide:circle' },\n]\n\nexport const ADDRESS_TYPE_DEFAULTS: DictionaryDefault[] = [\n { value: 'office', label: 'Office', color: '#3b82f6', icon: 'lucide:building' },\n { value: 'work', label: 'Work', color: '#6366f1', icon: 'lucide:briefcase' },\n { value: 'billing', label: 'Billing', color: '#f97316', icon: 'lucide:wallet' },\n { value: 'shipping', label: 'Shipping', color: '#22c55e', icon: 'lucide:truck' },\n { value: 'home', label: 'Home', color: '#10b981', icon: 'lucide:map-pin' },\n]\n\nexport const ACTIVITY_TYPE_DEFAULTS: DictionaryDefault[] = [\n { value: 'call', label: 'Call', color: '#2563eb', icon: 'lucide:phone-call' },\n { value: 'email', label: 'Email', color: '#16a34a', icon: 'lucide:mail' },\n { value: 'event', label: 'Event', color: '#6366f1', icon: 'lucide:calendar' },\n { value: 'meeting', label: 'Meeting', color: '#f59e0b', icon: 'lucide:users' },\n { value: 'note', label: 'Note', color: '#a855f7', icon: 'lucide:notebook' },\n { value: 'task', label: 'Task', color: '#ef4444', icon: 'lucide:check-square' },\n]\n\nexport const INTERACTION_STATUS_DEFAULTS: DictionaryDefault[] = [\n { value: 'planned', label: 'Planned', color: '#2563eb', icon: 'lucide:circle' },\n { value: 'in_progress', label: 'In progress', color: '#f59e0b', icon: 'lucide:activity' },\n { value: 'waiting', label: 'Waiting / blocked', color: '#a855f7', icon: 'lucide:pause-circle' },\n { value: 'done', label: 'Done', color: '#22c55e', icon: 'lucide:check-circle' },\n { value: 'canceled', label: 'Canceled', color: '#6b7280', icon: 'lucide:x-circle' },\n]\n\nexport const JOB_TITLE_DEFAULTS: DictionaryDefault[] = [\n { value: 'Director of Operations', label: 'Director of Operations', color: '#f97316', icon: 'lucide:settings' },\n { value: 'VP of Partnerships', label: 'VP of Partnerships', color: '#6366f1', icon: 'lucide:users' },\n { value: 'Founder & Principal', label: 'Founder & Principal', color: '#ec4899', icon: 'lucide:star' },\n { value: 'Senior Project Manager', label: 'Senior Project Manager', color: '#0ea5e9', icon: 'lucide:clipboard-list' },\n { value: 'Chief Revenue Officer', label: 'Chief Revenue Officer', color: '#8b5cf6', icon: 'lucide:bar-chart-3' },\n { value: 'Director of Retail Partnerships', label: 'Director of Retail Partnerships', color: '#f59e0b', icon: 'lucide:shopping-bag' },\n]\n\nexport const INDUSTRY_DEFAULTS: DictionaryDefault[] = [\n { value: 'Renewable Energy', label: 'Renewable Energy' },\n { value: 'Software', label: 'Software' },\n { value: 'Interior Design', label: 'Interior Design' },\n { value: 'SaaS', label: 'SaaS' },\n { value: 'E-commerce', label: 'E-commerce' },\n { value: 'Healthcare', label: 'Healthcare' },\n { value: 'Manufacturing', label: 'Manufacturing' },\n { value: 'Logistics', label: 'Logistics' },\n { value: 'Financial Services', label: 'Financial Services' },\n { value: 'Retail', label: 'Retail' },\n { value: 'Hospitality', label: 'Hospitality' },\n { value: 'Energy', label: 'Energy' },\n { value: 'Media', label: 'Media' },\n]\n\nexport const TEMPERATURE_DEFAULTS: DictionaryDefault[] = [\n { value: 'hot', label: 'Hot', color: '#ef4444', icon: 'lucide:flame' },\n { value: 'high', label: 'High', color: '#f59e0b', icon: 'lucide:trending-up' },\n { value: 'medium', label: 'Medium', color: '#8b5cf6', icon: 'lucide:sparkles' },\n { value: 'low', label: 'Low', color: '#64748b', icon: 'lucide:clock' },\n { value: 'cold', label: 'Cold', color: '#94a3b8', icon: 'lucide:snowflake' },\n]\n\nexport const PERSON_COMPANY_ROLE_DEFAULTS = [\n { value: 'decision_maker', label: 'Decision maker', color: '#f59e0b', icon: 'lucide:crown' },\n { value: 'influencer', label: 'Influencer', color: '#8b5cf6', icon: 'lucide:sparkles' },\n { value: 'budget_holder', label: 'Budget holder', color: '#3b82f6', icon: 'lucide:wallet' },\n { value: 'technical_evaluator', label: 'Technical evaluator', color: '#22c55e', icon: 'lucide:wrench' },\n { value: 'primary_contact', label: 'Primary contact', color: '#0ea5e9', icon: 'lucide:star' },\n { value: 'end_user', label: 'End user', color: '#64748b', icon: 'lucide:user' },\n]\n\nexport const CUSTOMER_DICTIONARY_DEFAULTS: Record<string, readonly DictionaryDefault[]> = {\n status: ENTITY_STATUS_DEFAULTS,\n source: ENTITY_SOURCE_DEFAULTS,\n lifecycle_stage: ENTITY_LIFECYCLE_STAGE_DEFAULTS,\n address_type: ADDRESS_TYPE_DEFAULTS,\n activity_type: ACTIVITY_TYPE_DEFAULTS,\n interaction_status: INTERACTION_STATUS_DEFAULTS,\n job_title: JOB_TITLE_DEFAULTS,\n deal_status: DEAL_STATUS_DEFAULTS,\n pipeline_stage: PIPELINE_STAGE_DEFAULTS,\n industry: INDUSTRY_DEFAULTS,\n temperature: TEMPERATURE_DEFAULTS,\n person_company_role: PERSON_COMPANY_ROLE_DEFAULTS,\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { loadDictionary } from "@open-mercato/shared/lib/i18n/server";
|
|
2
|
+
import { createFallbackTranslator } from "@open-mercato/shared/lib/i18n/translate";
|
|
3
|
+
import { getTranslationOverlayPlugin } from "@open-mercato/shared/lib/localization/overlay-plugin";
|
|
4
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
5
|
+
import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
|
|
6
|
+
import { CUSTOMER_DICTIONARY_DEFAULTS } from "./dictionaryDefaults.js";
|
|
7
|
+
const logger = createLogger("customers").child({ component: "dictionary-labels" });
|
|
8
|
+
function customerDictionaryLabelKey(kind, value) {
|
|
9
|
+
const token = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
10
|
+
return `customers.dictionaries.defaults.${kind}.${token}`;
|
|
11
|
+
}
|
|
12
|
+
async function localizeCustomerDictionaryEntries(entries, options) {
|
|
13
|
+
if (!entries.length) return [];
|
|
14
|
+
const dictionaryLocale = options.locale.toLowerCase().split("-")[0];
|
|
15
|
+
const translate = createFallbackTranslator(await loadDictionary(dictionaryLocale));
|
|
16
|
+
const defaults = Object.prototype.hasOwnProperty.call(CUSTOMER_DICTIONARY_DEFAULTS, options.kind) ? CUSTOMER_DICTIONARY_DEFAULTS[options.kind] : [];
|
|
17
|
+
const localized = entries.map((entry) => {
|
|
18
|
+
const seeded = defaults.find((candidate) => candidate.value === entry.value && candidate.label === entry.label);
|
|
19
|
+
const renewalQuarter = options.kind === "renewal_quarter" ? /^(\d{4})_q([1-4])$/.exec(entry.value) : null;
|
|
20
|
+
const label = renewalQuarter && entry.label === `Q${renewalQuarter[2]} ${renewalQuarter[1]}` ? translate("customers.dictionaries.defaults.renewal_quarter.label", entry.label, {
|
|
21
|
+
year: renewalQuarter[1],
|
|
22
|
+
quarter: renewalQuarter[2]
|
|
23
|
+
}) : seeded ? translate(customerDictionaryLabelKey(options.kind, seeded.value), entry.label) : entry.label;
|
|
24
|
+
return {
|
|
25
|
+
...entry,
|
|
26
|
+
label
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
const { overlay } = getTranslationOverlayPlugin();
|
|
30
|
+
if (!overlay) return localized;
|
|
31
|
+
const organizationIds = new Set(localized.map((entry) => entry.organizationId));
|
|
32
|
+
const labelsById = /* @__PURE__ */ new Map();
|
|
33
|
+
for (const organizationId of organizationIds) {
|
|
34
|
+
const items = localized.filter((entry) => entry.organizationId === organizationId);
|
|
35
|
+
try {
|
|
36
|
+
const translated = await overlay(items.map(({ id, label }) => ({ id, label })), {
|
|
37
|
+
entityType: "customers:customer_dictionary_entry",
|
|
38
|
+
locale: options.locale,
|
|
39
|
+
tenantId: options.tenantId,
|
|
40
|
+
organizationId,
|
|
41
|
+
container: options.container
|
|
42
|
+
});
|
|
43
|
+
for (const item of translated) {
|
|
44
|
+
if (typeof item.id === "string" && typeof item.label === "string" && item.label.trim()) {
|
|
45
|
+
labelsById.set(item.id, item.label);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
getTelemetryRuntime()?.reportError(error, { module: "customers", code: "customers.dictionary_translation_failed" });
|
|
50
|
+
logger.warn("Translation overlay failed", { error });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return localized.map((entry) => ({ ...entry, label: labelsById.get(entry.id) ?? entry.label }));
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
customerDictionaryLabelKey,
|
|
57
|
+
localizeCustomerDictionaryEntries
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=dictionaryLabels.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/customers/lib/dictionaryLabels.ts"],
|
|
4
|
+
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { Locale } from '@open-mercato/shared/lib/i18n/config'\nimport { loadDictionary } from '@open-mercato/shared/lib/i18n/server'\nimport { createFallbackTranslator } from '@open-mercato/shared/lib/i18n/translate'\nimport { getTranslationOverlayPlugin } from '@open-mercato/shared/lib/localization/overlay-plugin'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { CUSTOMER_DICTIONARY_DEFAULTS } from './dictionaryDefaults'\n\nconst logger = createLogger('customers').child({ component: 'dictionary-labels' })\n\ntype DictionaryLabelEntry = {\n id: string\n value: string\n label: string\n organizationId: string\n}\n\nexport function customerDictionaryLabelKey(kind: string, value: string): string {\n const token = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '')\n return `customers.dictionaries.defaults.${kind}.${token}`\n}\n\nexport async function localizeCustomerDictionaryEntries<T extends DictionaryLabelEntry>(\n entries: T[],\n options: { kind: string; locale: string; tenantId: string; container: AwilixContainer },\n): Promise<T[]> {\n if (!entries.length) return []\n const dictionaryLocale = options.locale.toLowerCase().split('-')[0] as Locale\n const translate = createFallbackTranslator(await loadDictionary(dictionaryLocale))\n const defaults = Object.prototype.hasOwnProperty.call(CUSTOMER_DICTIONARY_DEFAULTS, options.kind)\n ? CUSTOMER_DICTIONARY_DEFAULTS[options.kind]\n : []\n const localized = entries.map((entry) => {\n const seeded = defaults.find((candidate) => candidate.value === entry.value && candidate.label === entry.label)\n const renewalQuarter = options.kind === 'renewal_quarter' ? /^(\\d{4})_q([1-4])$/.exec(entry.value) : null\n const label = renewalQuarter && entry.label === `Q${renewalQuarter[2]} ${renewalQuarter[1]}`\n ? translate('customers.dictionaries.defaults.renewal_quarter.label', entry.label, {\n year: renewalQuarter[1], quarter: renewalQuarter[2],\n })\n : seeded ? translate(customerDictionaryLabelKey(options.kind, seeded.value), entry.label) : entry.label\n return {\n ...entry,\n label,\n }\n })\n const { overlay } = getTranslationOverlayPlugin()\n if (!overlay) return localized\n\n const organizationIds = new Set(localized.map((entry) => entry.organizationId))\n const labelsById = new Map<string, string>()\n for (const organizationId of organizationIds) {\n const items = localized.filter((entry) => entry.organizationId === organizationId)\n try {\n const translated = await overlay(items.map(({ id, label }) => ({ id, label })), {\n entityType: 'customers:customer_dictionary_entry',\n locale: options.locale,\n tenantId: options.tenantId,\n organizationId,\n container: options.container,\n })\n for (const item of translated) {\n if (typeof item.id === 'string' && typeof item.label === 'string' && item.label.trim()) {\n labelsById.set(item.id, item.label)\n }\n }\n } catch (error) {\n getTelemetryRuntime()?.reportError(error, { module: 'customers', code: 'customers.dictionary_translation_failed' })\n logger.warn('Translation overlay failed', { error })\n }\n }\n return localized.map((entry) => ({ ...entry, label: labelsById.get(entry.id) ?? entry.label }))\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,sBAAsB;AAC/B,SAAS,gCAAgC;AACzC,SAAS,mCAAmC;AAC5C,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,oCAAoC;AAE7C,MAAM,SAAS,aAAa,WAAW,EAAE,MAAM,EAAE,WAAW,oBAAoB,CAAC;AAS1E,SAAS,2BAA2B,MAAc,OAAuB;AAC9E,QAAM,QAAQ,MAAM,KAAK,EAAE,YAAY,EAAE,QAAQ,eAAe,GAAG,EAAE,QAAQ,UAAU,EAAE;AACzF,SAAO,mCAAmC,IAAI,IAAI,KAAK;AACzD;AAEA,eAAsB,kCACpB,SACA,SACc;AACd,MAAI,CAAC,QAAQ,OAAQ,QAAO,CAAC;AAC7B,QAAM,mBAAmB,QAAQ,OAAO,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAClE,QAAM,YAAY,yBAAyB,MAAM,eAAe,gBAAgB,CAAC;AACjF,QAAM,WAAW,OAAO,UAAU,eAAe,KAAK,8BAA8B,QAAQ,IAAI,IAC5F,6BAA6B,QAAQ,IAAI,IACzC,CAAC;AACL,QAAM,YAAY,QAAQ,IAAI,CAAC,UAAU;AACvC,UAAM,SAAS,SAAS,KAAK,CAAC,cAAc,UAAU,UAAU,MAAM,SAAS,UAAU,UAAU,MAAM,KAAK;AAC9G,UAAM,iBAAiB,QAAQ,SAAS,oBAAoB,qBAAqB,KAAK,MAAM,KAAK,IAAI;AACrG,UAAM,QAAQ,kBAAkB,MAAM,UAAU,IAAI,eAAe,CAAC,CAAC,IAAI,eAAe,CAAC,CAAC,KACtF,UAAU,yDAAyD,MAAM,OAAO;AAAA,MAC9E,MAAM,eAAe,CAAC;AAAA,MAAG,SAAS,eAAe,CAAC;AAAA,IACpD,CAAC,IACD,SAAS,UAAU,2BAA2B,QAAQ,MAAM,OAAO,KAAK,GAAG,MAAM,KAAK,IAAI,MAAM;AACpG,WAAO;AAAA,MACL,GAAG;AAAA,MACH;AAAA,IACF;AAAA,EACF,CAAC;AACD,QAAM,EAAE,QAAQ,IAAI,4BAA4B;AAChD,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,kBAAkB,IAAI,IAAI,UAAU,IAAI,CAAC,UAAU,MAAM,cAAc,CAAC;AAC9E,QAAM,aAAa,oBAAI,IAAoB;AAC3C,aAAW,kBAAkB,iBAAiB;AAC5C,UAAM,QAAQ,UAAU,OAAO,CAAC,UAAU,MAAM,mBAAmB,cAAc;AACjF,QAAI;AACF,YAAM,aAAa,MAAM,QAAQ,MAAM,IAAI,CAAC,EAAE,IAAI,MAAM,OAAO,EAAE,IAAI,MAAM,EAAE,GAAG;AAAA,QAC9E,YAAY;AAAA,QACZ,QAAQ,QAAQ;AAAA,QAChB,UAAU,QAAQ;AAAA,QAClB;AAAA,QACA,WAAW,QAAQ;AAAA,MACrB,CAAC;AACD,iBAAW,QAAQ,YAAY;AAC7B,YAAI,OAAO,KAAK,OAAO,YAAY,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,KAAK,GAAG;AACtF,qBAAW,IAAI,KAAK,IAAI,KAAK,KAAK;AAAA,QACpC;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,0BAAoB,GAAG,YAAY,OAAO,EAAE,QAAQ,aAAa,MAAM,0CAA0C,CAAC;AAClH,aAAO,KAAK,8BAA8B,EAAE,MAAM,CAAC;AAAA,IACrD;AAAA,EACF;AACA,SAAO,UAAU,IAAI,CAAC,WAAW,EAAE,GAAG,OAAO,OAAO,WAAW,IAAI,MAAM,EAAE,KAAK,MAAM,MAAM,EAAE;AAChG;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -47,6 +47,7 @@ function mapInteractionRecordToTodoSummary(interaction) {
|
|
|
47
47
|
todoId: interaction.id,
|
|
48
48
|
todoSource: CUSTOMER_INTERACTION_TASK_SOURCE,
|
|
49
49
|
createdAt: interaction.createdAt,
|
|
50
|
+
updatedAt: interaction.updatedAt,
|
|
50
51
|
title: interaction.title ?? null,
|
|
51
52
|
isDone: interaction.status === "done",
|
|
52
53
|
status: interaction.status,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customers/lib/interactionCompatibility.ts"],
|
|
4
|
-
"sourcesContent": ["import type { InteractionSummary, ActivitySummary, TodoLinkSummary } from '../components/detail/types'\n\nexport const CUSTOMER_INTERACTION_ENTITY_ID = 'customers:customer_interaction'\nexport const CUSTOMER_INTERACTION_TASK_SOURCE = 'customers:interaction'\nexport const CUSTOMER_INTERACTION_TASK_TYPE = 'task'\nexport const CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE = 'adapter:activity'\nexport const CUSTOMER_INTERACTION_TODO_ADAPTER_SOURCE = 'adapter:todo'\nexport const EXAMPLE_TODO_SOURCE = 'example:todo'\n\nexport type InteractionRecord = InteractionSummary & {\n authorName?: string | null\n authorEmail?: string | null\n dealTitle?: string | null\n customValues?: Record<string, unknown> | null\n _integrations?: Record<string, unknown>\n}\n\nexport function isTaskInteractionType(value: string | null | undefined): boolean {\n return typeof value === 'string' && value.trim().toLowerCase() === CUSTOMER_INTERACTION_TASK_TYPE\n}\n\nexport function isAdapterActivitySource(value: string | null | undefined): boolean {\n return value === CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE\n}\n\nexport function isAdapterTodoSource(value: string | null | undefined): boolean {\n return value === CUSTOMER_INTERACTION_TODO_ADAPTER_SOURCE\n}\n\nexport function mapInteractionRecordToActivitySummary(interaction: InteractionRecord): ActivitySummary {\n return {\n id: interaction.id,\n activityType: interaction.interactionType,\n subject: interaction.title ?? null,\n body: interaction.body ?? null,\n occurredAt: interaction.occurredAt ?? interaction.scheduledAt ?? null,\n createdAt: interaction.createdAt,\n appearanceIcon: interaction.appearanceIcon ?? null,\n appearanceColor: interaction.appearanceColor ?? null,\n entityId: interaction.entityId ?? null,\n authorUserId: interaction.authorUserId ?? null,\n authorName: interaction.authorName ?? null,\n authorEmail: interaction.authorEmail ?? null,\n dealId: interaction.dealId ?? null,\n dealTitle: interaction.dealTitle ?? null,\n customValues: interaction.customValues ?? null,\n }\n}\n\nexport function mapInteractionRecordToTodoSummary(interaction: InteractionRecord): TodoLinkSummary {\n const customValues: Record<string, unknown> = { ...(interaction.customValues ?? {}) }\n const externalHref = resolveExampleIntegrationHref(interaction)\n if (interaction.priority !== undefined) customValues.priority = interaction.priority\n if (interaction.body !== undefined && customValues.description === undefined) {\n customValues.description = interaction.body ?? null\n }\n if (interaction.scheduledAt !== undefined && customValues.due_at === undefined) {\n customValues.due_at = interaction.scheduledAt ?? null\n }\n\n return {\n id: interaction.id,\n todoId: interaction.id,\n todoSource: CUSTOMER_INTERACTION_TASK_SOURCE,\n createdAt: interaction.createdAt,\n title: interaction.title ?? null,\n isDone: interaction.status === 'done',\n status: interaction.status,\n priority: interaction.priority ?? null,\n severity:\n typeof customValues.severity === 'string' && customValues.severity.trim().length\n ? customValues.severity.trim()\n : null,\n description: interaction.body ?? null,\n dueAt: interaction.scheduledAt ?? null,\n todoOrganizationId: null,\n customValues: Object.keys(customValues).length > 0 ? customValues : null,\n externalHref,\n }\n}\n\ntype IntegrationCarrier = {\n _integrations?: {\n example?: { href?: string | null; syncStatus?: string | null; [key: string]: unknown }\n [key: string]: unknown\n }\n}\n\nexport function resolveExampleIntegrationHref(item: IntegrationCarrier): string | null {\n const example = item._integrations?.example\n if (!example || typeof example !== 'object') return null\n return typeof example.href === 'string' && example.href.trim().length > 0 ? example.href : null\n}\n"],
|
|
5
|
-
"mappings": "AAEO,MAAM,iCAAiC;AACvC,MAAM,mCAAmC;AACzC,MAAM,iCAAiC;AACvC,MAAM,+CAA+C;AACrD,MAAM,2CAA2C;AACjD,MAAM,sBAAsB;AAU5B,SAAS,sBAAsB,OAA2C;AAC/E,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,YAAY,MAAM;AACrE;AAEO,SAAS,wBAAwB,OAA2C;AACjF,SAAO,UAAU;AACnB;AAEO,SAAS,oBAAoB,OAA2C;AAC7E,SAAO,UAAU;AACnB;AAEO,SAAS,sCAAsC,aAAiD;AACrG,SAAO;AAAA,IACL,IAAI,YAAY;AAAA,IAChB,cAAc,YAAY;AAAA,IAC1B,SAAS,YAAY,SAAS;AAAA,IAC9B,MAAM,YAAY,QAAQ;AAAA,IAC1B,YAAY,YAAY,cAAc,YAAY,eAAe;AAAA,IACjE,WAAW,YAAY;AAAA,IACvB,gBAAgB,YAAY,kBAAkB;AAAA,IAC9C,iBAAiB,YAAY,mBAAmB;AAAA,IAChD,UAAU,YAAY,YAAY;AAAA,IAClC,cAAc,YAAY,gBAAgB;AAAA,IAC1C,YAAY,YAAY,cAAc;AAAA,IACtC,aAAa,YAAY,eAAe;AAAA,IACxC,QAAQ,YAAY,UAAU;AAAA,IAC9B,WAAW,YAAY,aAAa;AAAA,IACpC,cAAc,YAAY,gBAAgB;AAAA,EAC5C;AACF;AAEO,SAAS,kCAAkC,aAAiD;AACjG,QAAM,eAAwC,EAAE,GAAI,YAAY,gBAAgB,CAAC,EAAG;AACpF,QAAM,eAAe,8BAA8B,WAAW;AAC9D,MAAI,YAAY,aAAa,OAAW,cAAa,WAAW,YAAY;AAC5E,MAAI,YAAY,SAAS,UAAa,aAAa,gBAAgB,QAAW;AAC5E,iBAAa,cAAc,YAAY,QAAQ;AAAA,EACjD;AACA,MAAI,YAAY,gBAAgB,UAAa,aAAa,WAAW,QAAW;AAC9E,iBAAa,SAAS,YAAY,eAAe;AAAA,EACnD;AAEA,SAAO;AAAA,IACL,IAAI,YAAY;AAAA,IAChB,QAAQ,YAAY;AAAA,IACpB,YAAY;AAAA,IACZ,WAAW,YAAY;AAAA,IACvB,OAAO,YAAY,SAAS;AAAA,IAC5B,QAAQ,YAAY,WAAW;AAAA,IAC/B,QAAQ,YAAY;AAAA,IACpB,UAAU,YAAY,YAAY;AAAA,IAClC,UACE,OAAO,aAAa,aAAa,YAAY,aAAa,SAAS,KAAK,EAAE,SACtE,aAAa,SAAS,KAAK,IAC3B;AAAA,IACN,aAAa,YAAY,QAAQ;AAAA,IACjC,OAAO,YAAY,eAAe;AAAA,IAClC,oBAAoB;AAAA,IACpB,cAAc,OAAO,KAAK,YAAY,EAAE,SAAS,IAAI,eAAe;AAAA,IACpE;AAAA,EACF;AACF;AASO,SAAS,8BAA8B,MAAyC;AACrF,QAAM,UAAU,KAAK,eAAe;AACpC,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,SAAO,OAAO,QAAQ,SAAS,YAAY,QAAQ,KAAK,KAAK,EAAE,SAAS,IAAI,QAAQ,OAAO;AAC7F;",
|
|
4
|
+
"sourcesContent": ["import type { InteractionSummary, ActivitySummary, TodoLinkSummary } from '../components/detail/types'\n\nexport const CUSTOMER_INTERACTION_ENTITY_ID = 'customers:customer_interaction'\nexport const CUSTOMER_INTERACTION_TASK_SOURCE = 'customers:interaction'\nexport const CUSTOMER_INTERACTION_TASK_TYPE = 'task'\nexport const CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE = 'adapter:activity'\nexport const CUSTOMER_INTERACTION_TODO_ADAPTER_SOURCE = 'adapter:todo'\nexport const EXAMPLE_TODO_SOURCE = 'example:todo'\n\nexport type InteractionRecord = InteractionSummary & {\n authorName?: string | null\n authorEmail?: string | null\n dealTitle?: string | null\n customValues?: Record<string, unknown> | null\n _integrations?: Record<string, unknown>\n}\n\nexport function isTaskInteractionType(value: string | null | undefined): boolean {\n return typeof value === 'string' && value.trim().toLowerCase() === CUSTOMER_INTERACTION_TASK_TYPE\n}\n\nexport function isAdapterActivitySource(value: string | null | undefined): boolean {\n return value === CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE\n}\n\nexport function isAdapterTodoSource(value: string | null | undefined): boolean {\n return value === CUSTOMER_INTERACTION_TODO_ADAPTER_SOURCE\n}\n\nexport function mapInteractionRecordToActivitySummary(interaction: InteractionRecord): ActivitySummary {\n return {\n id: interaction.id,\n activityType: interaction.interactionType,\n subject: interaction.title ?? null,\n body: interaction.body ?? null,\n occurredAt: interaction.occurredAt ?? interaction.scheduledAt ?? null,\n createdAt: interaction.createdAt,\n appearanceIcon: interaction.appearanceIcon ?? null,\n appearanceColor: interaction.appearanceColor ?? null,\n entityId: interaction.entityId ?? null,\n authorUserId: interaction.authorUserId ?? null,\n authorName: interaction.authorName ?? null,\n authorEmail: interaction.authorEmail ?? null,\n dealId: interaction.dealId ?? null,\n dealTitle: interaction.dealTitle ?? null,\n customValues: interaction.customValues ?? null,\n }\n}\n\nexport function mapInteractionRecordToTodoSummary(interaction: InteractionRecord): TodoLinkSummary {\n const customValues: Record<string, unknown> = { ...(interaction.customValues ?? {}) }\n const externalHref = resolveExampleIntegrationHref(interaction)\n if (interaction.priority !== undefined) customValues.priority = interaction.priority\n if (interaction.body !== undefined && customValues.description === undefined) {\n customValues.description = interaction.body ?? null\n }\n if (interaction.scheduledAt !== undefined && customValues.due_at === undefined) {\n customValues.due_at = interaction.scheduledAt ?? null\n }\n\n return {\n id: interaction.id,\n todoId: interaction.id,\n todoSource: CUSTOMER_INTERACTION_TASK_SOURCE,\n createdAt: interaction.createdAt,\n updatedAt: interaction.updatedAt,\n title: interaction.title ?? null,\n isDone: interaction.status === 'done',\n status: interaction.status,\n priority: interaction.priority ?? null,\n severity:\n typeof customValues.severity === 'string' && customValues.severity.trim().length\n ? customValues.severity.trim()\n : null,\n description: interaction.body ?? null,\n dueAt: interaction.scheduledAt ?? null,\n todoOrganizationId: null,\n customValues: Object.keys(customValues).length > 0 ? customValues : null,\n externalHref,\n }\n}\n\ntype IntegrationCarrier = {\n _integrations?: {\n example?: { href?: string | null; syncStatus?: string | null; [key: string]: unknown }\n [key: string]: unknown\n }\n}\n\nexport function resolveExampleIntegrationHref(item: IntegrationCarrier): string | null {\n const example = item._integrations?.example\n if (!example || typeof example !== 'object') return null\n return typeof example.href === 'string' && example.href.trim().length > 0 ? example.href : null\n}\n"],
|
|
5
|
+
"mappings": "AAEO,MAAM,iCAAiC;AACvC,MAAM,mCAAmC;AACzC,MAAM,iCAAiC;AACvC,MAAM,+CAA+C;AACrD,MAAM,2CAA2C;AACjD,MAAM,sBAAsB;AAU5B,SAAS,sBAAsB,OAA2C;AAC/E,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,YAAY,MAAM;AACrE;AAEO,SAAS,wBAAwB,OAA2C;AACjF,SAAO,UAAU;AACnB;AAEO,SAAS,oBAAoB,OAA2C;AAC7E,SAAO,UAAU;AACnB;AAEO,SAAS,sCAAsC,aAAiD;AACrG,SAAO;AAAA,IACL,IAAI,YAAY;AAAA,IAChB,cAAc,YAAY;AAAA,IAC1B,SAAS,YAAY,SAAS;AAAA,IAC9B,MAAM,YAAY,QAAQ;AAAA,IAC1B,YAAY,YAAY,cAAc,YAAY,eAAe;AAAA,IACjE,WAAW,YAAY;AAAA,IACvB,gBAAgB,YAAY,kBAAkB;AAAA,IAC9C,iBAAiB,YAAY,mBAAmB;AAAA,IAChD,UAAU,YAAY,YAAY;AAAA,IAClC,cAAc,YAAY,gBAAgB;AAAA,IAC1C,YAAY,YAAY,cAAc;AAAA,IACtC,aAAa,YAAY,eAAe;AAAA,IACxC,QAAQ,YAAY,UAAU;AAAA,IAC9B,WAAW,YAAY,aAAa;AAAA,IACpC,cAAc,YAAY,gBAAgB;AAAA,EAC5C;AACF;AAEO,SAAS,kCAAkC,aAAiD;AACjG,QAAM,eAAwC,EAAE,GAAI,YAAY,gBAAgB,CAAC,EAAG;AACpF,QAAM,eAAe,8BAA8B,WAAW;AAC9D,MAAI,YAAY,aAAa,OAAW,cAAa,WAAW,YAAY;AAC5E,MAAI,YAAY,SAAS,UAAa,aAAa,gBAAgB,QAAW;AAC5E,iBAAa,cAAc,YAAY,QAAQ;AAAA,EACjD;AACA,MAAI,YAAY,gBAAgB,UAAa,aAAa,WAAW,QAAW;AAC9E,iBAAa,SAAS,YAAY,eAAe;AAAA,EACnD;AAEA,SAAO;AAAA,IACL,IAAI,YAAY;AAAA,IAChB,QAAQ,YAAY;AAAA,IACpB,YAAY;AAAA,IACZ,WAAW,YAAY;AAAA,IACvB,WAAW,YAAY;AAAA,IACvB,OAAO,YAAY,SAAS;AAAA,IAC5B,QAAQ,YAAY,WAAW;AAAA,IAC/B,QAAQ,YAAY;AAAA,IACpB,UAAU,YAAY,YAAY;AAAA,IAClC,UACE,OAAO,aAAa,aAAa,YAAY,aAAa,SAAS,KAAK,EAAE,SACtE,aAAa,SAAS,KAAK,IAC3B;AAAA,IACN,aAAa,YAAY,QAAQ;AAAA,IACjC,OAAO,YAAY,eAAe;AAAA,IAClC,oBAAoB;AAAA,IACpB,cAAc,OAAO,KAAK,YAAY,EAAE,SAAS,IAAI,eAAe;AAAA,IACpE;AAAA,EACF;AACF;AASO,SAAS,8BAA8B,MAAyC;AACrF,QAAM,UAAU,KAAK,eAAe;AACpC,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,SAAO,OAAO,QAAQ,SAAS,YAAY,QAAQ,KAAK,KAAK,EAAE,SAAS,IAAI,QAAQ,OAAO;AAC7F;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/modules/customers/translations.ts"],
|
|
4
|
+
"sourcesContent": ["export const translatableFields: Record<string, string[]> = {\n 'customers:customer_dictionary_entry': ['label'],\n}\n\nexport default translatableFields\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,qBAA+C;AAAA,EAC1D,uCAAuC,CAAC,OAAO;AACjD;AAEA,IAAO,uBAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -5,6 +5,19 @@ const metadata = {
|
|
|
5
5
|
description: "EU Deforestation Regulation compliance: product commodity mappings, supplier origin evidence, due diligence statements.",
|
|
6
6
|
author: "Open Mercato Team",
|
|
7
7
|
license: "MIT",
|
|
8
|
+
/**
|
|
9
|
+
* `catalog` is a HARD dependency, and saying so is the fix.
|
|
10
|
+
*
|
|
11
|
+
* `data/enrichers.ts` reads `E.catalog.catalog_product` at module scope, so with `catalog` disabled
|
|
12
|
+
* `E.catalog` is undefined and the read throws while the enricher registry is being imported — which takes
|
|
13
|
+
* `/api/auth/login` with it. An installation shaped as a CRM rather than a shop could not reach its own login
|
|
14
|
+
* screen, and the error named a property rather than a module.
|
|
15
|
+
*
|
|
16
|
+
* Declared rather than defended with optional chaining. This module exists to map PRODUCTS to deforestation
|
|
17
|
+
* compliance evidence, so without a catalogue it has nothing to work on at all — the honest answer is that it
|
|
18
|
+
* cannot be enabled, which the generator now says before anybody starts the app rather than after.
|
|
19
|
+
*/
|
|
20
|
+
requires: ["catalog"],
|
|
8
21
|
ejectable: true
|
|
9
22
|
};
|
|
10
23
|
import { features } from "./acl.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/eudr/index.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ModuleInfo } from '@open-mercato/shared/modules/registry'\n\nexport const metadata: ModuleInfo = {\n name: 'eudr',\n title: 'EUDR Compliance',\n version: '0.1.0',\n description: 'EU Deforestation Regulation compliance: product commodity mappings, supplier origin evidence, due diligence statements.',\n author: 'Open Mercato Team',\n license: 'MIT',\n ejectable: true,\n}\n\nexport { features } from './acl'\n"],
|
|
5
|
-
"mappings": "AAEO,MAAM,WAAuB;AAAA,EAClC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AAAA,EACT,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,SAAS;AAAA,
|
|
4
|
+
"sourcesContent": ["import type { ModuleInfo } from '@open-mercato/shared/modules/registry'\n\nexport const metadata: ModuleInfo = {\n name: 'eudr',\n title: 'EUDR Compliance',\n version: '0.1.0',\n description: 'EU Deforestation Regulation compliance: product commodity mappings, supplier origin evidence, due diligence statements.',\n author: 'Open Mercato Team',\n license: 'MIT',\n /**\n * `catalog` is a HARD dependency, and saying so is the fix.\n *\n * `data/enrichers.ts` reads `E.catalog.catalog_product` at module scope, so with `catalog` disabled\n * `E.catalog` is undefined and the read throws while the enricher registry is being imported \u2014 which takes\n * `/api/auth/login` with it. An installation shaped as a CRM rather than a shop could not reach its own login\n * screen, and the error named a property rather than a module.\n *\n * Declared rather than defended with optional chaining. This module exists to map PRODUCTS to deforestation\n * compliance evidence, so without a catalogue it has nothing to work on at all \u2014 the honest answer is that it\n * cannot be enabled, which the generator now says before anybody starts the app rather than after.\n */\n requires: ['catalog'],\n ejectable: true,\n}\n\nexport { features } from './acl'\n"],
|
|
5
|
+
"mappings": "AAEO,MAAM,WAAuB;AAAA,EAClC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AAAA,EACT,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaT,UAAU,CAAC,SAAS;AAAA,EACpB,WAAW;AACb;AAEA,SAAS,gBAAgB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
|
+
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
5
6
|
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
6
7
|
import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
|
|
7
8
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
@@ -39,7 +40,8 @@ async function resolveAccessRequestContext(req) {
|
|
|
39
40
|
if (!requesterUserId) {
|
|
40
41
|
throw new CrudHttpError(401, { error: translate("staff.errors.unauthorized", "Unauthorized") });
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
|
|
44
|
+
if (!scope.tenantId || !scope.selectedId || scope.selectionRejected === true) {
|
|
43
45
|
throw new CrudHttpError(400, {
|
|
44
46
|
error: translate("staff.errors.missingScope", "Missing tenant or organization scope.")
|
|
45
47
|
});
|
|
@@ -47,8 +49,8 @@ async function resolveAccessRequestContext(req) {
|
|
|
47
49
|
return {
|
|
48
50
|
container,
|
|
49
51
|
em: container.resolve("em").fork(),
|
|
50
|
-
tenantId:
|
|
51
|
-
organizationId:
|
|
52
|
+
tenantId: scope.tenantId,
|
|
53
|
+
organizationId: scope.selectedId,
|
|
52
54
|
requesterUserId,
|
|
53
55
|
requesterEmail: typeof auth.email === "string" && auth.email.trim().length > 0 ? auth.email : null,
|
|
54
56
|
translate
|
|
@@ -101,10 +103,14 @@ async function resolveRequesterName(context) {
|
|
|
101
103
|
async function POST(req) {
|
|
102
104
|
try {
|
|
103
105
|
const context = await resolveAccessRequestContext(req);
|
|
104
|
-
const
|
|
106
|
+
const featureAccess = await resolveFeatureAccess(context.container, context.requesterUserId, [VIEW_FEATURE], {
|
|
105
107
|
tenantId: context.tenantId,
|
|
106
108
|
organizationId: context.organizationId
|
|
107
|
-
})
|
|
109
|
+
});
|
|
110
|
+
if (!featureAccess.allowed) {
|
|
111
|
+
throw new CrudHttpError(403, { error: context.translate("staff.errors.forbidden", "Forbidden") });
|
|
112
|
+
}
|
|
113
|
+
const grantedFeatures = featureAccess.grantedFeatures;
|
|
108
114
|
const interceptors = await runTimesheetInterceptors({
|
|
109
115
|
request: req,
|
|
110
116
|
method: "POST",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/staff/api/timesheets/access-requests/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { runRouteMutationGuards } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { StaffTeamMember, StaffTimeProject } from '../../../data/entities'\nimport { emitStaffEvent } from '../../../events'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { STAFF_TIME_TRACKING_RESOURCE_KINDS } from '../../guards'\nimport { runTimesheetInterceptors } from '../_shared/withTimesheetInterceptors'\nimport { resolveFeatureAccess } from '../../../lib/time-tracking/featureAccess'\n\nconst logger = createLogger('staff').child({ component: 'api/timesheets/access-requests' })\n\nconst VIEW_FEATURE = 'staff.timesheets.view'\nconst RESOURCE_KIND = STAFF_TIME_TRACKING_RESOURCE_KINDS.accessRequest\n\n/**\n * Spec D-6: one pending request per user per project (or per user for the\n * general, project-less request) per 24h. The window is held in the shared\n * cache rather than a table \u2014 the notification itself carries a `groupKey`, so\n * even if the cache is cold the manager still sees a single refreshed\n * notification instead of a duplicate row.\n */\nexport const ACCESS_REQUEST_DEDUPE_WINDOW_MS = 24 * 60 * 60 * 1000\n\nconst GENERAL_REQUEST_SCOPE = 'general'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: [VIEW_FEATURE] },\n}\n\nconst accessRequestSchema = z.object({\n timeProjectId: z.string().uuid().nullish(),\n})\n\nconst accessRequestResponseSchema = z.object({\n ok: z.literal(true),\n deduplicated: z.boolean(),\n})\n\ntype DedupeCacheLike = {\n get: (key: string) => Promise<unknown>\n set: (key: string, value: unknown, options?: { ttl?: number }) => Promise<void>\n}\n\ntype AccessRequestContext = {\n container: Awaited<ReturnType<typeof createRequestContainer>>\n em: EntityManager\n tenantId: string\n organizationId: string\n requesterUserId: string\n requesterEmail: string | null\n translate: (key: string, fallback?: string) => string\n}\n\nasync function resolveAccessRequestContext(req: Request): Promise<AccessRequestContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n }\n const requesterUserId =\n (typeof auth.sub === 'string' && auth.sub.trim().length > 0 && auth.sub) ||\n (typeof auth.userId === 'string' && auth.userId.trim().length > 0 && auth.userId) ||\n null\n if (!requesterUserId) {\n throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n }\n if (!auth.orgId) {\n throw new CrudHttpError(400, {\n error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.'),\n })\n }\n return {\n container,\n em: (container.resolve('em') as EntityManager).fork(),\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n requesterUserId,\n requesterEmail: typeof auth.email === 'string' && auth.email.trim().length > 0 ? auth.email : null,\n translate,\n }\n}\n\nfunction resolveDedupeCache(context: AccessRequestContext): DedupeCacheLike | null {\n try {\n const cache = context.container.resolve('cache') as Partial<DedupeCacheLike> | undefined\n if (typeof cache?.get !== 'function' || typeof cache?.set !== 'function') return null\n return cache as DedupeCacheLike\n } catch {\n return null\n }\n}\n\nexport function buildAccessRequestDedupeKey(input: {\n tenantId: string\n organizationId: string\n requesterUserId: string\n timeProjectId: string | null\n}): string {\n const scope = input.timeProjectId ?? GENERAL_REQUEST_SCOPE\n return `staff:timesheets:access-request:${input.tenantId}:${input.organizationId}:${input.requesterUserId}:${scope}`\n}\n\n/**\n * Resolves the requested project inside the caller's scope. A project the\n * caller's tenant/organization does not own resolves to `null` and the request\n * degrades to a general one \u2014 the response never differentiates, so the route\n * cannot be used to probe which project ids exist (screen 17 note 1).\n */\nasync function resolveRequestedProject(\n context: AccessRequestContext,\n timeProjectId: string | null,\n): Promise<{ id: string; name: string } | null> {\n if (!timeProjectId) return null\n const scope = { tenantId: context.tenantId, organizationId: context.organizationId }\n const project = await findOneWithDecryption(\n context.em,\n StaffTimeProject,\n { id: timeProjectId, tenantId: context.tenantId, organizationId: context.organizationId, deletedAt: null },\n {},\n scope,\n )\n if (!project) return null\n return { id: project.id, name: project.name }\n}\n\nasync function resolveRequesterName(context: AccessRequestContext): Promise<string> {\n const scope = { tenantId: context.tenantId, organizationId: context.organizationId }\n const staffMember = await findOneWithDecryption(\n context.em,\n StaffTeamMember,\n {\n userId: context.requesterUserId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n deletedAt: null,\n },\n {},\n scope,\n )\n const displayName = staffMember?.displayName\n if (typeof displayName === 'string' && displayName.trim().length > 0) return displayName\n return context.requesterEmail ?? context.requesterUserId\n}\n\nasync function POST(req: Request) {\n try {\n const context = await resolveAccessRequestContext(req)\n // The grant list the interceptors and the mutation-guard registry gate on\n // comes from the module's single RBAC authority, so it is always an array:\n // the nullable read it replaces could not say whether an empty answer meant\n // \"no grants\" or \"could not ask\". Authorization itself stays with the\n // declarative `requireFeatures` guard in `metadata`.\n const grantedFeatures = (\n await resolveFeatureAccess(context.container, context.requesterUserId, [VIEW_FEATURE], {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n })\n ).grantedFeatures\n\n const interceptors = await runTimesheetInterceptors({\n request: req,\n method: 'POST',\n scope: {\n container: context.container,\n userId: context.requesterUserId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: grantedFeatures,\n },\n body: await readJsonSafe<Record<string, unknown>>(req, {}),\n })\n if (!interceptors.ok) return interceptors.response\n const { session } = interceptors\n\n const parsed = accessRequestSchema.parse(session.body)\n\n const guardResult = await runRouteMutationGuards({\n container: context.container,\n req,\n auth: {\n userId: context.requesterUserId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: grantedFeatures,\n },\n input: {\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.timeProjectId ?? GENERAL_REQUEST_SCOPE,\n operation: 'create',\n mutationPayload: { timeProjectId: parsed.timeProjectId ?? null },\n },\n })\n if (!guardResult.ok) return guardResult.response\n\n const effective = guardResult.modifiedPayload\n ? accessRequestSchema.parse({ ...parsed, ...guardResult.modifiedPayload })\n : parsed\n\n const project = await resolveRequestedProject(context, effective.timeProjectId ?? null)\n const dedupeKey = buildAccessRequestDedupeKey({\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n requesterUserId: context.requesterUserId,\n timeProjectId: project?.id ?? null,\n })\n\n const cache = resolveDedupeCache(context)\n if (cache) {\n const pending = await cache.get(dedupeKey).catch(() => null)\n if (pending != null) {\n await guardResult.runAfterSuccess()\n return session.respond(200, { ok: true, deduplicated: true })\n }\n }\n\n const requesterName = await resolveRequesterName(context)\n await emitStaffEvent('staff.timesheets.project_access.requested', {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n requesterUserId: context.requesterUserId,\n requesterName,\n timeProjectId: project?.id ?? null,\n timeProjectName: project?.name ?? null,\n requestedAt: new Date().toISOString(),\n })\n\n if (cache) {\n await cache\n .set(dedupeKey, { requestedAt: Date.now() }, { ttl: ACCESS_REQUEST_DEDUPE_WINDOW_MS })\n .catch((err: unknown) => {\n logger.warn('Failed to record the access-request dedupe window', { err })\n })\n }\n\n await guardResult.runAfterSuccess()\n\n return session.respond(200, { ok: true, deduplicated: false })\n } catch (err) {\n return errorResponse(err, 'staff.timesheets.access-requests.POST failed')\n }\n}\n\nasync function errorResponse(err: unknown, message: string) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n if (err instanceof z.ZodError) {\n return NextResponse.json(\n { error: translate('staff.errors.invalid_request', 'Invalid request'), details: err.issues },\n { status: 400 },\n )\n }\n logger.error(message, { err })\n return NextResponse.json(\n { error: translate('staff.errors.internal', 'Internal server error') },\n { status: 500 },\n )\n}\n\nconst postDoc: OpenApiMethodDoc = {\n summary: 'Request time tracking project access',\n description:\n 'Notifies every holder of staff.timesheets.projects.manage in the caller scope that a team member asked for project access. Omitting timeProjectId sends a general request from the no-assignments empty state.',\n tags: ['Staff'],\n requestBody: { schema: accessRequestSchema },\n responses: [\n {\n status: 200,\n description: 'Access request accepted (deduplicated when an identical request is still pending)',\n schema: accessRequestResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid request body or missing organization scope' },\n { status: 401, description: 'Unauthorized' },\n { status: 403, description: 'Missing staff.timesheets.view' },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Time tracking project access requests',\n methods: {\n POST: postDoc,\n },\n}\n\nexport { POST }\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,eAAe,uBAAuB;AAC/C,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAC7B,SAAS,6BAA6B;AAEtC,SAAS,iBAAiB,wBAAwB;AAClD,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAC7B,SAAS,0CAA0C;AACnD,SAAS,gCAAgC;AACzC,SAAS,4BAA4B;AAErC,MAAM,SAAS,aAAa,OAAO,EAAE,MAAM,EAAE,WAAW,iCAAiC,CAAC;AAE1F,MAAM,eAAe;AACrB,MAAM,gBAAgB,mCAAmC;AASlD,MAAM,kCAAkC,KAAK,KAAK,KAAK;AAE9D,MAAM,wBAAwB;AAEvB,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,YAAY,EAAE;AAC7D;AAEA,MAAM,sBAAsB,EAAE,OAAO;AAAA,EACnC,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ;AAC3C,CAAC;AAED,MAAM,8BAA8B,EAAE,OAAO;AAAA,EAC3C,IAAI,EAAE,QAAQ,IAAI;AAAA,EAClB,cAAc,EAAE,QAAQ;AAC1B,CAAC;AAiBD,eAAe,4BAA4B,KAA6C;AACtF,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,QAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,MAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,6BAA6B,cAAc,EAAE,CAAC;AAAA,EAChG;AACA,QAAM,kBACH,OAAO,KAAK,QAAQ,YAAY,KAAK,IAAI,KAAK,EAAE,SAAS,KAAK,KAAK,OACnE,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,KAAK,EAAE,SAAS,KAAK,KAAK,UAC1E;AACF,MAAI,CAAC,iBAAiB;AACpB,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,6BAA6B,cAAc,EAAE,CAAC;AAAA,EAChG;AACA,MAAI,CAAC,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { runRouteMutationGuards } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { StaffTeamMember, StaffTimeProject } from '../../../data/entities'\nimport { emitStaffEvent } from '../../../events'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { STAFF_TIME_TRACKING_RESOURCE_KINDS } from '../../guards'\nimport { runTimesheetInterceptors } from '../_shared/withTimesheetInterceptors'\nimport { resolveFeatureAccess } from '../../../lib/time-tracking/featureAccess'\n\nconst logger = createLogger('staff').child({ component: 'api/timesheets/access-requests' })\n\nconst VIEW_FEATURE = 'staff.timesheets.view'\nconst RESOURCE_KIND = STAFF_TIME_TRACKING_RESOURCE_KINDS.accessRequest\n\n/**\n * Spec D-6: one pending request per user per project (or per user for the\n * general, project-less request) per 24h. The window is held in the shared\n * cache rather than a table \u2014 the notification itself carries a `groupKey`, so\n * even if the cache is cold the manager still sees a single refreshed\n * notification instead of a duplicate row.\n */\nexport const ACCESS_REQUEST_DEDUPE_WINDOW_MS = 24 * 60 * 60 * 1000\n\nconst GENERAL_REQUEST_SCOPE = 'general'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: [VIEW_FEATURE] },\n}\n\nconst accessRequestSchema = z.object({\n timeProjectId: z.string().uuid().nullish(),\n})\n\nconst accessRequestResponseSchema = z.object({\n ok: z.literal(true),\n deduplicated: z.boolean(),\n})\n\ntype DedupeCacheLike = {\n get: (key: string) => Promise<unknown>\n set: (key: string, value: unknown, options?: { ttl?: number }) => Promise<void>\n}\n\ntype AccessRequestContext = {\n container: Awaited<ReturnType<typeof createRequestContainer>>\n em: EntityManager\n tenantId: string\n organizationId: string\n requesterUserId: string\n requesterEmail: string | null\n translate: (key: string, fallback?: string) => string\n}\n\nasync function resolveAccessRequestContext(req: Request): Promise<AccessRequestContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n }\n const requesterUserId =\n (typeof auth.sub === 'string' && auth.sub.trim().length > 0 && auth.sub) ||\n (typeof auth.userId === 'string' && auth.userId.trim().length > 0 && auth.userId) ||\n null\n if (!requesterUserId) {\n throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n }\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n if (!scope.tenantId || !scope.selectedId || scope.selectionRejected === true) {\n throw new CrudHttpError(400, {\n error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.'),\n })\n }\n return {\n container,\n em: (container.resolve('em') as EntityManager).fork(),\n tenantId: scope.tenantId,\n organizationId: scope.selectedId,\n requesterUserId,\n requesterEmail: typeof auth.email === 'string' && auth.email.trim().length > 0 ? auth.email : null,\n translate,\n }\n}\n\nfunction resolveDedupeCache(context: AccessRequestContext): DedupeCacheLike | null {\n try {\n const cache = context.container.resolve('cache') as Partial<DedupeCacheLike> | undefined\n if (typeof cache?.get !== 'function' || typeof cache?.set !== 'function') return null\n return cache as DedupeCacheLike\n } catch {\n return null\n }\n}\n\nexport function buildAccessRequestDedupeKey(input: {\n tenantId: string\n organizationId: string\n requesterUserId: string\n timeProjectId: string | null\n}): string {\n const scope = input.timeProjectId ?? GENERAL_REQUEST_SCOPE\n return `staff:timesheets:access-request:${input.tenantId}:${input.organizationId}:${input.requesterUserId}:${scope}`\n}\n\n/**\n * Resolves the requested project inside the caller's scope. A project the\n * caller's tenant/organization does not own resolves to `null` and the request\n * degrades to a general one \u2014 the response never differentiates, so the route\n * cannot be used to probe which project ids exist (screen 17 note 1).\n */\nasync function resolveRequestedProject(\n context: AccessRequestContext,\n timeProjectId: string | null,\n): Promise<{ id: string; name: string } | null> {\n if (!timeProjectId) return null\n const scope = { tenantId: context.tenantId, organizationId: context.organizationId }\n const project = await findOneWithDecryption(\n context.em,\n StaffTimeProject,\n { id: timeProjectId, tenantId: context.tenantId, organizationId: context.organizationId, deletedAt: null },\n {},\n scope,\n )\n if (!project) return null\n return { id: project.id, name: project.name }\n}\n\nasync function resolveRequesterName(context: AccessRequestContext): Promise<string> {\n const scope = { tenantId: context.tenantId, organizationId: context.organizationId }\n const staffMember = await findOneWithDecryption(\n context.em,\n StaffTeamMember,\n {\n userId: context.requesterUserId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n deletedAt: null,\n },\n {},\n scope,\n )\n const displayName = staffMember?.displayName\n if (typeof displayName === 'string' && displayName.trim().length > 0) return displayName\n return context.requesterEmail ?? context.requesterUserId\n}\n\nasync function POST(req: Request) {\n try {\n const context = await resolveAccessRequestContext(req)\n const featureAccess = await resolveFeatureAccess(context.container, context.requesterUserId, [VIEW_FEATURE], {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n })\n if (!featureAccess.allowed) {\n throw new CrudHttpError(403, { error: context.translate('staff.errors.forbidden', 'Forbidden') })\n }\n const grantedFeatures = featureAccess.grantedFeatures\n\n const interceptors = await runTimesheetInterceptors({\n request: req,\n method: 'POST',\n scope: {\n container: context.container,\n userId: context.requesterUserId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: grantedFeatures,\n },\n body: await readJsonSafe<Record<string, unknown>>(req, {}),\n })\n if (!interceptors.ok) return interceptors.response\n const { session } = interceptors\n\n const parsed = accessRequestSchema.parse(session.body)\n\n const guardResult = await runRouteMutationGuards({\n container: context.container,\n req,\n auth: {\n userId: context.requesterUserId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: grantedFeatures,\n },\n input: {\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.timeProjectId ?? GENERAL_REQUEST_SCOPE,\n operation: 'create',\n mutationPayload: { timeProjectId: parsed.timeProjectId ?? null },\n },\n })\n if (!guardResult.ok) return guardResult.response\n\n const effective = guardResult.modifiedPayload\n ? accessRequestSchema.parse({ ...parsed, ...guardResult.modifiedPayload })\n : parsed\n\n const project = await resolveRequestedProject(context, effective.timeProjectId ?? null)\n const dedupeKey = buildAccessRequestDedupeKey({\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n requesterUserId: context.requesterUserId,\n timeProjectId: project?.id ?? null,\n })\n\n const cache = resolveDedupeCache(context)\n if (cache) {\n const pending = await cache.get(dedupeKey).catch(() => null)\n if (pending != null) {\n await guardResult.runAfterSuccess()\n return session.respond(200, { ok: true, deduplicated: true })\n }\n }\n\n const requesterName = await resolveRequesterName(context)\n await emitStaffEvent('staff.timesheets.project_access.requested', {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n requesterUserId: context.requesterUserId,\n requesterName,\n timeProjectId: project?.id ?? null,\n timeProjectName: project?.name ?? null,\n requestedAt: new Date().toISOString(),\n })\n\n if (cache) {\n await cache\n .set(dedupeKey, { requestedAt: Date.now() }, { ttl: ACCESS_REQUEST_DEDUPE_WINDOW_MS })\n .catch((err: unknown) => {\n logger.warn('Failed to record the access-request dedupe window', { err })\n })\n }\n\n await guardResult.runAfterSuccess()\n\n return session.respond(200, { ok: true, deduplicated: false })\n } catch (err) {\n return errorResponse(err, 'staff.timesheets.access-requests.POST failed')\n }\n}\n\nasync function errorResponse(err: unknown, message: string) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n if (err instanceof z.ZodError) {\n return NextResponse.json(\n { error: translate('staff.errors.invalid_request', 'Invalid request'), details: err.issues },\n { status: 400 },\n )\n }\n logger.error(message, { err })\n return NextResponse.json(\n { error: translate('staff.errors.internal', 'Internal server error') },\n { status: 500 },\n )\n}\n\nconst postDoc: OpenApiMethodDoc = {\n summary: 'Request time tracking project access',\n description:\n 'Notifies every holder of staff.timesheets.projects.manage in the caller scope that a team member asked for project access. Omitting timeProjectId sends a general request from the no-assignments empty state.',\n tags: ['Staff'],\n requestBody: { schema: accessRequestSchema },\n responses: [\n {\n status: 200,\n description: 'Access request accepted (deduplicated when an identical request is still pending)',\n schema: accessRequestResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid request body or missing organization scope' },\n { status: 401, description: 'Unauthorized' },\n { status: 403, description: 'Missing staff.timesheets.view' },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Time tracking project access requests',\n methods: {\n POST: postDoc,\n },\n}\n\nexport { POST }\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|