@open-mercato/core 0.6.7-develop.6862.1.c11a64ce0a → 0.6.7-develop.6869.1.d1ce9999ea
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/attachments/backend/config/attachments/page.js +1 -1
- package/dist/modules/attachments/backend/config/attachments/page.js.map +2 -2
- package/dist/modules/audit_logs/components/AuditLogsActions.js +1 -1
- package/dist/modules/audit_logs/components/AuditLogsActions.js.map +2 -2
- package/dist/modules/auth/frontend/login.js +2 -2
- package/dist/modules/auth/frontend/login.js.map +2 -2
- package/dist/modules/catalog/widgets/injection/product-bulk-delete/widget.js +10 -5
- package/dist/modules/catalog/widgets/injection/product-bulk-delete/widget.js.map +2 -2
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +4 -4
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +2 -2
- package/dist/modules/customers/api/utils.js +9 -6
- package/dist/modules/customers/api/utils.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +1 -1
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js +1 -1
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +2 -2
- package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js +1 -1
- package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/MiniWeekCalendar.js +7 -52
- package/dist/modules/customers/components/detail/MiniWeekCalendar.js.map +2 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +1 -1
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/legacyActivities.js +69 -0
- package/dist/modules/customers/components/detail/legacyActivities.js.map +7 -0
- package/dist/modules/customers/utils/phoneDuplicates.js +1 -2
- package/dist/modules/customers/utils/phoneDuplicates.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +3 -3
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js +5 -5
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
- package/dist/modules/entities/api/records.js +8 -4
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js +1 -1
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
- package/dist/modules/feature_toggles/components/FeatureToggleOverrideCard.js +1 -1
- package/dist/modules/feature_toggles/components/FeatureToggleOverrideCard.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js +1 -1
- package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +1 -1
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js +1 -1
- package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js +1 -1
- package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/verify/page.js +1 -1
- package/dist/modules/portal/frontend/[orgSlug]/portal/verify/page.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +11 -1
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sync_excel/widgets/injection/upload-config/widget.client.js +1 -1
- package/dist/modules/sync_excel/widgets/injection/upload-config/widget.client.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/[id]/page.js +2 -2
- package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/create/page.js +1 -1
- package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js +3 -3
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -1
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
- package/dist/modules/workflows/components/NodeEditDialog.js +3 -3
- package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
- package/dist/modules/workflows/components/NodeEditDialogCrudForm.js +3 -3
- package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +2 -2
- package/dist/modules/workflows/components/WorkflowGraphImpl.js +1 -1
- package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
- package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js +1 -1
- package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/attachments/backend/config/attachments/page.tsx +1 -1
- package/src/modules/audit_logs/components/AuditLogsActions.tsx +1 -1
- package/src/modules/auth/frontend/login.tsx +2 -2
- package/src/modules/catalog/widgets/injection/product-bulk-delete/widget.ts +15 -5
- package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +4 -4
- package/src/modules/communication_channels/i18n/de.json +2 -2
- package/src/modules/communication_channels/i18n/en.json +2 -2
- package/src/modules/communication_channels/i18n/es.json +2 -2
- package/src/modules/communication_channels/i18n/ko.json +2 -2
- package/src/modules/communication_channels/i18n/pl.json +2 -2
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +2 -2
- package/src/modules/customers/api/utils.ts +13 -6
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +1 -1
- package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +1 -1
- package/src/modules/customers/components/detail/ConfirmDealLostDialog.tsx +1 -1
- package/src/modules/customers/components/detail/MiniWeekCalendar.tsx +8 -57
- package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +1 -1
- package/src/modules/customers/components/detail/legacyActivities.ts +79 -0
- package/src/modules/customers/utils/phoneDuplicates.ts +6 -3
- package/src/modules/data_sync/backend/data-sync/page.tsx +3 -3
- package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +5 -5
- package/src/modules/entities/api/records.ts +12 -4
- package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +1 -1
- package/src/modules/feature_toggles/components/FeatureToggleOverrideCard.tsx +1 -1
- package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.tsx +1 -1
- package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +1 -1
- package/src/modules/portal/frontend/[orgSlug]/portal/reset-password/page.tsx +1 -1
- package/src/modules/portal/frontend/[orgSlug]/portal/signup/page.tsx +1 -1
- package/src/modules/portal/frontend/[orgSlug]/portal/verify/page.tsx +1 -1
- package/src/modules/sales/components/documents/ItemsSection.tsx +13 -0
- package/src/modules/sales/components/documents/lineItemTypes.ts +1 -0
- package/src/modules/sync_excel/widgets/injection/upload-config/widget.client.tsx +1 -1
- package/src/modules/workflows/backend/definitions/[id]/page.tsx +2 -2
- package/src/modules/workflows/backend/definitions/create/page.tsx +1 -1
- package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +3 -3
- package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -1
- package/src/modules/workflows/components/NodeEditDialog.tsx +3 -3
- package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +3 -3
- package/src/modules/workflows/components/WorkflowGraphImpl.tsx +1 -1
- package/src/modules/workflows/components/fields/FormFieldArrayEditor.tsx +1 -1
|
@@ -13,7 +13,7 @@ export function DnsDiagnostics({ mapping }: DiagnosticsProps) {
|
|
|
13
13
|
const t = useT()
|
|
14
14
|
if (mapping.status !== 'dns_failed') return null
|
|
15
15
|
return (
|
|
16
|
-
<Alert
|
|
16
|
+
<Alert status="error">
|
|
17
17
|
<AlertTitle>
|
|
18
18
|
{t('customer_accounts.domainMapping.dns.diagnostics.title', 'DNS configuration issue')}
|
|
19
19
|
</AlertTitle>
|
|
@@ -38,7 +38,7 @@ export function TlsDiagnostics({ mapping }: DiagnosticsProps) {
|
|
|
38
38
|
const t = useT()
|
|
39
39
|
if (mapping.status !== 'tls_failed') return null
|
|
40
40
|
return (
|
|
41
|
-
<Alert
|
|
41
|
+
<Alert status="warning">
|
|
42
42
|
<AlertTitle>
|
|
43
43
|
{t('customer_accounts.domainMapping.tls.diagnostics.title', 'SSL certificate issue')}
|
|
44
44
|
</AlertTitle>
|
|
@@ -280,10 +280,14 @@ export async function findMatchingEntityIdsWithQueryEngine({
|
|
|
280
280
|
const qe = ctx.container.resolve('queryEngine') as QueryEngine
|
|
281
281
|
const ids = new Set<string>()
|
|
282
282
|
const pageSize = 100
|
|
283
|
+
const maxPages = 1000
|
|
283
284
|
let page = 1
|
|
284
|
-
let total = 0
|
|
285
285
|
|
|
286
|
-
|
|
286
|
+
// Short-page termination: `total` is a display value, not a loop bound. The previous
|
|
287
|
+
// `while (ids.size < total)` could spin forever — `ids` is a Set, so duplicate ids across
|
|
288
|
+
// pages keep its size permanently below an inflated `total`. Fail closed at the page
|
|
289
|
+
// ceiling: a partial id set fed into advanced filtering would silently drop matches.
|
|
290
|
+
for (;;) {
|
|
287
291
|
const result = await qe.query(entityId, {
|
|
288
292
|
fields: ['id'],
|
|
289
293
|
filters,
|
|
@@ -296,16 +300,19 @@ export async function findMatchingEntityIdsWithQueryEngine({
|
|
|
296
300
|
joins,
|
|
297
301
|
})
|
|
298
302
|
|
|
299
|
-
|
|
300
|
-
for (const item of
|
|
303
|
+
const items = result.items ?? []
|
|
304
|
+
for (const item of items) {
|
|
301
305
|
const id = item && typeof item === 'object' ? (item as Record<string, unknown>).id : null
|
|
302
306
|
if (typeof id === 'string' && id.length > 0) {
|
|
303
307
|
ids.add(id)
|
|
304
308
|
}
|
|
305
309
|
}
|
|
306
|
-
if (
|
|
310
|
+
if (items.length < pageSize) break
|
|
311
|
+
if (page >= maxPages) {
|
|
312
|
+
throw new Error(`[internal] advanced-filter id resolution exceeded ${maxPages} pages; refusing to return a partial id set`)
|
|
313
|
+
}
|
|
307
314
|
page += 1
|
|
308
|
-
}
|
|
315
|
+
}
|
|
309
316
|
|
|
310
317
|
return Array.from(ids)
|
|
311
318
|
}
|
|
@@ -2750,7 +2750,7 @@ export default function DealsKanbanPage(): React.ReactElement {
|
|
|
2750
2750
|
</div>
|
|
2751
2751
|
) : firstError ? (
|
|
2752
2752
|
<div className="max-w-xl">
|
|
2753
|
-
<Alert
|
|
2753
|
+
<Alert status="error">
|
|
2754
2754
|
<AlertTitle>{translateWithFallback(t, 'ui.errors.defaultTitle', 'Something went wrong')}</AlertTitle>
|
|
2755
2755
|
<AlertDescription>
|
|
2756
2756
|
{firstError instanceof Error
|
|
@@ -189,7 +189,7 @@ function EditorBody({
|
|
|
189
189
|
// and the type switcher span both columns.
|
|
190
190
|
<div className="grid w-full grid-cols-1 items-start gap-4 lg:grid-cols-2 lg:gap-x-6">
|
|
191
191
|
{conflict ? (
|
|
192
|
-
<Alert
|
|
192
|
+
<Alert status="warning" className="rounded-lg lg:col-span-2">
|
|
193
193
|
<AlertTitle>{t('customers.calendar.editor.conflictTitle', 'Calendar conflict')}</AlertTitle>
|
|
194
194
|
<AlertDescription>{conflict}</AlertDescription>
|
|
195
195
|
</Alert>
|
|
@@ -151,7 +151,7 @@ export function ConfirmDealLostDialog({
|
|
|
151
151
|
</DialogHeader>
|
|
152
152
|
|
|
153
153
|
<div className="space-y-6 px-7 py-6">
|
|
154
|
-
<Alert
|
|
154
|
+
<Alert status="warning" className="rounded-md">
|
|
155
155
|
<AlertTitle>
|
|
156
156
|
{t('customers.deals.detail.lost.warningTitle', 'This action closes the deal')}
|
|
157
157
|
</AlertTitle>
|
|
@@ -7,7 +7,8 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
|
7
7
|
import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
8
8
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
9
9
|
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
10
|
-
import type { InteractionSummary
|
|
10
|
+
import type { InteractionSummary } from './types'
|
|
11
|
+
import { loadLegacyActivitiesInRange } from './legacyActivities'
|
|
11
12
|
|
|
12
13
|
interface MiniWeekCalendarProps {
|
|
13
14
|
entityId: string
|
|
@@ -132,61 +133,6 @@ export function MiniWeekCalendar({ entityId, useCanonicalInteractions = true, re
|
|
|
132
133
|
return items.filter(isWithinRange)
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
async function loadLegacyActivities(): Promise<InteractionSummary[]> {
|
|
136
|
-
const items: InteractionSummary[] = []
|
|
137
|
-
let page = 1
|
|
138
|
-
let totalPages = 1
|
|
139
|
-
do {
|
|
140
|
-
const payload = await readApiResultOrThrow<{
|
|
141
|
-
items?: ActivitySummary[]
|
|
142
|
-
totalPages?: number
|
|
143
|
-
}>(
|
|
144
|
-
`/api/customers/activities?entityId=${encodeURIComponent(entityId)}&page=${page}&pageSize=100&sortField=occurredAt&sortDir=desc`,
|
|
145
|
-
{ signal: controller.signal },
|
|
146
|
-
)
|
|
147
|
-
const pageItems = Array.isArray(payload?.items) ? payload.items : []
|
|
148
|
-
const mappedItems: InteractionSummary[] = pageItems.map((activity) => ({
|
|
149
|
-
id: activity.id,
|
|
150
|
-
interactionType: activity.activityType,
|
|
151
|
-
title: activity.subject ?? null,
|
|
152
|
-
body: activity.body ?? null,
|
|
153
|
-
status: 'done',
|
|
154
|
-
scheduledAt: null,
|
|
155
|
-
occurredAt: activity.occurredAt ?? null,
|
|
156
|
-
priority: null,
|
|
157
|
-
authorUserId: activity.authorUserId ?? null,
|
|
158
|
-
ownerUserId: null,
|
|
159
|
-
appearanceIcon: activity.appearanceIcon ?? null,
|
|
160
|
-
appearanceColor: activity.appearanceColor ?? null,
|
|
161
|
-
source: 'legacy-activity',
|
|
162
|
-
entityId: activity.entityId ?? null,
|
|
163
|
-
dealId: activity.dealId ?? null,
|
|
164
|
-
organizationId: null,
|
|
165
|
-
tenantId: null,
|
|
166
|
-
authorName: activity.authorName ?? null,
|
|
167
|
-
authorEmail: activity.authorEmail ?? null,
|
|
168
|
-
dealTitle: activity.dealTitle ?? null,
|
|
169
|
-
customValues: null,
|
|
170
|
-
createdAt: activity.createdAt,
|
|
171
|
-
updatedAt: activity.createdAt,
|
|
172
|
-
}))
|
|
173
|
-
items.push(...mappedItems.filter(isWithinRange))
|
|
174
|
-
totalPages = typeof payload?.totalPages === 'number' ? payload.totalPages : totalPages
|
|
175
|
-
if (!pageItems.length) break
|
|
176
|
-
const oldestPageTimestamp = pageItems.reduce<number>((oldest, activity) => {
|
|
177
|
-
const rawDate = activity.occurredAt ?? activity.createdAt
|
|
178
|
-
const timestamp = rawDate ? new Date(rawDate).getTime() : Number.NaN
|
|
179
|
-
if (!Number.isFinite(timestamp)) return oldest
|
|
180
|
-
return Math.min(oldest, timestamp)
|
|
181
|
-
}, Number.POSITIVE_INFINITY)
|
|
182
|
-
if (Number.isFinite(oldestPageTimestamp) && oldestPageTimestamp < rangeStart.getTime()) {
|
|
183
|
-
break
|
|
184
|
-
}
|
|
185
|
-
page += 1
|
|
186
|
-
} while (page <= totalPages)
|
|
187
|
-
return items
|
|
188
|
-
}
|
|
189
|
-
|
|
190
136
|
void (async () => {
|
|
191
137
|
try {
|
|
192
138
|
const canonicalItems = await loadCanonicalInteractions()
|
|
@@ -194,7 +140,12 @@ export function MiniWeekCalendar({ entityId, useCanonicalInteractions = true, re
|
|
|
194
140
|
setEvents(canonicalItems)
|
|
195
141
|
return
|
|
196
142
|
}
|
|
197
|
-
const legacyItems = await
|
|
143
|
+
const legacyItems = await loadLegacyActivitiesInRange({
|
|
144
|
+
entityId,
|
|
145
|
+
rangeStart,
|
|
146
|
+
isWithinRange,
|
|
147
|
+
signal: controller.signal,
|
|
148
|
+
})
|
|
198
149
|
const seen = new Set<string>()
|
|
199
150
|
const merged: InteractionSummary[] = []
|
|
200
151
|
for (const item of [...canonicalItems, ...legacyItems]) {
|
|
@@ -511,7 +511,7 @@ export function ScheduleActivityDialog({
|
|
|
511
511
|
|
|
512
512
|
{/* Conflict warning */}
|
|
513
513
|
{state.conflict && (
|
|
514
|
-
<Alert
|
|
514
|
+
<Alert status="warning" className="rounded-lg">
|
|
515
515
|
<AlertTitle>
|
|
516
516
|
{t('customers.schedule.conflict.title', 'Calendar conflict')}
|
|
517
517
|
</AlertTitle>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
2
|
+
import type { ActivitySummary, InteractionSummary } from './types'
|
|
3
|
+
|
|
4
|
+
export const LEGACY_ACTIVITIES_PAGE_SIZE = 100
|
|
5
|
+
export const LEGACY_ACTIVITIES_MAX_PAGES = 1000
|
|
6
|
+
|
|
7
|
+
function mapActivityToInteraction(activity: ActivitySummary): InteractionSummary {
|
|
8
|
+
return {
|
|
9
|
+
id: activity.id,
|
|
10
|
+
interactionType: activity.activityType,
|
|
11
|
+
title: activity.subject ?? null,
|
|
12
|
+
body: activity.body ?? null,
|
|
13
|
+
status: 'done',
|
|
14
|
+
scheduledAt: null,
|
|
15
|
+
occurredAt: activity.occurredAt ?? null,
|
|
16
|
+
priority: null,
|
|
17
|
+
authorUserId: activity.authorUserId ?? null,
|
|
18
|
+
ownerUserId: null,
|
|
19
|
+
appearanceIcon: activity.appearanceIcon ?? null,
|
|
20
|
+
appearanceColor: activity.appearanceColor ?? null,
|
|
21
|
+
source: 'legacy-activity',
|
|
22
|
+
entityId: activity.entityId ?? null,
|
|
23
|
+
dealId: activity.dealId ?? null,
|
|
24
|
+
organizationId: null,
|
|
25
|
+
tenantId: null,
|
|
26
|
+
authorName: activity.authorName ?? null,
|
|
27
|
+
authorEmail: activity.authorEmail ?? null,
|
|
28
|
+
dealTitle: activity.dealTitle ?? null,
|
|
29
|
+
customValues: null,
|
|
30
|
+
createdAt: activity.createdAt,
|
|
31
|
+
updatedAt: activity.createdAt,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function loadLegacyActivitiesInRange({
|
|
36
|
+
entityId,
|
|
37
|
+
rangeStart,
|
|
38
|
+
isWithinRange,
|
|
39
|
+
signal,
|
|
40
|
+
}: {
|
|
41
|
+
entityId: string
|
|
42
|
+
rangeStart: Date
|
|
43
|
+
isWithinRange: (item: InteractionSummary) => boolean
|
|
44
|
+
signal?: AbortSignal
|
|
45
|
+
}): Promise<InteractionSummary[]> {
|
|
46
|
+
const items: InteractionSummary[] = []
|
|
47
|
+
let page = 1
|
|
48
|
+
// Short-page termination: server-reported `totalPages` is a display value, not a loop
|
|
49
|
+
// bound — it can under-report (capped counts) or drift mid-enumeration. Pages are sorted
|
|
50
|
+
// by `occurredAt` desc, so the scan also stops once a page reaches past the requested
|
|
51
|
+
// range. Fail closed at the page ceiling rather than rendering a partial week silently.
|
|
52
|
+
for (;;) {
|
|
53
|
+
const payload = await readApiResultOrThrow<{
|
|
54
|
+
items?: ActivitySummary[]
|
|
55
|
+
totalPages?: number
|
|
56
|
+
}>(
|
|
57
|
+
`/api/customers/activities?entityId=${encodeURIComponent(entityId)}&page=${page}&pageSize=${LEGACY_ACTIVITIES_PAGE_SIZE}&sortField=occurredAt&sortDir=desc`,
|
|
58
|
+
{ signal },
|
|
59
|
+
)
|
|
60
|
+
const pageItems = Array.isArray(payload?.items) ? payload.items : []
|
|
61
|
+
items.push(...pageItems.map(mapActivityToInteraction).filter(isWithinRange))
|
|
62
|
+
if (!pageItems.length) break
|
|
63
|
+
const oldestPageTimestamp = pageItems.reduce<number>((oldest, activity) => {
|
|
64
|
+
const rawDate = activity.occurredAt ?? activity.createdAt
|
|
65
|
+
const timestamp = rawDate ? new Date(rawDate).getTime() : Number.NaN
|
|
66
|
+
if (!Number.isFinite(timestamp)) return oldest
|
|
67
|
+
return Math.min(oldest, timestamp)
|
|
68
|
+
}, Number.POSITIVE_INFINITY)
|
|
69
|
+
if (Number.isFinite(oldestPageTimestamp) && oldestPageTimestamp < rangeStart.getTime()) {
|
|
70
|
+
break
|
|
71
|
+
}
|
|
72
|
+
if (pageItems.length < LEGACY_ACTIVITIES_PAGE_SIZE) break
|
|
73
|
+
if (page >= LEGACY_ACTIVITIES_MAX_PAGES) {
|
|
74
|
+
throw new Error(`[internal] legacy activity enumeration exceeded ${LEGACY_ACTIVITIES_MAX_PAGES} pages; refusing to render a partial set`)
|
|
75
|
+
}
|
|
76
|
+
page += 1
|
|
77
|
+
}
|
|
78
|
+
return items
|
|
79
|
+
}
|
|
@@ -22,7 +22,7 @@ export async function lookupPhoneDuplicate(
|
|
|
22
22
|
for (let page = 1; page <= MAX_PAGES; page += 1) {
|
|
23
23
|
try {
|
|
24
24
|
const url = `/api/customers/people?hasPhone=true&page=${page}&pageSize=${PAGE_SIZE}&sortField=createdAt&sortDir=desc`
|
|
25
|
-
const call = await apiCall<{ items?: unknown[]
|
|
25
|
+
const call = await apiCall<{ items?: unknown[] }>(url)
|
|
26
26
|
if (!call.ok) continue
|
|
27
27
|
const payload = call.result ?? {}
|
|
28
28
|
const items = Array.isArray(payload?.items) ? payload.items : []
|
|
@@ -45,8 +45,11 @@ export async function lookupPhoneDuplicate(
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
// Short-page termination: a page shorter than requested is the end of the result
|
|
49
|
+
// set. `total` is a display value and may under-report (capped counts), so it is
|
|
50
|
+
// no longer consulted. MAX_PAGES stays as the deliberate best-effort bound — this
|
|
51
|
+
// is a duplicate hint, not an exhaustive check.
|
|
52
|
+
if (items.length < PAGE_SIZE) {
|
|
50
53
|
break
|
|
51
54
|
}
|
|
52
55
|
} catch {
|
|
@@ -950,21 +950,21 @@ export default function SyncRunsDashboardPage() {
|
|
|
950
950
|
</div>
|
|
951
951
|
|
|
952
952
|
{selectedIntegration && !selectedIntegration.isEnabled ? (
|
|
953
|
-
<Alert
|
|
953
|
+
<Alert status="warning">
|
|
954
954
|
<AlertDescription>
|
|
955
955
|
{t('integrations.detail.state.disabled', 'This integration is disabled. Enable it on the integration settings page before starting a sync.')}
|
|
956
956
|
</AlertDescription>
|
|
957
957
|
</Alert>
|
|
958
958
|
) : null}
|
|
959
959
|
{selectedIntegration && !selectedIntegration.hasCredentials ? (
|
|
960
|
-
<Alert
|
|
960
|
+
<Alert status="warning">
|
|
961
961
|
<AlertDescription>
|
|
962
962
|
{t('integrations.detail.credentials.notConfigured', 'Credentials are not configured yet. Save the integration credentials before starting a sync.')}
|
|
963
963
|
</AlertDescription>
|
|
964
964
|
</Alert>
|
|
965
965
|
) : null}
|
|
966
966
|
{selectedIntegration && selectedIntegration.canStartRun === false ? (
|
|
967
|
-
<Alert
|
|
967
|
+
<Alert status="information">
|
|
968
968
|
<AlertDescription>
|
|
969
969
|
{t('data_sync.dashboard.start.providerManaged', 'This integration starts sync runs from its own setup flow. Open the integration settings page to continue.')}
|
|
970
970
|
</AlertDescription>
|
|
@@ -393,7 +393,7 @@ export function IntegrationScheduleTab(props: IntegrationScheduleTabProps) {
|
|
|
393
393
|
|
|
394
394
|
if (!option) {
|
|
395
395
|
return (
|
|
396
|
-
<Alert
|
|
396
|
+
<Alert status="warning">
|
|
397
397
|
<AlertDescription>
|
|
398
398
|
{t('data_sync.integrationTab.notAvailable', 'This integration is not registered as a data sync provider.')}
|
|
399
399
|
</AlertDescription>
|
|
@@ -433,7 +433,7 @@ export function IntegrationScheduleTab(props: IntegrationScheduleTabProps) {
|
|
|
433
433
|
</div>
|
|
434
434
|
|
|
435
435
|
{!props.isEnabled ? (
|
|
436
|
-
<Alert
|
|
436
|
+
<Alert status="warning">
|
|
437
437
|
<AlertDescription>
|
|
438
438
|
{t('data_sync.integrationTab.integrationDisabledNotice', 'The integration is disabled. You can save schedules now, but runs will stay blocked until the integration is enabled.')}
|
|
439
439
|
</AlertDescription>
|
|
@@ -441,7 +441,7 @@ export function IntegrationScheduleTab(props: IntegrationScheduleTabProps) {
|
|
|
441
441
|
) : null}
|
|
442
442
|
|
|
443
443
|
{!props.hasCredentials ? (
|
|
444
|
-
<Alert
|
|
444
|
+
<Alert status="warning">
|
|
445
445
|
<AlertDescription>
|
|
446
446
|
{t('data_sync.integrationTab.credentialsMissingNotice', 'Credentials are still missing. Save schedules first if you want, but manual and scheduled runs will fail until credentials are configured.')}
|
|
447
447
|
</AlertDescription>
|
|
@@ -449,7 +449,7 @@ export function IntegrationScheduleTab(props: IntegrationScheduleTabProps) {
|
|
|
449
449
|
) : null}
|
|
450
450
|
|
|
451
451
|
{option.canStartRun === false ? (
|
|
452
|
-
<Alert
|
|
452
|
+
<Alert status="information">
|
|
453
453
|
<AlertDescription>
|
|
454
454
|
{t('data_sync.integrationTab.providerManagedNotice', 'This provider needs its own setup flow before a run can start. Use the provider tab on this page instead of generic schedules.')}
|
|
455
455
|
</AlertDescription>
|
|
@@ -457,7 +457,7 @@ export function IntegrationScheduleTab(props: IntegrationScheduleTabProps) {
|
|
|
457
457
|
) : null}
|
|
458
458
|
|
|
459
459
|
{rows.length === 0 ? (
|
|
460
|
-
<Alert
|
|
460
|
+
<Alert status="information">
|
|
461
461
|
<AlertDescription>
|
|
462
462
|
{t('data_sync.integrationTab.empty', 'This provider does not expose any schedulable sync entities yet.')}
|
|
463
463
|
</AlertDescription>
|
|
@@ -120,6 +120,7 @@ export const metadata = {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
const DEFAULT_EXPORT_PAGE_SIZE = 1000
|
|
123
|
+
const EXPORT_MAX_PAGES = 1000
|
|
123
124
|
|
|
124
125
|
const listRecordsQuerySchema = z
|
|
125
126
|
.object({
|
|
@@ -330,10 +331,14 @@ export async function GET(req: Request) {
|
|
|
330
331
|
}
|
|
331
332
|
|
|
332
333
|
if (requestedExport) {
|
|
333
|
-
|
|
334
|
-
|
|
334
|
+
const exportItems: any[] = exportFullRequested ? [...fullPageItems] : [...viewPageItems]
|
|
335
|
+
// Short-page termination: `total` is a display value, not a loop bound — it can
|
|
336
|
+
// under-report (capped counts) or drift while rows are inserted/deleted mid-export.
|
|
337
|
+
// Keep fetching while pages come back full; fail closed at the page ceiling rather
|
|
338
|
+
// than serializing a partial export.
|
|
339
|
+
if (rawItems.length >= pageSize) {
|
|
335
340
|
let nextPage = 2
|
|
336
|
-
|
|
341
|
+
for (;;) {
|
|
337
342
|
const nextRes = await qe.query(entityId as any, {
|
|
338
343
|
...qopts,
|
|
339
344
|
page: { page: nextPage, pageSize },
|
|
@@ -344,7 +349,10 @@ export async function GET(req: Request) {
|
|
|
344
349
|
const nextFullItems = nextRawItems.map(mapFullRow)
|
|
345
350
|
const nextBatch = exportFullRequested ? nextFullItems : nextViewItems
|
|
346
351
|
exportItems.push(...nextBatch)
|
|
347
|
-
if (
|
|
352
|
+
if (nextRawItems.length < pageSize) break
|
|
353
|
+
if (nextPage >= EXPORT_MAX_PAGES) {
|
|
354
|
+
throw new Error(`[internal] export exceeded ${EXPORT_MAX_PAGES} pages; refusing to return a partial export`)
|
|
355
|
+
}
|
|
348
356
|
nextPage += 1
|
|
349
357
|
}
|
|
350
358
|
}
|
|
@@ -574,7 +574,7 @@ export default function EditDefinitionsPage({ params }: { params?: { entityId?:
|
|
|
574
574
|
<Page>
|
|
575
575
|
<PageBody>
|
|
576
576
|
<div className="p-6">
|
|
577
|
-
<Alert
|
|
577
|
+
<Alert status="error">
|
|
578
578
|
<AlertTitle>{t('entities.userEntities.edit.errors.invalidEntityTitle', 'Invalid entity')}</AlertTitle>
|
|
579
579
|
<AlertDescription>{t('entities.userEntities.edit.errors.invalidEntityDescription', 'The requested entity ID is missing or invalid.')}</AlertDescription>
|
|
580
580
|
</Alert>
|
|
@@ -69,7 +69,7 @@ export function FeatureToggleOverrideCard({ toggleId }: { toggleId: string }) {
|
|
|
69
69
|
<CardTitle>{t('feature_toggles.override.title', 'Override')}</CardTitle>
|
|
70
70
|
</CardHeader>
|
|
71
71
|
<CardContent>
|
|
72
|
-
<Alert
|
|
72
|
+
<Alert status="error">
|
|
73
73
|
<AlertTitle>{t('ui.errors.defaultTitle', 'Something went wrong')}</AlertTitle>
|
|
74
74
|
<AlertDescription>{error.message}</AlertDescription>
|
|
75
75
|
</Alert>
|
|
@@ -126,7 +126,7 @@ export default function PortalInvitePage({ params }: Props) {
|
|
|
126
126
|
|
|
127
127
|
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
128
128
|
{error ? (
|
|
129
|
-
<Alert
|
|
129
|
+
<Alert status="error">
|
|
130
130
|
<AlertDescription>{error}</AlertDescription>
|
|
131
131
|
</Alert>
|
|
132
132
|
) : null}
|
|
@@ -101,7 +101,7 @@ export default function PortalLoginPage({ params }: Props) {
|
|
|
101
101
|
|
|
102
102
|
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
103
103
|
{error ? (
|
|
104
|
-
<Alert
|
|
104
|
+
<Alert status="error">
|
|
105
105
|
<AlertDescription>{error}</AlertDescription>
|
|
106
106
|
</Alert>
|
|
107
107
|
) : null}
|
|
@@ -140,7 +140,7 @@ export default function PortalResetPasswordPage({ params }: Props) {
|
|
|
140
140
|
|
|
141
141
|
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
142
142
|
{error ? (
|
|
143
|
-
<Alert
|
|
143
|
+
<Alert status="error">
|
|
144
144
|
<AlertDescription>{error}</AlertDescription>
|
|
145
145
|
</Alert>
|
|
146
146
|
) : null}
|
|
@@ -138,7 +138,7 @@ export default function PortalSignupPage({ params }: Props) {
|
|
|
138
138
|
|
|
139
139
|
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
140
140
|
{error ? (
|
|
141
|
-
<Alert
|
|
141
|
+
<Alert status="error">
|
|
142
142
|
<AlertDescription>{error}</AlertDescription>
|
|
143
143
|
</Alert>
|
|
144
144
|
) : null}
|
|
@@ -81,7 +81,7 @@ export default function PortalVerifyPage({ params }: Props) {
|
|
|
81
81
|
|
|
82
82
|
return (
|
|
83
83
|
<div className="mx-auto flex max-w-sm flex-col gap-4 py-12">
|
|
84
|
-
<Alert
|
|
84
|
+
<Alert status="error">
|
|
85
85
|
<AlertDescription>{error || t('portal.verify.error.generic', 'Email verification failed. Please try again.')}</AlertDescription>
|
|
86
86
|
</Alert>
|
|
87
87
|
<Button asChild variant="outline" className="rounded-lg">
|
|
@@ -239,6 +239,10 @@ export function SalesDocumentItemsSection({
|
|
|
239
239
|
typeof (item.catalog_snapshot as Record<string, unknown>).name === "string"
|
|
240
240
|
? (item.catalog_snapshot as Record<string, unknown>).name as string
|
|
241
241
|
: null;
|
|
242
|
+
const description =
|
|
243
|
+
typeof item.description === "string" && item.description.trim()
|
|
244
|
+
? item.description
|
|
245
|
+
: null;
|
|
242
246
|
const quantity = normalizeNumber(item.quantity, 0);
|
|
243
247
|
const uomFields = getUomFields(item);
|
|
244
248
|
const quantityUnit = canonicalizeUnitCode(uomFields.quantityUnit);
|
|
@@ -303,6 +307,7 @@ export function SalesDocumentItemsSection({
|
|
|
303
307
|
const record: SalesLineRecord = {
|
|
304
308
|
id,
|
|
305
309
|
name,
|
|
310
|
+
description,
|
|
306
311
|
productId:
|
|
307
312
|
typeof item.product_id === "string" ? item.product_id : null,
|
|
308
313
|
productVariantId:
|
|
@@ -725,6 +730,14 @@ export function SalesDocumentItemsSection({
|
|
|
725
730
|
{showProductSku}
|
|
726
731
|
</div>
|
|
727
732
|
) : null}
|
|
733
|
+
{item.description ? (
|
|
734
|
+
<div
|
|
735
|
+
className="text-xs text-muted-foreground line-clamp-2"
|
|
736
|
+
title={item.description}
|
|
737
|
+
>
|
|
738
|
+
{item.description}
|
|
739
|
+
</div>
|
|
740
|
+
) : null}
|
|
728
741
|
</div>
|
|
729
742
|
</div>
|
|
730
743
|
</td>
|
|
@@ -1156,7 +1156,7 @@ function StatusAlert({
|
|
|
1156
1156
|
message: string
|
|
1157
1157
|
}) {
|
|
1158
1158
|
return (
|
|
1159
|
-
<Alert
|
|
1159
|
+
<Alert status={variant === 'info' ? 'information' : variant}>
|
|
1160
1160
|
<AlertTitle>{title}</AlertTitle>
|
|
1161
1161
|
<AlertDescription>{message}</AlertDescription>
|
|
1162
1162
|
</Alert>
|
|
@@ -288,7 +288,7 @@ export default function EditWorkflowDefinitionPage() {
|
|
|
288
288
|
<Page>
|
|
289
289
|
<PageBody>
|
|
290
290
|
{isCodeOnly && (
|
|
291
|
-
<Alert
|
|
291
|
+
<Alert status="information" className="mb-4">
|
|
292
292
|
<AlertDescription className="flex items-center justify-between">
|
|
293
293
|
<span>{t('workflows.source.code.readonlyBanner')}</span>
|
|
294
294
|
<Button variant="outline" size="sm" onClick={handleCustomize}>
|
|
@@ -298,7 +298,7 @@ export default function EditWorkflowDefinitionPage() {
|
|
|
298
298
|
</Alert>
|
|
299
299
|
)}
|
|
300
300
|
{isCodeOverride && (
|
|
301
|
-
<Alert
|
|
301
|
+
<Alert status="warning" className="mb-4">
|
|
302
302
|
<AlertDescription className="flex items-center justify-between">
|
|
303
303
|
<span>{t('workflows.source.code_override.banner')}</span>
|
|
304
304
|
<Button variant="outline" size="sm" onClick={handleResetToCode}>
|
|
@@ -53,7 +53,7 @@ export default function CreateWorkflowDefinitionPage() {
|
|
|
53
53
|
return (
|
|
54
54
|
<Page>
|
|
55
55
|
<PageBody>
|
|
56
|
-
<Alert
|
|
56
|
+
<Alert status="information" icon={<Zap aria-hidden="true" />} className="mb-6">
|
|
57
57
|
<AlertTitle>{t('workflows.create.eventTriggersTitle')}</AlertTitle>
|
|
58
58
|
<AlertDescription>
|
|
59
59
|
{t('workflows.create.eventTriggersDescription')}
|
|
@@ -837,12 +837,12 @@ export default function VisualEditorPage() {
|
|
|
837
837
|
{(isCodeOnly || isCodeOverride) && (
|
|
838
838
|
<div className="shrink-0 border-b border-border bg-background px-3 py-2 md:px-6 md:py-3">
|
|
839
839
|
{isCodeOnly && (
|
|
840
|
-
<Alert
|
|
840
|
+
<Alert status="information">
|
|
841
841
|
<AlertTitle>{t('workflows.source.code.readonlyBanner')}</AlertTitle>
|
|
842
842
|
</Alert>
|
|
843
843
|
)}
|
|
844
844
|
{isCodeOverride && (
|
|
845
|
-
<Alert
|
|
845
|
+
<Alert status="warning">
|
|
846
846
|
<AlertTitle>{t('workflows.source.code_override.banner')}</AlertTitle>
|
|
847
847
|
</Alert>
|
|
848
848
|
)}
|
|
@@ -1170,7 +1170,7 @@ export default function VisualEditorPage() {
|
|
|
1170
1170
|
</div>
|
|
1171
1171
|
|
|
1172
1172
|
{/* Instructions */}
|
|
1173
|
-
<Alert
|
|
1173
|
+
<Alert status="information" className="mt-6">
|
|
1174
1174
|
<AlertTitle className="text-xs">{t('workflows.visualEditor.howToUse', 'How to use:')}</AlertTitle>
|
|
1175
1175
|
<div className="mt-2">
|
|
1176
1176
|
<ul className="list-inside list-disc space-y-1 text-xs">
|
|
@@ -301,7 +301,7 @@ export function DefinitionTriggersEditor({
|
|
|
301
301
|
</p>
|
|
302
302
|
|
|
303
303
|
{value.length === 0 ? (
|
|
304
|
-
<Alert
|
|
304
|
+
<Alert status="information">
|
|
305
305
|
<AlertTitle>{t('workflows.triggers.empty.title', 'No triggers configured')}</AlertTitle>
|
|
306
306
|
<AlertDescription>
|
|
307
307
|
{t('workflows.triggers.empty.description', 'Click "Add Trigger" to create an event trigger that automatically starts this workflow.')}
|
|
@@ -540,7 +540,7 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
540
540
|
|
|
541
541
|
<div className="space-y-4">
|
|
542
542
|
{!isEditable ? (
|
|
543
|
-
<Alert
|
|
543
|
+
<Alert status="information">
|
|
544
544
|
<AlertDescription>
|
|
545
545
|
{t('workflows.nodeEditor.endStepsNotEditable')}
|
|
546
546
|
</AlertDescription>
|
|
@@ -548,7 +548,7 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
548
548
|
) : isStartNode ? (
|
|
549
549
|
<div className="space-y-4">
|
|
550
550
|
{/* Info Alert for START nodes */}
|
|
551
|
-
<Alert
|
|
551
|
+
<Alert status="information">
|
|
552
552
|
<AlertDescription>
|
|
553
553
|
{t('workflows.nodeEditor.startStepsInfo')}
|
|
554
554
|
</AlertDescription>
|
|
@@ -691,7 +691,7 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
691
691
|
|
|
692
692
|
{/* JSON Schema Format Notice */}
|
|
693
693
|
{isJsonSchemaFormat && (
|
|
694
|
-
<Alert
|
|
694
|
+
<Alert status="information" className="mb-3">
|
|
695
695
|
<AlertDescription>
|
|
696
696
|
{t('workflows.nodeEditor.jsonSchemaFormat')}
|
|
697
697
|
</AlertDescription>
|