@open-mercato/core 0.9.1-develop.7462.1.94c09f158f → 0.9.1-develop.7464.1.9a392fca41
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.
|
@@ -74,6 +74,7 @@ function ActivitiesSection({
|
|
|
74
74
|
useCanonicalInteractions = false,
|
|
75
75
|
onActionChange,
|
|
76
76
|
onLoadingChange,
|
|
77
|
+
onDataRefresh,
|
|
77
78
|
refreshKey = 0,
|
|
78
79
|
onEditActivity,
|
|
79
80
|
runGuardedMutation,
|
|
@@ -232,8 +233,14 @@ function ActivitiesSection({
|
|
|
232
233
|
} catch (err) {
|
|
233
234
|
logger.warn("Mark done failed", { component: "ActivitiesSection", activityId, err });
|
|
234
235
|
flash(t("customers.activities.actions.markDoneError", "Could not mark activity as done"), "error");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
await Promise.resolve(onDataRefresh?.());
|
|
240
|
+
} catch (err) {
|
|
241
|
+
logger.warn("Parent refresh after mark done failed", { component: "ActivitiesSection", activityId, err });
|
|
235
242
|
}
|
|
236
|
-
}, [loadActivities, runGuardedMutation, t]);
|
|
243
|
+
}, [loadActivities, onDataRefresh, runGuardedMutation, t]);
|
|
237
244
|
const resolvedUserIdsRef = React.useRef(/* @__PURE__ */ new Set());
|
|
238
245
|
React.useEffect(() => {
|
|
239
246
|
loadActivities().then(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/ActivitiesSection.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Clock, Search } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport type { SectionAction, TabEmptyStateConfig } from '@open-mercato/ui/backend/detail'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Kbd } from '@open-mercato/ui/primitives/kbd'\nimport { ActivityTimelineFilters } from './ActivityTimelineFilters'\nimport { ActivityTimeline } from './ActivityTimeline'\nimport { resolveAuthorUserNames } from './authorUserLookup'\nimport type { ActivitySummary, InteractionSummary } from './types'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\ntype GuardedMutationRunner = <T>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\nexport type ActivitiesSectionProps = {\n entityId: string | null\n entityName?: string | null\n dealId?: string | null\n useCanonicalInteractions?: boolean\n addActionLabel: string\n emptyState: TabEmptyStateConfig\n onActionChange?: (action: SectionAction | null) => void\n onLoadingChange?: (isLoading: boolean) => void\n onDataRefresh?: () => void\n dealOptions?: Array<{ id: string; label: string }>\n entityOptions?: Array<{ id: string; label: string }>\n defaultEntityId?: string | null\n runGuardedMutation?: GuardedMutationRunner\n refreshKey?: number\n onEditActivity?: (activity: InteractionSummary) => void\n /** Interaction type hidden from the timeline by default ('task' unless overridden); pass null to show every type. */\n excludeInteractionType?: string | null\n}\n\nfunction toDateOnly(value: string | null | undefined): string {\n if (!value) return ''\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? '' : date.toISOString().slice(0, 10)\n}\n\nfunction normalizeLegacyActivity(activity: ActivitySummary): InteractionSummary {\n return {\n id: activity.id,\n interactionType: activity.activityType,\n title: activity.subject ?? null,\n body: activity.body ?? null,\n status: 'done',\n scheduledAt: null,\n occurredAt: activity.occurredAt ?? null,\n priority: null,\n authorUserId: activity.authorUserId ?? null,\n ownerUserId: null,\n appearanceIcon: activity.appearanceIcon ?? null,\n appearanceColor: activity.appearanceColor ?? null,\n source: 'legacy-activity',\n entityId: activity.entityId ?? null,\n dealId: activity.dealId ?? null,\n organizationId: null,\n tenantId: null,\n authorName: activity.authorName ?? null,\n authorEmail: activity.authorEmail ?? null,\n dealTitle: activity.dealTitle ?? null,\n customValues: activity.customValues ?? null,\n createdAt: activity.createdAt,\n updatedAt: activity.createdAt,\n }\n}\n\nfunction sortTimelineActivities(items: InteractionSummary[]): InteractionSummary[] {\n const now = Date.now()\n return [...items].sort((left, right) => {\n const leftScheduled = left.scheduledAt ? new Date(left.scheduledAt).getTime() : Number.NaN\n const rightScheduled = right.scheduledAt ? new Date(right.scheduledAt).getTime() : Number.NaN\n const leftIsPlanned = left.status === 'planned' && Number.isFinite(leftScheduled)\n const rightIsPlanned = right.status === 'planned' && Number.isFinite(rightScheduled)\n const leftIsUpcoming = leftIsPlanned && leftScheduled >= now\n const rightIsUpcoming = rightIsPlanned && rightScheduled >= now\n\n if (leftIsUpcoming !== rightIsUpcoming) {\n return leftIsUpcoming ? -1 : 1\n }\n\n if (leftIsUpcoming && rightIsUpcoming) {\n if (leftScheduled === rightScheduled) return left.id.localeCompare(right.id)\n return leftScheduled - rightScheduled\n }\n\n const leftTime = left.occurredAt ?? left.createdAt\n const rightTime = right.occurredAt ?? right.createdAt\n const compare = rightTime.localeCompare(leftTime)\n if (compare !== 0) return compare\n return right.id.localeCompare(left.id)\n })\n}\n\nexport function ActivitiesSection({\n entityId,\n entityName,\n dealId,\n useCanonicalInteractions = false,\n onActionChange,\n onLoadingChange,\n refreshKey = 0,\n onEditActivity,\n runGuardedMutation,\n excludeInteractionType = 'task',\n}: ActivitiesSectionProps) {\n const t = useT()\n const [filterTypes, setFilterTypes] = React.useState<string[]>([])\n const [filterDateFrom, setFilterDateFrom] = React.useState('')\n const [filterDateTo, setFilterDateTo] = React.useState('')\n const [searchTerm, setSearchTerm] = React.useState('')\n const [activities, setActivities] = React.useState<InteractionSummary[]>([])\n const [loading, setLoading] = React.useState(false)\n const [hasMore, setHasMore] = React.useState(false)\n const [loadedPages, setLoadedPages] = React.useState(1)\n const searchInputRef = React.useRef<HTMLInputElement>(null)\n\n React.useEffect(() => {\n if (!entityId) return\n function handleShortcut(event: KeyboardEvent) {\n if ((event.metaKey || event.ctrlKey) && event.key === '1') {\n event.preventDefault()\n searchInputRef.current?.focus()\n }\n }\n window.addEventListener('keydown', handleShortcut)\n return () => window.removeEventListener('keydown', handleShortcut)\n }, [entityId])\n\n const visibleActivities = React.useMemo(() => {\n const term = searchTerm.trim().toLowerCase()\n if (!term) return activities\n return activities.filter((activity) => {\n const haystack = [\n activity.title,\n activity.body,\n activity.authorName,\n activity.dealTitle,\n activity.interactionType,\n ]\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n .join(' ')\n .toLowerCase()\n return haystack.includes(term)\n })\n }, [activities, searchTerm])\n\n React.useEffect(() => {\n onActionChange?.(null)\n return () => onActionChange?.(null)\n }, [onActionChange])\n\n React.useEffect(() => {\n onLoadingChange?.(loading)\n }, [loading, onLoadingChange])\n\n const loadActivities = React.useCallback(async () => {\n if (!entityId) {\n setActivities([])\n return\n }\n\n setLoading(true)\n try {\n // Always fetch canonical interactions (new activities are always created here)\n const excludedFilterActive = excludeInteractionType ? filterTypes.includes(excludeInteractionType) : true\n const canonicalParams = new URLSearchParams({\n entityId,\n limit: '50',\n sortField: 'occurredAt',\n sortDir: 'desc',\n })\n // Hide the configured type (tasks by default \u2014 they have their own tab)\n // from the activity timeline, but lift the exclusion when the user\n // explicitly toggled that type's chip on (mirrors\n // `ActivityHistorySection.tsx` after the #1805 fix).\n if (excludeInteractionType && !excludedFilterActive) canonicalParams.set('excludeInteractionType', excludeInteractionType)\n if (dealId) canonicalParams.set('dealId', dealId)\n if (filterTypes.length > 0) canonicalParams.set('type', filterTypes.join(','))\n if (filterDateFrom) canonicalParams.set('from', filterDateFrom)\n if (filterDateTo) canonicalParams.set('to', filterDateTo)\n\n const canonicalItems: InteractionSummary[] = []\n let canonicalCursor: string | undefined\n let canonicalHasMore = false\n let pageIndex = 0\n do {\n const params = new URLSearchParams(canonicalParams)\n if (canonicalCursor) params.set('cursor', canonicalCursor)\n const canonicalPayload = await readApiResultOrThrow<{ items?: InteractionSummary[]; nextCursor?: string }>(\n `/api/customers/interactions?${params.toString()}`,\n ).catch(() => ({ items: [] as InteractionSummary[], nextCursor: undefined }))\n canonicalItems.push(...(Array.isArray(canonicalPayload?.items) ? canonicalPayload.items : []))\n canonicalCursor = typeof canonicalPayload?.nextCursor === 'string' ? canonicalPayload.nextCursor : undefined\n canonicalHasMore = Boolean(canonicalCursor)\n pageIndex += 1\n } while (canonicalCursor && pageIndex < loadedPages)\n\n if (useCanonicalInteractions) {\n setActivities(sortTimelineActivities(canonicalItems))\n setHasMore(canonicalHasMore)\n return\n }\n\n // In legacy mode, also fetch legacy activities and merge with canonical.\n // Legacy fallback uses known page numbers, so request every page up front\n // and resolve them together instead of awaiting each one sequentially.\n const legacyPageNumbers = Array.from({ length: loadedPages }, (_, index) => index + 1)\n const legacyPayloads = await Promise.all(\n legacyPageNumbers.map((legacyPage) => {\n const legacyParams = new URLSearchParams({\n entityId,\n page: String(legacyPage),\n pageSize: '50',\n sortField: 'occurredAt',\n sortDir: 'desc',\n })\n if (dealId) legacyParams.set('dealId', dealId)\n return readApiResultOrThrow<{ items?: ActivitySummary[]; totalPages?: number }>(\n `/api/customers/activities?${legacyParams.toString()}`,\n ).catch(() => ({ items: [] as ActivitySummary[], totalPages: 1 }))\n }),\n )\n // Merge in page order so timeline ordering stays stable regardless of\n // which request settles first.\n const legacyItems: InteractionSummary[] = []\n let legacyTotalPages = 1\n for (const legacyPayload of legacyPayloads) {\n legacyItems.push(...(Array.isArray(legacyPayload?.items) ? legacyPayload.items.map(normalizeLegacyActivity) : []))\n legacyTotalPages = typeof legacyPayload?.totalPages === 'number' ? legacyPayload.totalPages : legacyTotalPages\n }\n const legacyFiltered = legacyItems.filter((entry) => {\n if (filterTypes.length > 0 && !filterTypes.includes(entry.interactionType)) return false\n const dateOnly = toDateOnly(entry.occurredAt ?? entry.createdAt)\n if (filterDateFrom && dateOnly < filterDateFrom) return false\n if (filterDateTo && dateOnly > filterDateTo) return false\n return true\n })\n\n // Merge and deduplicate by id, sort newest first\n const seen = new Set<string>()\n const merged: InteractionSummary[] = []\n for (const item of [...canonicalItems, ...legacyFiltered]) {\n if (!seen.has(item.id)) {\n seen.add(item.id)\n merged.push(item)\n }\n }\n setActivities(sortTimelineActivities(merged))\n setHasMore(canonicalHasMore || legacyTotalPages > loadedPages)\n } catch (error) {\n logger.error('customers.activities.history failed', { err: error })\n flash(t('customers.activities.loadFailed', 'Failed to load activities.'), 'error')\n setActivities([])\n setHasMore(false)\n } finally {\n setLoading(false)\n }\n }, [dealId, entityId, excludeInteractionType, filterDateFrom, filterDateTo, filterTypes, loadedPages, useCanonicalInteractions, refreshKey, t])\n\n React.useEffect(() => {\n setLoadedPages(1)\n }, [dealId, entityId, filterDateFrom, filterDateTo, filterTypes, useCanonicalInteractions])\n\n const handleMarkDone = React.useCallback(async (activityId: string) => {\n try {\n const operation = () =>\n apiCallOrThrow('/api/customers/interactions/complete', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: activityId, occurredAt: new Date().toISOString() }),\n })\n if (runGuardedMutation) {\n await runGuardedMutation(operation, {\n id: activityId,\n status: 'done',\n operation: 'completeActivity',\n })\n } else {\n await operation()\n }\n flash(t('customers.activities.actions.markDoneSuccess', 'Activity marked done'), 'success')\n await loadActivities()\n } catch (err) {\n logger.warn('Mark done failed', { component: 'ActivitiesSection', activityId, err })\n flash(t('customers.activities.actions.markDoneError', 'Could not mark activity as done'), 'error')\n }\n }, [loadActivities, runGuardedMutation, t])\n\n const resolvedUserIdsRef = React.useRef(new Set<string>())\n\n // Resolve missing author names from user IDs\n React.useEffect(() => {\n loadActivities()\n .then(() => { resolvedUserIdsRef.current = new Set() })\n .catch((err) => logger.warn('loadActivities failed', { component: 'ActivitiesSection', err }))\n }, [loadActivities])\n\n React.useEffect(() => {\n const unresolvedIds = new Set<string>()\n for (const a of activities) {\n if (a.authorUserId && !a.authorName && !resolvedUserIdsRef.current.has(a.authorUserId)) {\n unresolvedIds.add(a.authorUserId)\n }\n }\n if (unresolvedIds.size === 0) return\n\n const controller = new AbortController()\n resolveAuthorUserNames([...unresolvedIds], controller.signal)\n .then(({ names: nameMap, resolvedIds }) => {\n if (controller.signal.aborted) return\n // Only ids the server actually answered for are remembered. Marking an id whose request\n // failed \u2014 or one the server silently dropped past its `?ids=` cap \u2014 would leave that\n // author blank for the life of the component, even though the next attempt would work.\n for (const uid of resolvedIds) resolvedUserIdsRef.current.add(uid)\n if (nameMap.size > 0) {\n setActivities((prev) =>\n prev.map((a) => {\n if (a.authorUserId && !a.authorName && nameMap.has(a.authorUserId)) {\n return { ...a, authorName: nameMap.get(a.authorUserId) ?? null }\n }\n return a\n }),\n )\n }\n })\n .catch((err) => logger.warn('resolve author names failed', { component: 'ActivitiesSection', err }))\n return () => controller.abort()\n }, [activities])\n\n const totalCount = activities.length\n const visibleCount = visibleActivities.length\n\n return (\n <div className=\"flex flex-col gap-3.5 rounded-[10px] border border-border bg-card pt-4 pb-[18px] px-[18px]\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-[15px] text-muted-foreground\" />\n <h3 className=\"text-[13px] font-semibold text-foreground\">\n {entityName\n ? t('customers.timeline.history.title', 'Interaction history with {{name}}', { name: entityName })\n : t('customers.timeline.history.titleGeneric', 'Interaction history')}\n </h3>\n </div>\n\n <label className=\"relative flex items-center\">\n <Search className=\"pointer-events-none absolute left-2.5 size-5 text-muted-foreground\" aria-hidden />\n <input\n ref={searchInputRef}\n type=\"search\"\n value={searchTerm}\n onChange={(event) => setSearchTerm(event.target.value)}\n placeholder={t('customers.timeline.history.searchPlaceholder', 'Search...')}\n aria-label={t('customers.timeline.history.searchAriaLabel', 'Search interaction history')}\n className=\"h-9 w-full rounded-[10px] border border-border bg-card pl-9 pr-14 text-sm text-foreground shadow-xs placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden\"\n />\n <Kbd className=\"pointer-events-none absolute right-2 hidden text-[11px] uppercase tracking-[0.48px] sm:inline-flex\">\n \u23181\n </Kbd>\n </label>\n\n <ActivityTimelineFilters\n entityId={entityId}\n activeTypes={filterTypes}\n dateFrom={filterDateFrom}\n dateTo={filterDateTo}\n onTypesChange={setFilterTypes}\n onDateFromChange={setFilterDateFrom}\n onDateToChange={setFilterDateTo}\n onReset={() => {\n setFilterTypes([])\n setFilterDateFrom('')\n setFilterDateTo('')\n }}\n />\n\n {loading && totalCount === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border/70 px-4 py-8 text-sm text-muted-foreground\">\n {t('customers.people.detail.activities.loading', 'Loading activities\u2026')}\n </div>\n ) : (\n <>\n <ActivityTimeline\n activities={visibleActivities}\n onEdit={onEditActivity}\n onMarkDone={handleMarkDone}\n />\n {totalCount > 0 ? (\n <div className=\"flex items-center justify-between gap-3 border-t border-border/60 pt-3\">\n <span className=\"text-xs text-muted-foreground\">\n {searchTerm.trim()\n ? t('customers.activities.seeMatching', 'Showing {visible} of {total} activities', {\n visible: visibleCount,\n total: totalCount,\n })\n : t('customers.activities.seeAll', 'See all {count} activities', { count: totalCount })}\n </span>\n {hasMore ? (\n <Button type=\"button\" variant=\"link\" size=\"sm\" onClick={() => setLoadedPages((value) => value + 1)}>\n {t('customers.activities.loadMore', 'Load more')}\n </Button>\n ) : null}\n </div>\n ) : null}\n </>\n )}\n </div>\n )\n}\n\nexport default ActivitiesSection\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Clock, Search } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport type { SectionAction, TabEmptyStateConfig } from '@open-mercato/ui/backend/detail'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Kbd } from '@open-mercato/ui/primitives/kbd'\nimport { ActivityTimelineFilters } from './ActivityTimelineFilters'\nimport { ActivityTimeline } from './ActivityTimeline'\nimport { resolveAuthorUserNames } from './authorUserLookup'\nimport type { ActivitySummary, InteractionSummary } from './types'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\ntype GuardedMutationRunner = <T>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\nexport type ActivitiesSectionProps = {\n entityId: string | null\n entityName?: string | null\n dealId?: string | null\n useCanonicalInteractions?: boolean\n addActionLabel: string\n emptyState: TabEmptyStateConfig\n onActionChange?: (action: SectionAction | null) => void\n onLoadingChange?: (isLoading: boolean) => void\n onDataRefresh?: () => void\n dealOptions?: Array<{ id: string; label: string }>\n entityOptions?: Array<{ id: string; label: string }>\n defaultEntityId?: string | null\n runGuardedMutation?: GuardedMutationRunner\n refreshKey?: number\n onEditActivity?: (activity: InteractionSummary) => void\n /** Interaction type hidden from the timeline by default ('task' unless overridden); pass null to show every type. */\n excludeInteractionType?: string | null\n}\n\nfunction toDateOnly(value: string | null | undefined): string {\n if (!value) return ''\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? '' : date.toISOString().slice(0, 10)\n}\n\nfunction normalizeLegacyActivity(activity: ActivitySummary): InteractionSummary {\n return {\n id: activity.id,\n interactionType: activity.activityType,\n title: activity.subject ?? null,\n body: activity.body ?? null,\n status: 'done',\n scheduledAt: null,\n occurredAt: activity.occurredAt ?? null,\n priority: null,\n authorUserId: activity.authorUserId ?? null,\n ownerUserId: null,\n appearanceIcon: activity.appearanceIcon ?? null,\n appearanceColor: activity.appearanceColor ?? null,\n source: 'legacy-activity',\n entityId: activity.entityId ?? null,\n dealId: activity.dealId ?? null,\n organizationId: null,\n tenantId: null,\n authorName: activity.authorName ?? null,\n authorEmail: activity.authorEmail ?? null,\n dealTitle: activity.dealTitle ?? null,\n customValues: activity.customValues ?? null,\n createdAt: activity.createdAt,\n updatedAt: activity.createdAt,\n }\n}\n\nfunction sortTimelineActivities(items: InteractionSummary[]): InteractionSummary[] {\n const now = Date.now()\n return [...items].sort((left, right) => {\n const leftScheduled = left.scheduledAt ? new Date(left.scheduledAt).getTime() : Number.NaN\n const rightScheduled = right.scheduledAt ? new Date(right.scheduledAt).getTime() : Number.NaN\n const leftIsPlanned = left.status === 'planned' && Number.isFinite(leftScheduled)\n const rightIsPlanned = right.status === 'planned' && Number.isFinite(rightScheduled)\n const leftIsUpcoming = leftIsPlanned && leftScheduled >= now\n const rightIsUpcoming = rightIsPlanned && rightScheduled >= now\n\n if (leftIsUpcoming !== rightIsUpcoming) {\n return leftIsUpcoming ? -1 : 1\n }\n\n if (leftIsUpcoming && rightIsUpcoming) {\n if (leftScheduled === rightScheduled) return left.id.localeCompare(right.id)\n return leftScheduled - rightScheduled\n }\n\n const leftTime = left.occurredAt ?? left.createdAt\n const rightTime = right.occurredAt ?? right.createdAt\n const compare = rightTime.localeCompare(leftTime)\n if (compare !== 0) return compare\n return right.id.localeCompare(left.id)\n })\n}\n\nexport function ActivitiesSection({\n entityId,\n entityName,\n dealId,\n useCanonicalInteractions = false,\n onActionChange,\n onLoadingChange,\n onDataRefresh,\n refreshKey = 0,\n onEditActivity,\n runGuardedMutation,\n excludeInteractionType = 'task',\n}: ActivitiesSectionProps) {\n const t = useT()\n const [filterTypes, setFilterTypes] = React.useState<string[]>([])\n const [filterDateFrom, setFilterDateFrom] = React.useState('')\n const [filterDateTo, setFilterDateTo] = React.useState('')\n const [searchTerm, setSearchTerm] = React.useState('')\n const [activities, setActivities] = React.useState<InteractionSummary[]>([])\n const [loading, setLoading] = React.useState(false)\n const [hasMore, setHasMore] = React.useState(false)\n const [loadedPages, setLoadedPages] = React.useState(1)\n const searchInputRef = React.useRef<HTMLInputElement>(null)\n\n React.useEffect(() => {\n if (!entityId) return\n function handleShortcut(event: KeyboardEvent) {\n if ((event.metaKey || event.ctrlKey) && event.key === '1') {\n event.preventDefault()\n searchInputRef.current?.focus()\n }\n }\n window.addEventListener('keydown', handleShortcut)\n return () => window.removeEventListener('keydown', handleShortcut)\n }, [entityId])\n\n const visibleActivities = React.useMemo(() => {\n const term = searchTerm.trim().toLowerCase()\n if (!term) return activities\n return activities.filter((activity) => {\n const haystack = [\n activity.title,\n activity.body,\n activity.authorName,\n activity.dealTitle,\n activity.interactionType,\n ]\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n .join(' ')\n .toLowerCase()\n return haystack.includes(term)\n })\n }, [activities, searchTerm])\n\n React.useEffect(() => {\n onActionChange?.(null)\n return () => onActionChange?.(null)\n }, [onActionChange])\n\n React.useEffect(() => {\n onLoadingChange?.(loading)\n }, [loading, onLoadingChange])\n\n const loadActivities = React.useCallback(async () => {\n if (!entityId) {\n setActivities([])\n return\n }\n\n setLoading(true)\n try {\n // Always fetch canonical interactions (new activities are always created here)\n const excludedFilterActive = excludeInteractionType ? filterTypes.includes(excludeInteractionType) : true\n const canonicalParams = new URLSearchParams({\n entityId,\n limit: '50',\n sortField: 'occurredAt',\n sortDir: 'desc',\n })\n // Hide the configured type (tasks by default \u2014 they have their own tab)\n // from the activity timeline, but lift the exclusion when the user\n // explicitly toggled that type's chip on (mirrors\n // `ActivityHistorySection.tsx` after the #1805 fix).\n if (excludeInteractionType && !excludedFilterActive) canonicalParams.set('excludeInteractionType', excludeInteractionType)\n if (dealId) canonicalParams.set('dealId', dealId)\n if (filterTypes.length > 0) canonicalParams.set('type', filterTypes.join(','))\n if (filterDateFrom) canonicalParams.set('from', filterDateFrom)\n if (filterDateTo) canonicalParams.set('to', filterDateTo)\n\n const canonicalItems: InteractionSummary[] = []\n let canonicalCursor: string | undefined\n let canonicalHasMore = false\n let pageIndex = 0\n do {\n const params = new URLSearchParams(canonicalParams)\n if (canonicalCursor) params.set('cursor', canonicalCursor)\n const canonicalPayload = await readApiResultOrThrow<{ items?: InteractionSummary[]; nextCursor?: string }>(\n `/api/customers/interactions?${params.toString()}`,\n ).catch(() => ({ items: [] as InteractionSummary[], nextCursor: undefined }))\n canonicalItems.push(...(Array.isArray(canonicalPayload?.items) ? canonicalPayload.items : []))\n canonicalCursor = typeof canonicalPayload?.nextCursor === 'string' ? canonicalPayload.nextCursor : undefined\n canonicalHasMore = Boolean(canonicalCursor)\n pageIndex += 1\n } while (canonicalCursor && pageIndex < loadedPages)\n\n if (useCanonicalInteractions) {\n setActivities(sortTimelineActivities(canonicalItems))\n setHasMore(canonicalHasMore)\n return\n }\n\n // In legacy mode, also fetch legacy activities and merge with canonical.\n // Legacy fallback uses known page numbers, so request every page up front\n // and resolve them together instead of awaiting each one sequentially.\n const legacyPageNumbers = Array.from({ length: loadedPages }, (_, index) => index + 1)\n const legacyPayloads = await Promise.all(\n legacyPageNumbers.map((legacyPage) => {\n const legacyParams = new URLSearchParams({\n entityId,\n page: String(legacyPage),\n pageSize: '50',\n sortField: 'occurredAt',\n sortDir: 'desc',\n })\n if (dealId) legacyParams.set('dealId', dealId)\n return readApiResultOrThrow<{ items?: ActivitySummary[]; totalPages?: number }>(\n `/api/customers/activities?${legacyParams.toString()}`,\n ).catch(() => ({ items: [] as ActivitySummary[], totalPages: 1 }))\n }),\n )\n // Merge in page order so timeline ordering stays stable regardless of\n // which request settles first.\n const legacyItems: InteractionSummary[] = []\n let legacyTotalPages = 1\n for (const legacyPayload of legacyPayloads) {\n legacyItems.push(...(Array.isArray(legacyPayload?.items) ? legacyPayload.items.map(normalizeLegacyActivity) : []))\n legacyTotalPages = typeof legacyPayload?.totalPages === 'number' ? legacyPayload.totalPages : legacyTotalPages\n }\n const legacyFiltered = legacyItems.filter((entry) => {\n if (filterTypes.length > 0 && !filterTypes.includes(entry.interactionType)) return false\n const dateOnly = toDateOnly(entry.occurredAt ?? entry.createdAt)\n if (filterDateFrom && dateOnly < filterDateFrom) return false\n if (filterDateTo && dateOnly > filterDateTo) return false\n return true\n })\n\n // Merge and deduplicate by id, sort newest first\n const seen = new Set<string>()\n const merged: InteractionSummary[] = []\n for (const item of [...canonicalItems, ...legacyFiltered]) {\n if (!seen.has(item.id)) {\n seen.add(item.id)\n merged.push(item)\n }\n }\n setActivities(sortTimelineActivities(merged))\n setHasMore(canonicalHasMore || legacyTotalPages > loadedPages)\n } catch (error) {\n logger.error('customers.activities.history failed', { err: error })\n flash(t('customers.activities.loadFailed', 'Failed to load activities.'), 'error')\n setActivities([])\n setHasMore(false)\n } finally {\n setLoading(false)\n }\n }, [dealId, entityId, excludeInteractionType, filterDateFrom, filterDateTo, filterTypes, loadedPages, useCanonicalInteractions, refreshKey, t])\n\n React.useEffect(() => {\n setLoadedPages(1)\n }, [dealId, entityId, filterDateFrom, filterDateTo, filterTypes, useCanonicalInteractions])\n\n const handleMarkDone = React.useCallback(async (activityId: string) => {\n try {\n const operation = () =>\n apiCallOrThrow('/api/customers/interactions/complete', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: activityId, occurredAt: new Date().toISOString() }),\n })\n if (runGuardedMutation) {\n await runGuardedMutation(operation, {\n id: activityId,\n status: 'done',\n operation: 'completeActivity',\n })\n } else {\n await operation()\n }\n flash(t('customers.activities.actions.markDoneSuccess', 'Activity marked done'), 'success')\n await loadActivities()\n } catch (err) {\n logger.warn('Mark done failed', { component: 'ActivitiesSection', activityId, err })\n flash(t('customers.activities.actions.markDoneError', 'Could not mark activity as done'), 'error')\n return\n }\n try {\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n logger.warn('Parent refresh after mark done failed', { component: 'ActivitiesSection', activityId, err })\n }\n }, [loadActivities, onDataRefresh, runGuardedMutation, t])\n\n const resolvedUserIdsRef = React.useRef(new Set<string>())\n\n // Resolve missing author names from user IDs\n React.useEffect(() => {\n loadActivities()\n .then(() => { resolvedUserIdsRef.current = new Set() })\n .catch((err) => logger.warn('loadActivities failed', { component: 'ActivitiesSection', err }))\n }, [loadActivities])\n\n React.useEffect(() => {\n const unresolvedIds = new Set<string>()\n for (const a of activities) {\n if (a.authorUserId && !a.authorName && !resolvedUserIdsRef.current.has(a.authorUserId)) {\n unresolvedIds.add(a.authorUserId)\n }\n }\n if (unresolvedIds.size === 0) return\n\n const controller = new AbortController()\n resolveAuthorUserNames([...unresolvedIds], controller.signal)\n .then(({ names: nameMap, resolvedIds }) => {\n if (controller.signal.aborted) return\n // Only ids the server actually answered for are remembered. Marking an id whose request\n // failed \u2014 or one the server silently dropped past its `?ids=` cap \u2014 would leave that\n // author blank for the life of the component, even though the next attempt would work.\n for (const uid of resolvedIds) resolvedUserIdsRef.current.add(uid)\n if (nameMap.size > 0) {\n setActivities((prev) =>\n prev.map((a) => {\n if (a.authorUserId && !a.authorName && nameMap.has(a.authorUserId)) {\n return { ...a, authorName: nameMap.get(a.authorUserId) ?? null }\n }\n return a\n }),\n )\n }\n })\n .catch((err) => logger.warn('resolve author names failed', { component: 'ActivitiesSection', err }))\n return () => controller.abort()\n }, [activities])\n\n const totalCount = activities.length\n const visibleCount = visibleActivities.length\n\n return (\n <div className=\"flex flex-col gap-3.5 rounded-[10px] border border-border bg-card pt-4 pb-[18px] px-[18px]\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-[15px] text-muted-foreground\" />\n <h3 className=\"text-[13px] font-semibold text-foreground\">\n {entityName\n ? t('customers.timeline.history.title', 'Interaction history with {{name}}', { name: entityName })\n : t('customers.timeline.history.titleGeneric', 'Interaction history')}\n </h3>\n </div>\n\n <label className=\"relative flex items-center\">\n <Search className=\"pointer-events-none absolute left-2.5 size-5 text-muted-foreground\" aria-hidden />\n <input\n ref={searchInputRef}\n type=\"search\"\n value={searchTerm}\n onChange={(event) => setSearchTerm(event.target.value)}\n placeholder={t('customers.timeline.history.searchPlaceholder', 'Search...')}\n aria-label={t('customers.timeline.history.searchAriaLabel', 'Search interaction history')}\n className=\"h-9 w-full rounded-[10px] border border-border bg-card pl-9 pr-14 text-sm text-foreground shadow-xs placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden\"\n />\n <Kbd className=\"pointer-events-none absolute right-2 hidden text-[11px] uppercase tracking-[0.48px] sm:inline-flex\">\n \u23181\n </Kbd>\n </label>\n\n <ActivityTimelineFilters\n entityId={entityId}\n activeTypes={filterTypes}\n dateFrom={filterDateFrom}\n dateTo={filterDateTo}\n onTypesChange={setFilterTypes}\n onDateFromChange={setFilterDateFrom}\n onDateToChange={setFilterDateTo}\n onReset={() => {\n setFilterTypes([])\n setFilterDateFrom('')\n setFilterDateTo('')\n }}\n />\n\n {loading && totalCount === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border/70 px-4 py-8 text-sm text-muted-foreground\">\n {t('customers.people.detail.activities.loading', 'Loading activities\u2026')}\n </div>\n ) : (\n <>\n <ActivityTimeline\n activities={visibleActivities}\n onEdit={onEditActivity}\n onMarkDone={handleMarkDone}\n />\n {totalCount > 0 ? (\n <div className=\"flex items-center justify-between gap-3 border-t border-border/60 pt-3\">\n <span className=\"text-xs text-muted-foreground\">\n {searchTerm.trim()\n ? t('customers.activities.seeMatching', 'Showing {visible} of {total} activities', {\n visible: visibleCount,\n total: totalCount,\n })\n : t('customers.activities.seeAll', 'See all {count} activities', { count: totalCount })}\n </span>\n {hasMore ? (\n <Button type=\"button\" variant=\"link\" size=\"sm\" onClick={() => setLoadedPages((value) => value + 1)}>\n {t('customers.activities.loadMore', 'Load more')}\n </Button>\n ) : null}\n </div>\n ) : null}\n </>\n )}\n </div>\n )\n}\n\nexport default ActivitiesSection\n"],
|
|
5
|
+
"mappings": ";AAgWM,SA6CE,UA5CA,KADF;AA9VN,YAAY,WAAW;AACvB,SAAS,OAAO,cAAc;AAC9B,SAAS,YAAY;AACrB,SAAS,gBAAgB,4BAA4B;AACrD,SAAS,aAAa;AAEtB,SAAS,cAAc;AACvB,SAAS,WAAW;AACpB,SAAS,+BAA+B;AACxC,SAAS,wBAAwB;AACjC,SAAS,8BAA8B;AAEvC,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,WAAW;AA2BvC,SAAS,WAAW,OAA0C;AAC5D,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,SAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,IAAI,KAAK,KAAK,YAAY,EAAE,MAAM,GAAG,EAAE;AAC3E;AAEA,SAAS,wBAAwB,UAA+C;AAC9E,SAAO;AAAA,IACL,IAAI,SAAS;AAAA,IACb,iBAAiB,SAAS;AAAA,IAC1B,OAAO,SAAS,WAAW;AAAA,IAC3B,MAAM,SAAS,QAAQ;AAAA,IACvB,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,YAAY,SAAS,cAAc;AAAA,IACnC,UAAU;AAAA,IACV,cAAc,SAAS,gBAAgB;AAAA,IACvC,aAAa;AAAA,IACb,gBAAgB,SAAS,kBAAkB;AAAA,IAC3C,iBAAiB,SAAS,mBAAmB;AAAA,IAC7C,QAAQ;AAAA,IACR,UAAU,SAAS,YAAY;AAAA,IAC/B,QAAQ,SAAS,UAAU;AAAA,IAC3B,gBAAgB;AAAA,IAChB,UAAU;AAAA,IACV,YAAY,SAAS,cAAc;AAAA,IACnC,aAAa,SAAS,eAAe;AAAA,IACrC,WAAW,SAAS,aAAa;AAAA,IACjC,cAAc,SAAS,gBAAgB;AAAA,IACvC,WAAW,SAAS;AAAA,IACpB,WAAW,SAAS;AAAA,EACtB;AACF;AAEA,SAAS,uBAAuB,OAAmD;AACjF,QAAM,MAAM,KAAK,IAAI;AACrB,SAAO,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,UAAU;AACtC,UAAM,gBAAgB,KAAK,cAAc,IAAI,KAAK,KAAK,WAAW,EAAE,QAAQ,IAAI,OAAO;AACvF,UAAM,iBAAiB,MAAM,cAAc,IAAI,KAAK,MAAM,WAAW,EAAE,QAAQ,IAAI,OAAO;AAC1F,UAAM,gBAAgB,KAAK,WAAW,aAAa,OAAO,SAAS,aAAa;AAChF,UAAM,iBAAiB,MAAM,WAAW,aAAa,OAAO,SAAS,cAAc;AACnF,UAAM,iBAAiB,iBAAiB,iBAAiB;AACzD,UAAM,kBAAkB,kBAAkB,kBAAkB;AAE5D,QAAI,mBAAmB,iBAAiB;AACtC,aAAO,iBAAiB,KAAK;AAAA,IAC/B;AAEA,QAAI,kBAAkB,iBAAiB;AACrC,UAAI,kBAAkB,eAAgB,QAAO,KAAK,GAAG,cAAc,MAAM,EAAE;AAC3E,aAAO,gBAAgB;AAAA,IACzB;AAEA,UAAM,WAAW,KAAK,cAAc,KAAK;AACzC,UAAM,YAAY,MAAM,cAAc,MAAM;AAC5C,UAAM,UAAU,UAAU,cAAc,QAAQ;AAChD,QAAI,YAAY,EAAG,QAAO;AAC1B,WAAO,MAAM,GAAG,cAAc,KAAK,EAAE;AAAA,EACvC,CAAC;AACH;AAEO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA,2BAA2B;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA,yBAAyB;AAC3B,GAA2B;AACzB,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAmB,CAAC,CAAC;AACjE,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAS,EAAE;AAC7D,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,EAAE;AACzD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,EAAE;AACrD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAA+B,CAAC,CAAC;AAC3E,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAClD,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAClD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,CAAC;AACtD,QAAM,iBAAiB,MAAM,OAAyB,IAAI;AAE1D,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,SAAU;AACf,aAAS,eAAe,OAAsB;AAC5C,WAAK,MAAM,WAAW,MAAM,YAAY,MAAM,QAAQ,KAAK;AACzD,cAAM,eAAe;AACrB,uBAAe,SAAS,MAAM;AAAA,MAChC;AAAA,IACF;AACA,WAAO,iBAAiB,WAAW,cAAc;AACjD,WAAO,MAAM,OAAO,oBAAoB,WAAW,cAAc;AAAA,EACnE,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,oBAAoB,MAAM,QAAQ,MAAM;AAC5C,UAAM,OAAO,WAAW,KAAK,EAAE,YAAY;AAC3C,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO,WAAW,OAAO,CAAC,aAAa;AACrC,YAAM,WAAW;AAAA,QACf,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,MACX,EACG,OAAO,CAAC,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC,EAChF,KAAK,GAAG,EACR,YAAY;AACf,aAAO,SAAS,SAAS,IAAI;AAAA,IAC/B,CAAC;AAAA,EACH,GAAG,CAAC,YAAY,UAAU,CAAC;AAE3B,QAAM,UAAU,MAAM;AACpB,qBAAiB,IAAI;AACrB,WAAO,MAAM,iBAAiB,IAAI;AAAA,EACpC,GAAG,CAAC,cAAc,CAAC;AAEnB,QAAM,UAAU,MAAM;AACpB,sBAAkB,OAAO;AAAA,EAC3B,GAAG,CAAC,SAAS,eAAe,CAAC;AAE7B,QAAM,iBAAiB,MAAM,YAAY,YAAY;AACnD,QAAI,CAAC,UAAU;AACb,oBAAc,CAAC,CAAC;AAChB;AAAA,IACF;AAEA,eAAW,IAAI;AACf,QAAI;AAEF,YAAM,uBAAuB,yBAAyB,YAAY,SAAS,sBAAsB,IAAI;AACrG,YAAM,kBAAkB,IAAI,gBAAgB;AAAA,QAC1C;AAAA,QACA,OAAO;AAAA,QACP,WAAW;AAAA,QACX,SAAS;AAAA,MACX,CAAC;AAKD,UAAI,0BAA0B,CAAC,qBAAsB,iBAAgB,IAAI,0BAA0B,sBAAsB;AACzH,UAAI,OAAQ,iBAAgB,IAAI,UAAU,MAAM;AAChD,UAAI,YAAY,SAAS,EAAG,iBAAgB,IAAI,QAAQ,YAAY,KAAK,GAAG,CAAC;AAC7E,UAAI,eAAgB,iBAAgB,IAAI,QAAQ,cAAc;AAC9D,UAAI,aAAc,iBAAgB,IAAI,MAAM,YAAY;AAExD,YAAM,iBAAuC,CAAC;AAC9C,UAAI;AACJ,UAAI,mBAAmB;AACvB,UAAI,YAAY;AAChB,SAAG;AACD,cAAM,SAAS,IAAI,gBAAgB,eAAe;AAClD,YAAI,gBAAiB,QAAO,IAAI,UAAU,eAAe;AACzD,cAAM,mBAAmB,MAAM;AAAA,UAC7B,+BAA+B,OAAO,SAAS,CAAC;AAAA,QAClD,EAAE,MAAM,OAAO,EAAE,OAAO,CAAC,GAA2B,YAAY,OAAU,EAAE;AAC5E,uBAAe,KAAK,GAAI,MAAM,QAAQ,kBAAkB,KAAK,IAAI,iBAAiB,QAAQ,CAAC,CAAE;AAC7F,0BAAkB,OAAO,kBAAkB,eAAe,WAAW,iBAAiB,aAAa;AACnG,2BAAmB,QAAQ,eAAe;AAC1C,qBAAa;AAAA,MACf,SAAS,mBAAmB,YAAY;AAExC,UAAI,0BAA0B;AAC5B,sBAAc,uBAAuB,cAAc,CAAC;AACpD,mBAAW,gBAAgB;AAC3B;AAAA,MACF;AAKA,YAAM,oBAAoB,MAAM,KAAK,EAAE,QAAQ,YAAY,GAAG,CAAC,GAAG,UAAU,QAAQ,CAAC;AACrF,YAAM,iBAAiB,MAAM,QAAQ;AAAA,QACnC,kBAAkB,IAAI,CAAC,eAAe;AACpC,gBAAM,eAAe,IAAI,gBAAgB;AAAA,YACvC;AAAA,YACA,MAAM,OAAO,UAAU;AAAA,YACvB,UAAU;AAAA,YACV,WAAW;AAAA,YACX,SAAS;AAAA,UACX,CAAC;AACD,cAAI,OAAQ,cAAa,IAAI,UAAU,MAAM;AAC7C,iBAAO;AAAA,YACL,6BAA6B,aAAa,SAAS,CAAC;AAAA,UACtD,EAAE,MAAM,OAAO,EAAE,OAAO,CAAC,GAAwB,YAAY,EAAE,EAAE;AAAA,QACnE,CAAC;AAAA,MACH;AAGA,YAAM,cAAoC,CAAC;AAC3C,UAAI,mBAAmB;AACvB,iBAAW,iBAAiB,gBAAgB;AAC1C,oBAAY,KAAK,GAAI,MAAM,QAAQ,eAAe,KAAK,IAAI,cAAc,MAAM,IAAI,uBAAuB,IAAI,CAAC,CAAE;AACjH,2BAAmB,OAAO,eAAe,eAAe,WAAW,cAAc,aAAa;AAAA,MAChG;AACA,YAAM,iBAAiB,YAAY,OAAO,CAAC,UAAU;AACnD,YAAI,YAAY,SAAS,KAAK,CAAC,YAAY,SAAS,MAAM,eAAe,EAAG,QAAO;AACnF,cAAM,WAAW,WAAW,MAAM,cAAc,MAAM,SAAS;AAC/D,YAAI,kBAAkB,WAAW,eAAgB,QAAO;AACxD,YAAI,gBAAgB,WAAW,aAAc,QAAO;AACpD,eAAO;AAAA,MACT,CAAC;AAGD,YAAM,OAAO,oBAAI,IAAY;AAC7B,YAAM,SAA+B,CAAC;AACtC,iBAAW,QAAQ,CAAC,GAAG,gBAAgB,GAAG,cAAc,GAAG;AACzD,YAAI,CAAC,KAAK,IAAI,KAAK,EAAE,GAAG;AACtB,eAAK,IAAI,KAAK,EAAE;AAChB,iBAAO,KAAK,IAAI;AAAA,QAClB;AAAA,MACF;AACA,oBAAc,uBAAuB,MAAM,CAAC;AAC5C,iBAAW,oBAAoB,mBAAmB,WAAW;AAAA,IAC/D,SAAS,OAAO;AACd,aAAO,MAAM,uCAAuC,EAAE,KAAK,MAAM,CAAC;AAClE,YAAM,EAAE,mCAAmC,4BAA4B,GAAG,OAAO;AACjF,oBAAc,CAAC,CAAC;AAChB,iBAAW,KAAK;AAAA,IAClB,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,QAAQ,UAAU,wBAAwB,gBAAgB,cAAc,aAAa,aAAa,0BAA0B,YAAY,CAAC,CAAC;AAE9I,QAAM,UAAU,MAAM;AACpB,mBAAe,CAAC;AAAA,EAClB,GAAG,CAAC,QAAQ,UAAU,gBAAgB,cAAc,aAAa,wBAAwB,CAAC;AAE1F,QAAM,iBAAiB,MAAM,YAAY,OAAO,eAAuB;AACrE,QAAI;AACF,YAAM,YAAY,MAChB,eAAe,wCAAwC;AAAA,QACrD,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,IAAI,YAAY,aAAY,oBAAI,KAAK,GAAE,YAAY,EAAE,CAAC;AAAA,MAC/E,CAAC;AACH,UAAI,oBAAoB;AACtB,cAAM,mBAAmB,WAAW;AAAA,UAClC,IAAI;AAAA,UACJ,QAAQ;AAAA,UACR,WAAW;AAAA,QACb,CAAC;AAAA,MACH,OAAO;AACL,cAAM,UAAU;AAAA,MAClB;AACA,YAAM,EAAE,gDAAgD,sBAAsB,GAAG,SAAS;AAC1F,YAAM,eAAe;AAAA,IACvB,SAAS,KAAK;AACZ,aAAO,KAAK,oBAAoB,EAAE,WAAW,qBAAqB,YAAY,IAAI,CAAC;AACnF,YAAM,EAAE,8CAA8C,iCAAiC,GAAG,OAAO;AACjG;AAAA,IACF;AACA,QAAI;AACF,YAAM,QAAQ,QAAQ,gBAAgB,CAAC;AAAA,IACzC,SAAS,KAAK;AACZ,aAAO,KAAK,yCAAyC,EAAE,WAAW,qBAAqB,YAAY,IAAI,CAAC;AAAA,IAC1G;AAAA,EACF,GAAG,CAAC,gBAAgB,eAAe,oBAAoB,CAAC,CAAC;AAEzD,QAAM,qBAAqB,MAAM,OAAO,oBAAI,IAAY,CAAC;AAGzD,QAAM,UAAU,MAAM;AACpB,mBAAe,EACZ,KAAK,MAAM;AAAE,yBAAmB,UAAU,oBAAI,IAAI;AAAA,IAAE,CAAC,EACrD,MAAM,CAAC,QAAQ,OAAO,KAAK,yBAAyB,EAAE,WAAW,qBAAqB,IAAI,CAAC,CAAC;AAAA,EACjG,GAAG,CAAC,cAAc,CAAC;AAEnB,QAAM,UAAU,MAAM;AACpB,UAAM,gBAAgB,oBAAI,IAAY;AACtC,eAAW,KAAK,YAAY;AAC1B,UAAI,EAAE,gBAAgB,CAAC,EAAE,cAAc,CAAC,mBAAmB,QAAQ,IAAI,EAAE,YAAY,GAAG;AACtF,sBAAc,IAAI,EAAE,YAAY;AAAA,MAClC;AAAA,IACF;AACA,QAAI,cAAc,SAAS,EAAG;AAE9B,UAAM,aAAa,IAAI,gBAAgB;AACvC,2BAAuB,CAAC,GAAG,aAAa,GAAG,WAAW,MAAM,EACzD,KAAK,CAAC,EAAE,OAAO,SAAS,YAAY,MAAM;AACzC,UAAI,WAAW,OAAO,QAAS;AAI/B,iBAAW,OAAO,YAAa,oBAAmB,QAAQ,IAAI,GAAG;AACjE,UAAI,QAAQ,OAAO,GAAG;AACpB;AAAA,UAAc,CAAC,SACb,KAAK,IAAI,CAAC,MAAM;AACd,gBAAI,EAAE,gBAAgB,CAAC,EAAE,cAAc,QAAQ,IAAI,EAAE,YAAY,GAAG;AAClE,qBAAO,EAAE,GAAG,GAAG,YAAY,QAAQ,IAAI,EAAE,YAAY,KAAK,KAAK;AAAA,YACjE;AACA,mBAAO;AAAA,UACT,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,CAAC,EACA,MAAM,CAAC,QAAQ,OAAO,KAAK,+BAA+B,EAAE,WAAW,qBAAqB,IAAI,CAAC,CAAC;AACrG,WAAO,MAAM,WAAW,MAAM;AAAA,EAChC,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,aAAa,WAAW;AAC9B,QAAM,eAAe,kBAAkB;AAEvC,SACE,qBAAC,SAAI,WAAU,8FACb;AAAA,yBAAC,SAAI,WAAU,2BACb;AAAA,0BAAC,SAAM,WAAU,qCAAoC;AAAA,MACrD,oBAAC,QAAG,WAAU,6CACX,uBACG,EAAE,oCAAoC,qCAAqC,EAAE,MAAM,WAAW,CAAC,IAC/F,EAAE,2CAA2C,qBAAqB,GACxE;AAAA,OACF;AAAA,IAEA,qBAAC,WAAM,WAAU,8BACf;AAAA,0BAAC,UAAO,WAAU,sEAAqE,eAAW,MAAC;AAAA,MACnG;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,UAAU,cAAc,MAAM,OAAO,KAAK;AAAA,UACrD,aAAa,EAAE,gDAAgD,WAAW;AAAA,UAC1E,cAAY,EAAE,8CAA8C,4BAA4B;AAAA,UACxF,WAAU;AAAA;AAAA,MACZ;AAAA,MACA,oBAAC,OAAI,WAAU,sGAAqG,qBAEpH;AAAA,OACF;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,aAAa;AAAA,QACb,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,eAAe;AAAA,QACf,kBAAkB;AAAA,QAClB,gBAAgB;AAAA,QAChB,SAAS,MAAM;AACb,yBAAe,CAAC,CAAC;AACjB,4BAAkB,EAAE;AACpB,0BAAgB,EAAE;AAAA,QACpB;AAAA;AAAA,IACF;AAAA,IAEC,WAAW,eAAe,IACzB,oBAAC,SAAI,WAAU,4FACZ,YAAE,8CAA8C,0BAAqB,GACxE,IAEA,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,YAAY;AAAA,UACZ,QAAQ;AAAA,UACR,YAAY;AAAA;AAAA,MACd;AAAA,MACC,aAAa,IACZ,qBAAC,SAAI,WAAU,0EACb;AAAA,4BAAC,UAAK,WAAU,iCACb,qBAAW,KAAK,IACb,EAAE,oCAAoC,2CAA2C;AAAA,UAC/E,SAAS;AAAA,UACT,OAAO;AAAA,QACT,CAAC,IACD,EAAE,+BAA+B,8BAA8B,EAAE,OAAO,WAAW,CAAC,GAC1F;AAAA,QACC,UACC,oBAAC,UAAO,MAAK,UAAS,SAAQ,QAAO,MAAK,MAAK,SAAS,MAAM,eAAe,CAAC,UAAU,QAAQ,CAAC,GAC9F,YAAE,iCAAiC,WAAW,GACjD,IACE;AAAA,SACN,IACE;AAAA,OACN;AAAA,KAEJ;AAEJ;AAEA,IAAO,4BAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.9.1-develop.
|
|
3
|
+
"version": "0.9.1-develop.7464.1.9a392fca41",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -264,16 +264,16 @@
|
|
|
264
264
|
"zod": "^4.4.3"
|
|
265
265
|
},
|
|
266
266
|
"peerDependencies": {
|
|
267
|
-
"@open-mercato/ai-assistant": "0.9.1-develop.
|
|
268
|
-
"@open-mercato/shared": "0.9.1-develop.
|
|
269
|
-
"@open-mercato/ui": "0.9.1-develop.
|
|
267
|
+
"@open-mercato/ai-assistant": "0.9.1-develop.7464.1.9a392fca41",
|
|
268
|
+
"@open-mercato/shared": "0.9.1-develop.7464.1.9a392fca41",
|
|
269
|
+
"@open-mercato/ui": "0.9.1-develop.7464.1.9a392fca41",
|
|
270
270
|
"react": "^19.0.0",
|
|
271
271
|
"react-dom": "^19.0.0"
|
|
272
272
|
},
|
|
273
273
|
"devDependencies": {
|
|
274
|
-
"@open-mercato/ai-assistant": "0.9.1-develop.
|
|
275
|
-
"@open-mercato/shared": "0.9.1-develop.
|
|
276
|
-
"@open-mercato/ui": "0.9.1-develop.
|
|
274
|
+
"@open-mercato/ai-assistant": "0.9.1-develop.7464.1.9a392fca41",
|
|
275
|
+
"@open-mercato/shared": "0.9.1-develop.7464.1.9a392fca41",
|
|
276
|
+
"@open-mercato/ui": "0.9.1-develop.7464.1.9a392fca41",
|
|
277
277
|
"@testing-library/dom": "^10.4.1",
|
|
278
278
|
"@testing-library/jest-dom": "^7.0.1",
|
|
279
279
|
"@testing-library/react": "^16.3.3",
|
|
@@ -109,6 +109,7 @@ export function ActivitiesSection({
|
|
|
109
109
|
useCanonicalInteractions = false,
|
|
110
110
|
onActionChange,
|
|
111
111
|
onLoadingChange,
|
|
112
|
+
onDataRefresh,
|
|
112
113
|
refreshKey = 0,
|
|
113
114
|
onEditActivity,
|
|
114
115
|
runGuardedMutation,
|
|
@@ -294,8 +295,14 @@ export function ActivitiesSection({
|
|
|
294
295
|
} catch (err) {
|
|
295
296
|
logger.warn('Mark done failed', { component: 'ActivitiesSection', activityId, err })
|
|
296
297
|
flash(t('customers.activities.actions.markDoneError', 'Could not mark activity as done'), 'error')
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
await Promise.resolve(onDataRefresh?.())
|
|
302
|
+
} catch (err) {
|
|
303
|
+
logger.warn('Parent refresh after mark done failed', { component: 'ActivitiesSection', activityId, err })
|
|
297
304
|
}
|
|
298
|
-
}, [loadActivities, runGuardedMutation, t])
|
|
305
|
+
}, [loadActivities, onDataRefresh, runGuardedMutation, t])
|
|
299
306
|
|
|
300
307
|
const resolvedUserIdsRef = React.useRef(new Set<string>())
|
|
301
308
|
|