@open-mercato/core 0.10.1-develop.7874.1.67e097d23a → 0.10.1-develop.7883.1.44ac2125f5
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/lib/transferClient.js +72 -0
- package/dist/modules/attachments/lib/transferClient.js.map +7 -0
- package/dist/modules/attachments/lib/transferLimits.js +5 -0
- package/dist/modules/attachments/lib/transferLimits.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js +23 -26
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/create/page.js +31 -23
- package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductsDataTable.js +7 -8
- package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
- package/dist/modules/catalog/components/products/mediaTransfer.js +27 -0
- package/dist/modules/catalog/components/products/mediaTransfer.js.map +7 -0
- package/dist/modules/customer_groups/backend/customer-groups/page.js +5 -3
- package/dist/modules/customer_groups/backend/customer-groups/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChipButton.js +4 -4
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChipButton.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/Lane.js +5 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/Lane.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +43 -31
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/push_notifications/backend/push_notifications/page.js +8 -13
- package/dist/modules/push_notifications/backend/push_notifications/page.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/attachments/lib/transferClient.ts +87 -0
- package/src/modules/attachments/lib/transferLimits.ts +1 -0
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/create/page.tsx +23 -32
- package/src/modules/catalog/backend/catalog/products/create/page.tsx +31 -28
- package/src/modules/catalog/components/products/ProductsDataTable.tsx +7 -8
- package/src/modules/catalog/components/products/mediaTransfer.ts +40 -0
- package/src/modules/catalog/i18n/de.json +2 -0
- package/src/modules/catalog/i18n/en.json +2 -0
- package/src/modules/catalog/i18n/es.json +2 -0
- package/src/modules/catalog/i18n/ko.json +2 -0
- package/src/modules/catalog/i18n/pl.json +2 -0
- package/src/modules/customer_groups/backend/customer-groups/page.tsx +5 -3
- package/src/modules/customers/backend/customers/deals/pipeline/components/ChipButton.tsx +4 -4
- package/src/modules/customers/backend/customers/deals/pipeline/components/Lane.tsx +6 -2
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +45 -32
- package/src/modules/push_notifications/backend/push_notifications/page.tsx +8 -13
|
@@ -28,16 +28,16 @@ export const ChipButton = React.forwardRef<HTMLButtonElement, ChipButtonProps>(
|
|
|
28
28
|
onClick={onClick}
|
|
29
29
|
aria-label={ariaLabel ?? (label ? `${label}: ${value}` : value)}
|
|
30
30
|
aria-pressed={active}
|
|
31
|
-
className={`inline-flex items-center gap-1.5 rounded-sm border border-border bg-card px-2.5 py-1.5 text-xs leading-normal transition-colors hover:bg-muted/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${
|
|
31
|
+
className={`inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-sm border border-border bg-card px-2.5 py-1.5 text-xs leading-normal transition-colors hover:bg-muted/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${
|
|
32
32
|
active ? 'border-foreground/40 bg-muted text-foreground' : 'text-foreground'
|
|
33
33
|
}`}
|
|
34
34
|
>
|
|
35
|
-
{label ? <span className="font-normal text-muted-foreground">{label}:</span> : null}
|
|
36
|
-
<span className={label ? 'font-semibold text-foreground' : 'font-normal text-foreground'}>
|
|
35
|
+
{label ? <span className="shrink-0 font-normal text-muted-foreground">{label}:</span> : null}
|
|
36
|
+
<span className={`min-w-0 truncate ${label ? 'font-semibold text-foreground' : 'font-normal text-foreground'}`}>
|
|
37
37
|
{value}
|
|
38
38
|
</span>
|
|
39
39
|
{withChevron ? (
|
|
40
|
-
<ChevronDown className="size-3 text-muted-foreground" aria-hidden="true" />
|
|
40
|
+
<ChevronDown className="size-3 shrink-0 text-muted-foreground" aria-hidden="true" />
|
|
41
41
|
) : null}
|
|
42
42
|
</Button>
|
|
43
43
|
)
|
|
@@ -36,6 +36,7 @@ type LaneProps = {
|
|
|
36
36
|
deals: DealCardData[]
|
|
37
37
|
/** Server-side aggregate for accurate counts/totals across all deals in this stage, not just loaded ones */
|
|
38
38
|
aggregate?: LaneAggregateProp | null
|
|
39
|
+
paginationTotal?: number
|
|
39
40
|
selectedDealIds: Set<string>
|
|
40
41
|
buildMenuItems: (deal: DealCardData) => RowActionItem[]
|
|
41
42
|
activeDragDealId?: string | null
|
|
@@ -176,6 +177,7 @@ function LaneImpl({
|
|
|
176
177
|
stage,
|
|
177
178
|
deals,
|
|
178
179
|
aggregate,
|
|
180
|
+
paginationTotal,
|
|
179
181
|
selectedDealIds,
|
|
180
182
|
buildMenuItems,
|
|
181
183
|
activeDragDealId,
|
|
@@ -212,7 +214,8 @@ function LaneImpl({
|
|
|
212
214
|
: null
|
|
213
215
|
const visibleCount = deals.length
|
|
214
216
|
const totalCount = stats.count
|
|
215
|
-
const
|
|
217
|
+
const remainingCount = (paginationTotal ?? totalCount) - visibleCount
|
|
218
|
+
const hasMoreDeals = remainingCount > 0
|
|
216
219
|
const accentClass = getAccentClass(stage.tone)
|
|
217
220
|
const countBadgeClass = getCountBadgeClass(stage.tone)
|
|
218
221
|
|
|
@@ -336,7 +339,7 @@ function LaneImpl({
|
|
|
336
339
|
t,
|
|
337
340
|
'customers.deals.kanban.lane.loadMore',
|
|
338
341
|
'Show more ({remaining})',
|
|
339
|
-
{ remaining:
|
|
342
|
+
{ remaining: remainingCount },
|
|
340
343
|
)}
|
|
341
344
|
</DashedTileButton>
|
|
342
345
|
) : null}
|
|
@@ -358,6 +361,7 @@ export const Lane = React.memo(LaneImpl, (prev, next) => {
|
|
|
358
361
|
if (prev.stage !== next.stage) return false
|
|
359
362
|
if (prev.deals !== next.deals) return false
|
|
360
363
|
if (prev.aggregate !== next.aggregate) return false
|
|
364
|
+
if (prev.paginationTotal !== next.paginationTotal) return false
|
|
361
365
|
if (prev.selectedDealIds !== next.selectedDealIds) return false
|
|
362
366
|
if (prev.buildMenuItems !== next.buildMenuItems) return false
|
|
363
367
|
if (prev.activeDragDealId !== next.activeDragDealId) return false
|
|
@@ -613,11 +613,6 @@ export default function DealsKanbanPage(): React.ReactElement {
|
|
|
613
613
|
// fetched on demand and appended here, so the cached page-1 cards stay in place (no full lane refetch).
|
|
614
614
|
const [extraCardsByStage, setExtraCardsByStage] = React.useState<Record<string, DealApiRecord[]>>({})
|
|
615
615
|
const [loadingMoreByStage, setLoadingMoreByStage] = React.useState<Record<string, boolean>>({})
|
|
616
|
-
// Reset extra-cards + loading state when any filter changes
|
|
617
|
-
React.useEffect(() => {
|
|
618
|
-
setExtraCardsByStage({})
|
|
619
|
-
setLoadingMoreByStage({})
|
|
620
|
-
}, [selectedPipelineId, search, statusFilters, ownerFilters, peopleFilters, companyFilters, closeDateFilter, currencyFilter])
|
|
621
616
|
|
|
622
617
|
// Filter signature shared by every per-lane query so all lanes invalidate together when filters change
|
|
623
618
|
const filterSignature = React.useMemo(
|
|
@@ -636,7 +631,7 @@ export default function DealsKanbanPage(): React.ReactElement {
|
|
|
636
631
|
)
|
|
637
632
|
|
|
638
633
|
// Aggregate query — gives accurate per-stage counts + totals (in base currency) across the entire pipeline,
|
|
639
|
-
// not just the loaded slice. Drives
|
|
634
|
+
// not just the loaded slice. Drives lane headers and currency breakdowns.
|
|
640
635
|
const aggregateQuery = useQuery<StageAggregateResponse | null>({
|
|
641
636
|
queryKey: [
|
|
642
637
|
'customers',
|
|
@@ -711,28 +706,40 @@ export default function DealsKanbanPage(): React.ReactElement {
|
|
|
711
706
|
return mapped ?? { sortField: 'updatedAt' as const, sortDir: 'desc' as const }
|
|
712
707
|
}, [sortBy])
|
|
713
708
|
|
|
709
|
+
const laneQueryKey = React.useMemo(
|
|
710
|
+
() => [
|
|
711
|
+
'customers',
|
|
712
|
+
'deals',
|
|
713
|
+
'kanban-lane',
|
|
714
|
+
`scope:${scopeVersion}`,
|
|
715
|
+
`organization:${activeOrgId ?? 'none'}`,
|
|
716
|
+
`pipeline:${filterSignature.pipelineId ?? 'none'}`,
|
|
717
|
+
`search:${filterSignature.search}`,
|
|
718
|
+
`status:${filterSignature.status}`,
|
|
719
|
+
`owners:${filterSignature.owners}`,
|
|
720
|
+
`people:${filterSignature.people}`,
|
|
721
|
+
`companies:${filterSignature.companies}`,
|
|
722
|
+
`close:${filterSignature.closeFrom}-${filterSignature.closeTo}`,
|
|
723
|
+
`sort:${apiSort.sortField}:${apiSort.sortDir}`,
|
|
724
|
+
`currency:${filterSignature.currency}`,
|
|
725
|
+
],
|
|
726
|
+
[scopeVersion, activeOrgId, filterSignature, apiSort],
|
|
727
|
+
)
|
|
728
|
+
const paginationGeneration = React.useRef(0)
|
|
729
|
+
|
|
730
|
+
React.useEffect(() => {
|
|
731
|
+
paginationGeneration.current += 1
|
|
732
|
+
setExtraCardsByStage({})
|
|
733
|
+
setLoadingMoreByStage({})
|
|
734
|
+
return () => {
|
|
735
|
+
paginationGeneration.current += 1
|
|
736
|
+
}
|
|
737
|
+
}, [laneQueryKey])
|
|
738
|
+
|
|
714
739
|
const laneQueries = useQueries({
|
|
715
740
|
queries: lanes.map((stage) => {
|
|
716
741
|
return {
|
|
717
|
-
queryKey: [
|
|
718
|
-
'customers',
|
|
719
|
-
'deals',
|
|
720
|
-
'kanban-lane',
|
|
721
|
-
`scope:${scopeVersion}`,
|
|
722
|
-
`pipeline:${filterSignature.pipelineId ?? 'none'}`,
|
|
723
|
-
`search:${filterSignature.search}`,
|
|
724
|
-
`status:${filterSignature.status}`,
|
|
725
|
-
`owners:${filterSignature.owners}`,
|
|
726
|
-
`people:${filterSignature.people}`,
|
|
727
|
-
`companies:${filterSignature.companies}`,
|
|
728
|
-
`close:${filterSignature.closeFrom}-${filterSignature.closeTo}`,
|
|
729
|
-
// Sort affects which 25 deals come back on page 1, so it MUST be in the key —
|
|
730
|
-
// otherwise switching from "Value (high to low)" to "Close (soonest)" reuses
|
|
731
|
-
// the old (wrong-sort) cache and silently keeps the wrong slice on screen.
|
|
732
|
-
`sort:${apiSort.sortField}:${apiSort.sortDir}`,
|
|
733
|
-
`currency:${filterSignature.currency}`,
|
|
734
|
-
`stage:${stage.id}`,
|
|
735
|
-
],
|
|
742
|
+
queryKey: [...laneQueryKey, `stage:${stage.id}`],
|
|
736
743
|
enabled: !!selectedPipelineId,
|
|
737
744
|
staleTime: 30_000,
|
|
738
745
|
queryFn: async () => {
|
|
@@ -900,6 +907,7 @@ export default function DealsKanbanPage(): React.ReactElement {
|
|
|
900
907
|
// Page 1 = first 25 (initial useQueries fetch). Subsequent pages come from extraCardsByStage.
|
|
901
908
|
const already = extraCardsByStage[stageId]?.length ?? 0
|
|
902
909
|
const nextPage = Math.floor((LANE_PAGE_SIZE + already) / LANE_PAGE_SIZE) + 1
|
|
910
|
+
const requestGeneration = paginationGeneration.current
|
|
903
911
|
setLoadingMoreByStage((prev) => ({ ...prev, [stageId]: true }))
|
|
904
912
|
try {
|
|
905
913
|
const params = new URLSearchParams()
|
|
@@ -922,18 +930,20 @@ export default function DealsKanbanPage(): React.ReactElement {
|
|
|
922
930
|
const call = await apiCall<{ items?: DealApiRecord[] }>(
|
|
923
931
|
`/api/customers/deals?${params.toString()}`,
|
|
924
932
|
)
|
|
925
|
-
if (!call.ok) return
|
|
933
|
+
if (requestGeneration !== paginationGeneration.current || !call.ok) return
|
|
926
934
|
const items = Array.isArray(call.result?.items) ? (call.result!.items as DealApiRecord[]) : []
|
|
927
935
|
setExtraCardsByStage((prev) => ({
|
|
928
936
|
...prev,
|
|
929
937
|
[stageId]: [...(prev[stageId] ?? []), ...items],
|
|
930
938
|
}))
|
|
931
939
|
} finally {
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
940
|
+
if (requestGeneration === paginationGeneration.current) {
|
|
941
|
+
setLoadingMoreByStage((prev) => {
|
|
942
|
+
const next = { ...prev }
|
|
943
|
+
delete next[stageId]
|
|
944
|
+
return next
|
|
945
|
+
})
|
|
946
|
+
}
|
|
937
947
|
}
|
|
938
948
|
},
|
|
939
949
|
[
|
|
@@ -947,7 +957,9 @@ export default function DealsKanbanPage(): React.ReactElement {
|
|
|
947
957
|
companyFilters,
|
|
948
958
|
closeDateFilter.from,
|
|
949
959
|
closeDateFilter.to,
|
|
960
|
+
currencyFilter,
|
|
950
961
|
apiSort,
|
|
962
|
+
laneQueryKey,
|
|
951
963
|
],
|
|
952
964
|
)
|
|
953
965
|
|
|
@@ -2841,7 +2853,7 @@ export default function DealsKanbanPage(): React.ReactElement {
|
|
|
2841
2853
|
/>
|
|
2842
2854
|
) : (
|
|
2843
2855
|
<>
|
|
2844
|
-
{lanes.map((stage) => {
|
|
2856
|
+
{lanes.map((stage, stageIndex) => {
|
|
2845
2857
|
const laneDeals = sortedDealsByStage.get(stage.id) ?? EMPTY_DEAL_ARRAY
|
|
2846
2858
|
return (
|
|
2847
2859
|
<Lane
|
|
@@ -2849,6 +2861,7 @@ export default function DealsKanbanPage(): React.ReactElement {
|
|
|
2849
2861
|
stage={stage}
|
|
2850
2862
|
deals={laneDeals}
|
|
2851
2863
|
aggregate={aggregateByStage.get(stage.id) ?? null}
|
|
2864
|
+
paginationTotal={laneQueries[stageIndex]?.data?.total ?? 0}
|
|
2852
2865
|
selectedDealIds={selectedDealIds}
|
|
2853
2866
|
buildMenuItems={buildMenuItems}
|
|
2854
2867
|
activeDragDealId={activeDragDealId}
|
|
@@ -8,7 +8,8 @@ import { StatusBadge, type StatusMap } from '@open-mercato/ui/primitives/status-
|
|
|
8
8
|
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
9
9
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
10
10
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
11
|
-
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
11
|
+
import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
|
|
12
|
+
import { formatDisplayDateTime } from '@open-mercato/ui/primitives/date-format'
|
|
12
13
|
import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
|
|
13
14
|
|
|
14
15
|
type PushDeliveryStatus = 'pending' | 'sending' | 'sent' | 'failed' | 'skipped' | 'expired'
|
|
@@ -44,15 +45,8 @@ const statusVariant: StatusMap<PushDeliveryStatus> = {
|
|
|
44
45
|
expired: 'warning',
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
function formatDate(value: string | null, t: (key: string) => string) {
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const date = new Date(value)
|
|
51
|
-
if (Number.isNaN(date.getTime())) return t('push_notifications.deliveries.noValue')
|
|
52
|
-
return date.toLocaleString()
|
|
53
|
-
} catch {
|
|
54
|
-
return t('push_notifications.deliveries.noValue')
|
|
55
|
-
}
|
|
48
|
+
function formatDate(value: string | null, locale: string | undefined, t: (key: string) => string) {
|
|
49
|
+
return formatDisplayDateTime(value, locale) ?? t('push_notifications.deliveries.noValue')
|
|
56
50
|
}
|
|
57
51
|
|
|
58
52
|
export default function PushDeliveriesListPage() {
|
|
@@ -64,6 +58,7 @@ export default function PushDeliveriesListPage() {
|
|
|
64
58
|
const [isLoading, setIsLoading] = React.useState(true)
|
|
65
59
|
const scopeVersion = useOrganizationScopeVersion()
|
|
66
60
|
const t = useT()
|
|
61
|
+
const dateLocale = useLocale()
|
|
67
62
|
const [filterValues, setFilterValues] = React.useState<FilterValues>({})
|
|
68
63
|
const [userOptions, setUserOptions] = React.useState<{ value: string; label: string; description?: string | null }[]>([])
|
|
69
64
|
|
|
@@ -210,14 +205,14 @@ export default function PushDeliveriesListPage() {
|
|
|
210
205
|
{
|
|
211
206
|
accessorKey: 'created_at',
|
|
212
207
|
header: t('push_notifications.deliveries.columns.created'),
|
|
213
|
-
cell: ({ row }) => formatDate(row.original.created_at, t),
|
|
208
|
+
cell: ({ row }) => formatDate(row.original.created_at, dateLocale, t),
|
|
214
209
|
},
|
|
215
210
|
{
|
|
216
211
|
accessorKey: 'sent_at',
|
|
217
212
|
header: t('push_notifications.deliveries.columns.sent'),
|
|
218
|
-
cell: ({ row }) => formatDate(row.original.sent_at, t),
|
|
213
|
+
cell: ({ row }) => formatDate(row.original.sent_at, dateLocale, t),
|
|
219
214
|
},
|
|
220
|
-
], [t, userLabelById])
|
|
215
|
+
], [t, dateLocale, userLabelById])
|
|
221
216
|
|
|
222
217
|
return (
|
|
223
218
|
<Page>
|