@open-mercato/core 0.6.4-develop.4121.1.0d7f20d229 → 0.6.4-develop.4152.1.1c429e5200
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js +1 -0
- package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users/[id]/send-reset-link.js +1 -0
- package/dist/modules/customer_accounts/api/admin/users/[id]/send-reset-link.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users/[id]/verify-email.js +1 -0
- package/dist/modules/customer_accounts/api/admin/users/[id]/verify-email.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users/[id].js +1 -0
- package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
- package/dist/modules/customer_accounts/api/email/verify.js +1 -0
- package/dist/modules/customer_accounts/api/email/verify.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/events/stream.js +20 -2
- package/dist/modules/customer_accounts/api/portal/events/stream.js.map +2 -2
- package/dist/modules/dashboards/api/widgets/data/batch/route.js +137 -0
- package/dist/modules/dashboards/api/widgets/data/batch/route.js.map +7 -0
- package/dist/modules/dashboards/api/widgets/data/route.js +1 -75
- package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
- package/dist/modules/dashboards/api/widgets/data/schema.js +85 -0
- package/dist/modules/dashboards/api/widgets/data/schema.js.map +7 -0
- package/dist/modules/dashboards/lib/widgetDataBatch.js +49 -0
- package/dist/modules/dashboards/lib/widgetDataBatch.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +19 -13
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/planner/components/availabilityRulesEditorState.js +10 -1
- package/dist/modules/planner/components/availabilityRulesEditorState.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customer_accounts/api/admin/users/[id]/reset-password.ts +1 -0
- package/src/modules/customer_accounts/api/admin/users/[id]/send-reset-link.ts +1 -0
- package/src/modules/customer_accounts/api/admin/users/[id]/verify-email.ts +1 -0
- package/src/modules/customer_accounts/api/admin/users/[id].ts +1 -0
- package/src/modules/customer_accounts/api/email/verify.ts +1 -0
- package/src/modules/customer_accounts/api/portal/events/stream.ts +23 -2
- package/src/modules/dashboards/api/widgets/data/batch/route.ts +168 -0
- package/src/modules/dashboards/api/widgets/data/route.ts +1 -90
- package/src/modules/dashboards/api/widgets/data/schema.ts +90 -0
- package/src/modules/dashboards/lib/widgetDataBatch.ts +89 -0
- package/src/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/top-customers/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/top-products/widget.client.tsx +6 -16
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +20 -13
- package/src/modules/planner/components/availabilityRulesEditorState.ts +21 -0
- package/src/modules/planner/i18n/de.json +3 -3
- package/src/modules/planner/i18n/en.json +3 -3
- package/src/modules/planner/i18n/es.json +3 -3
- package/src/modules/planner/i18n/pl.json +3 -3
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
5
|
-
import {
|
|
5
|
+
import { useWidgetData, type WidgetDataFetcher } from '@open-mercato/ui/backend/dashboard/widgetData'
|
|
6
6
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { KpiCard, type KpiTrend } from '@open-mercato/ui/backend/charts'
|
|
8
8
|
import {
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { DEFAULT_SETTINGS, hydrateSettings, type NewCustomersKpiSettings } from './config'
|
|
15
15
|
import type { WidgetDataResponse } from '../../../services/widgetDataService'
|
|
16
16
|
|
|
17
|
-
async function fetchNewCustomersData(settings: NewCustomersKpiSettings): Promise<WidgetDataResponse> {
|
|
17
|
+
async function fetchNewCustomersData(settings: NewCustomersKpiSettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {
|
|
18
18
|
const body = {
|
|
19
19
|
entityType: 'customers:entities',
|
|
20
20
|
metric: {
|
|
@@ -28,18 +28,7 @@ async function fetchNewCustomersData(settings: NewCustomersKpiSettings): Promise
|
|
|
28
28
|
comparison: settings.showComparison ? { type: 'previous_period' } : undefined,
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
method: 'POST',
|
|
33
|
-
headers: { 'Content-Type': 'application/json' },
|
|
34
|
-
body: JSON.stringify(body),
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
if (!call.ok) {
|
|
38
|
-
const errorMsg = (call.result as Record<string, unknown>)?.error
|
|
39
|
-
throw new Error(typeof errorMsg === 'string' ? errorMsg : 'Failed to fetch new customers data')
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return call.result as WidgetDataResponse
|
|
31
|
+
return fetchWidgetData<WidgetDataResponse>(body)
|
|
43
32
|
}
|
|
44
33
|
|
|
45
34
|
const NewCustomersKpiWidget: React.FC<DashboardWidgetComponentProps<NewCustomersKpiSettings>> = ({
|
|
@@ -56,12 +45,13 @@ const NewCustomersKpiWidget: React.FC<DashboardWidgetComponentProps<NewCustomers
|
|
|
56
45
|
const [loading, setLoading] = React.useState(true)
|
|
57
46
|
const [error, setError] = React.useState<string | null>(null)
|
|
58
47
|
|
|
48
|
+
const fetchWidgetData = useWidgetData()
|
|
59
49
|
const refresh = React.useCallback(async () => {
|
|
60
50
|
onRefreshStateChange?.(true)
|
|
61
51
|
setLoading(true)
|
|
62
52
|
setError(null)
|
|
63
53
|
try {
|
|
64
|
-
const data = await fetchNewCustomersData(hydrated)
|
|
54
|
+
const data = await fetchNewCustomersData(hydrated, fetchWidgetData)
|
|
65
55
|
setValue(data.value)
|
|
66
56
|
if (data.comparison) {
|
|
67
57
|
setTrend({
|
|
@@ -78,7 +68,7 @@ const NewCustomersKpiWidget: React.FC<DashboardWidgetComponentProps<NewCustomers
|
|
|
78
68
|
setLoading(false)
|
|
79
69
|
onRefreshStateChange?.(false)
|
|
80
70
|
}
|
|
81
|
-
}, [hydrated, onRefreshStateChange, t])
|
|
71
|
+
}, [hydrated, fetchWidgetData, onRefreshStateChange, t])
|
|
82
72
|
|
|
83
73
|
React.useEffect(() => {
|
|
84
74
|
refresh().catch(() => {})
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
5
|
-
import {
|
|
5
|
+
import { useWidgetData, type WidgetDataFetcher } from '@open-mercato/ui/backend/dashboard/widgetData'
|
|
6
6
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { PieChart, type PieChartDataItem } from '@open-mercato/ui/backend/charts'
|
|
8
8
|
import {
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
import { DEFAULT_SETTINGS, hydrateSettings, type OrdersByStatusSettings } from './config'
|
|
21
21
|
import type { WidgetDataResponse } from '../../../services/widgetDataService'
|
|
22
22
|
|
|
23
|
-
async function fetchOrdersByStatusData(settings: OrdersByStatusSettings): Promise<WidgetDataResponse> {
|
|
23
|
+
async function fetchOrdersByStatusData(settings: OrdersByStatusSettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {
|
|
24
24
|
const body = {
|
|
25
25
|
entityType: 'sales:orders',
|
|
26
26
|
metric: {
|
|
@@ -36,18 +36,7 @@ async function fetchOrdersByStatusData(settings: OrdersByStatusSettings): Promis
|
|
|
36
36
|
},
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
method: 'POST',
|
|
41
|
-
headers: { 'Content-Type': 'application/json' },
|
|
42
|
-
body: JSON.stringify(body),
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
if (!call.ok) {
|
|
46
|
-
const errorMsg = (call.result as Record<string, unknown>)?.error
|
|
47
|
-
throw new Error(typeof errorMsg === 'string' ? errorMsg : 'Failed to fetch orders by status data')
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return call.result as WidgetDataResponse
|
|
39
|
+
return fetchWidgetData<WidgetDataResponse>(body)
|
|
51
40
|
}
|
|
52
41
|
|
|
53
42
|
const ORDER_STATUS_KEYS: Record<string, string> = {
|
|
@@ -82,12 +71,13 @@ const OrdersByStatusWidget: React.FC<DashboardWidgetComponentProps<OrdersByStatu
|
|
|
82
71
|
const [loading, setLoading] = React.useState(true)
|
|
83
72
|
const [error, setError] = React.useState<string | null>(null)
|
|
84
73
|
|
|
74
|
+
const fetchWidgetData = useWidgetData()
|
|
85
75
|
const refresh = React.useCallback(async () => {
|
|
86
76
|
onRefreshStateChange?.(true)
|
|
87
77
|
setLoading(true)
|
|
88
78
|
setError(null)
|
|
89
79
|
try {
|
|
90
|
-
const result = await fetchOrdersByStatusData(hydrated)
|
|
80
|
+
const result = await fetchOrdersByStatusData(hydrated, fetchWidgetData)
|
|
91
81
|
const chartData = result.data.map((item) => ({
|
|
92
82
|
name: formatStatusLabel(item.groupKey as string | null, t),
|
|
93
83
|
value: item.value ?? 0,
|
|
@@ -100,7 +90,7 @@ const OrdersByStatusWidget: React.FC<DashboardWidgetComponentProps<OrdersByStatu
|
|
|
100
90
|
setLoading(false)
|
|
101
91
|
onRefreshStateChange?.(false)
|
|
102
92
|
}
|
|
103
|
-
}, [hydrated, onRefreshStateChange, t])
|
|
93
|
+
}, [hydrated, fetchWidgetData, onRefreshStateChange, t])
|
|
104
94
|
|
|
105
95
|
React.useEffect(() => {
|
|
106
96
|
refresh().catch(() => {})
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
5
|
-
import {
|
|
5
|
+
import { useWidgetData, type WidgetDataFetcher } from '@open-mercato/ui/backend/dashboard/widgetData'
|
|
6
6
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { KpiCard, type KpiTrend } from '@open-mercato/ui/backend/charts'
|
|
8
8
|
import {
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { DEFAULT_SETTINGS, hydrateSettings, type OrdersKpiSettings } from './config'
|
|
15
15
|
import type { WidgetDataResponse } from '../../../services/widgetDataService'
|
|
16
16
|
|
|
17
|
-
async function fetchOrdersData(settings: OrdersKpiSettings): Promise<WidgetDataResponse> {
|
|
17
|
+
async function fetchOrdersData(settings: OrdersKpiSettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {
|
|
18
18
|
const body = {
|
|
19
19
|
entityType: 'sales:orders',
|
|
20
20
|
metric: {
|
|
@@ -28,18 +28,7 @@ async function fetchOrdersData(settings: OrdersKpiSettings): Promise<WidgetDataR
|
|
|
28
28
|
comparison: settings.showComparison ? { type: 'previous_period' } : undefined,
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
method: 'POST',
|
|
33
|
-
headers: { 'Content-Type': 'application/json' },
|
|
34
|
-
body: JSON.stringify(body),
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
if (!call.ok) {
|
|
38
|
-
const errorMsg = (call.result as Record<string, unknown>)?.error
|
|
39
|
-
throw new Error(typeof errorMsg === 'string' ? errorMsg : 'Failed to fetch orders data')
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return call.result as WidgetDataResponse
|
|
31
|
+
return fetchWidgetData<WidgetDataResponse>(body)
|
|
43
32
|
}
|
|
44
33
|
|
|
45
34
|
const OrdersKpiWidget: React.FC<DashboardWidgetComponentProps<OrdersKpiSettings>> = ({
|
|
@@ -56,12 +45,13 @@ const OrdersKpiWidget: React.FC<DashboardWidgetComponentProps<OrdersKpiSettings>
|
|
|
56
45
|
const [loading, setLoading] = React.useState(true)
|
|
57
46
|
const [error, setError] = React.useState<string | null>(null)
|
|
58
47
|
|
|
48
|
+
const fetchWidgetData = useWidgetData()
|
|
59
49
|
const refresh = React.useCallback(async () => {
|
|
60
50
|
onRefreshStateChange?.(true)
|
|
61
51
|
setLoading(true)
|
|
62
52
|
setError(null)
|
|
63
53
|
try {
|
|
64
|
-
const data = await fetchOrdersData(hydrated)
|
|
54
|
+
const data = await fetchOrdersData(hydrated, fetchWidgetData)
|
|
65
55
|
setValue(data.value)
|
|
66
56
|
if (data.comparison) {
|
|
67
57
|
setTrend({
|
|
@@ -78,7 +68,7 @@ const OrdersKpiWidget: React.FC<DashboardWidgetComponentProps<OrdersKpiSettings>
|
|
|
78
68
|
setLoading(false)
|
|
79
69
|
onRefreshStateChange?.(false)
|
|
80
70
|
}
|
|
81
|
-
}, [hydrated, onRefreshStateChange, t])
|
|
71
|
+
}, [hydrated, fetchWidgetData, onRefreshStateChange, t])
|
|
82
72
|
|
|
83
73
|
React.useEffect(() => {
|
|
84
74
|
refresh().catch(() => {})
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
5
|
-
import {
|
|
5
|
+
import { useWidgetData, type WidgetDataFetcher } from '@open-mercato/ui/backend/dashboard/widgetData'
|
|
6
6
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { BarChart, type BarChartDataItem } from '@open-mercato/ui/backend/charts'
|
|
8
8
|
import {
|
|
@@ -14,7 +14,7 @@ import { DEFAULT_SETTINGS, hydrateSettings, type PipelineSummarySettings } from
|
|
|
14
14
|
import type { WidgetDataResponse } from '../../../services/widgetDataService'
|
|
15
15
|
import { formatCurrencyCompact } from '../../../lib/formatters'
|
|
16
16
|
|
|
17
|
-
async function fetchPipelineData(settings: PipelineSummarySettings): Promise<WidgetDataResponse> {
|
|
17
|
+
async function fetchPipelineData(settings: PipelineSummarySettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {
|
|
18
18
|
const body = {
|
|
19
19
|
entityType: 'customers:deals',
|
|
20
20
|
metric: {
|
|
@@ -31,18 +31,7 @@ async function fetchPipelineData(settings: PipelineSummarySettings): Promise<Wid
|
|
|
31
31
|
},
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
method: 'POST',
|
|
36
|
-
headers: { 'Content-Type': 'application/json' },
|
|
37
|
-
body: JSON.stringify(body),
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
if (!call.ok) {
|
|
41
|
-
const errorMsg = (call.result as Record<string, unknown>)?.error
|
|
42
|
-
throw new Error(typeof errorMsg === 'string' ? errorMsg : 'Failed to fetch pipeline data')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return call.result as WidgetDataResponse
|
|
34
|
+
return fetchWidgetData<WidgetDataResponse>(body)
|
|
46
35
|
}
|
|
47
36
|
|
|
48
37
|
function formatStageLabel(stage: unknown, t: (key: string, fallback: string) => string): string {
|
|
@@ -69,12 +58,13 @@ const PipelineSummaryWidget: React.FC<DashboardWidgetComponentProps<PipelineSumm
|
|
|
69
58
|
const [loading, setLoading] = React.useState(true)
|
|
70
59
|
const [error, setError] = React.useState<string | null>(null)
|
|
71
60
|
|
|
61
|
+
const fetchWidgetData = useWidgetData()
|
|
72
62
|
const refresh = React.useCallback(async () => {
|
|
73
63
|
onRefreshStateChange?.(true)
|
|
74
64
|
setLoading(true)
|
|
75
65
|
setError(null)
|
|
76
66
|
try {
|
|
77
|
-
const result = await fetchPipelineData(hydrated)
|
|
67
|
+
const result = await fetchPipelineData(hydrated, fetchWidgetData)
|
|
78
68
|
const chartData = result.data
|
|
79
69
|
.filter((item) => item.groupKey != null && item.groupKey !== '' && String(item.groupKey) !== '0')
|
|
80
70
|
.map((item) => ({
|
|
@@ -89,7 +79,7 @@ const PipelineSummaryWidget: React.FC<DashboardWidgetComponentProps<PipelineSumm
|
|
|
89
79
|
setLoading(false)
|
|
90
80
|
onRefreshStateChange?.(false)
|
|
91
81
|
}
|
|
92
|
-
}, [hydrated, onRefreshStateChange, t])
|
|
82
|
+
}, [hydrated, fetchWidgetData, onRefreshStateChange, t])
|
|
93
83
|
|
|
94
84
|
React.useEffect(() => {
|
|
95
85
|
refresh().catch(() => {})
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
5
|
-
import {
|
|
5
|
+
import { useWidgetData, type WidgetDataFetcher } from '@open-mercato/ui/backend/dashboard/widgetData'
|
|
6
6
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { KpiCard, type KpiTrend } from '@open-mercato/ui/backend/charts'
|
|
8
8
|
import {
|
|
@@ -15,7 +15,7 @@ import { DEFAULT_SETTINGS, hydrateSettings, type RevenueKpiSettings } from './co
|
|
|
15
15
|
import type { WidgetDataResponse } from '../../../services/widgetDataService'
|
|
16
16
|
import { formatCurrency } from '../../../lib/formatters'
|
|
17
17
|
|
|
18
|
-
async function fetchRevenueData(settings: RevenueKpiSettings): Promise<WidgetDataResponse> {
|
|
18
|
+
async function fetchRevenueData(settings: RevenueKpiSettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {
|
|
19
19
|
const body = {
|
|
20
20
|
entityType: 'sales:orders',
|
|
21
21
|
metric: {
|
|
@@ -29,18 +29,7 @@ async function fetchRevenueData(settings: RevenueKpiSettings): Promise<WidgetDat
|
|
|
29
29
|
comparison: settings.showComparison ? { type: 'previous_period' } : undefined,
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
method: 'POST',
|
|
34
|
-
headers: { 'Content-Type': 'application/json' },
|
|
35
|
-
body: JSON.stringify(body),
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
if (!call.ok) {
|
|
39
|
-
const errorMsg = (call.result as Record<string, unknown>)?.error
|
|
40
|
-
throw new Error(typeof errorMsg === 'string' ? errorMsg : 'Failed to fetch revenue data')
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return call.result as WidgetDataResponse
|
|
32
|
+
return fetchWidgetData<WidgetDataResponse>(body)
|
|
44
33
|
}
|
|
45
34
|
|
|
46
35
|
const RevenueKpiWidget: React.FC<DashboardWidgetComponentProps<RevenueKpiSettings>> = ({
|
|
@@ -57,12 +46,13 @@ const RevenueKpiWidget: React.FC<DashboardWidgetComponentProps<RevenueKpiSetting
|
|
|
57
46
|
const [loading, setLoading] = React.useState(true)
|
|
58
47
|
const [error, setError] = React.useState<string | null>(null)
|
|
59
48
|
|
|
49
|
+
const fetchWidgetData = useWidgetData()
|
|
60
50
|
const refresh = React.useCallback(async () => {
|
|
61
51
|
onRefreshStateChange?.(true)
|
|
62
52
|
setLoading(true)
|
|
63
53
|
setError(null)
|
|
64
54
|
try {
|
|
65
|
-
const data = await fetchRevenueData(hydrated)
|
|
55
|
+
const data = await fetchRevenueData(hydrated, fetchWidgetData)
|
|
66
56
|
setValue(data.value)
|
|
67
57
|
if (data.comparison) {
|
|
68
58
|
setTrend({
|
|
@@ -79,7 +69,7 @@ const RevenueKpiWidget: React.FC<DashboardWidgetComponentProps<RevenueKpiSetting
|
|
|
79
69
|
setLoading(false)
|
|
80
70
|
onRefreshStateChange?.(false)
|
|
81
71
|
}
|
|
82
|
-
}, [hydrated, onRefreshStateChange, t])
|
|
72
|
+
}, [hydrated, fetchWidgetData, onRefreshStateChange, t])
|
|
83
73
|
|
|
84
74
|
React.useEffect(() => {
|
|
85
75
|
refresh().catch(() => {})
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
5
|
-
import {
|
|
5
|
+
import { useWidgetData, type WidgetDataFetcher } from '@open-mercato/ui/backend/dashboard/widgetData'
|
|
6
6
|
import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { LineChart, type LineChartDataItem } from '@open-mercato/ui/backend/charts'
|
|
8
8
|
import {
|
|
@@ -22,7 +22,7 @@ import { DEFAULT_SETTINGS, hydrateSettings, type RevenueTrendSettings } from './
|
|
|
22
22
|
import type { WidgetDataResponse } from '../../../services/widgetDataService'
|
|
23
23
|
import { formatCurrencyCompact } from '../../../lib/formatters'
|
|
24
24
|
|
|
25
|
-
async function fetchRevenueTrendData(settings: RevenueTrendSettings): Promise<WidgetDataResponse> {
|
|
25
|
+
async function fetchRevenueTrendData(settings: RevenueTrendSettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {
|
|
26
26
|
const body = {
|
|
27
27
|
entityType: 'sales:orders',
|
|
28
28
|
metric: {
|
|
@@ -39,18 +39,7 @@ async function fetchRevenueTrendData(settings: RevenueTrendSettings): Promise<Wi
|
|
|
39
39
|
},
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
method: 'POST',
|
|
44
|
-
headers: { 'Content-Type': 'application/json' },
|
|
45
|
-
body: JSON.stringify(body),
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
if (!call.ok) {
|
|
49
|
-
const errorMsg = (call.result as Record<string, unknown>)?.error
|
|
50
|
-
throw new Error(typeof errorMsg === 'string' ? errorMsg : 'Failed to fetch revenue trend data')
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return call.result as WidgetDataResponse
|
|
42
|
+
return fetchWidgetData<WidgetDataResponse>(body)
|
|
54
43
|
}
|
|
55
44
|
|
|
56
45
|
function formatDate(dateStr: string | null, granularity: DateGranularity, locale?: string): string {
|
|
@@ -125,12 +114,13 @@ const RevenueTrendWidget: React.FC<DashboardWidgetComponentProps<RevenueTrendSet
|
|
|
125
114
|
const [loading, setLoading] = React.useState(true)
|
|
126
115
|
const [error, setError] = React.useState<string | null>(null)
|
|
127
116
|
|
|
117
|
+
const fetchWidgetData = useWidgetData()
|
|
128
118
|
const refresh = React.useCallback(async () => {
|
|
129
119
|
onRefreshStateChange?.(true)
|
|
130
120
|
setLoading(true)
|
|
131
121
|
setError(null)
|
|
132
122
|
try {
|
|
133
|
-
const result = await fetchRevenueTrendData(hydrated)
|
|
123
|
+
const result = await fetchRevenueTrendData(hydrated, fetchWidgetData)
|
|
134
124
|
const sortedData = [...result.data].sort((a, b) => {
|
|
135
125
|
const aTime = new Date(a.groupKey as string || 0).getTime()
|
|
136
126
|
const bTime = new Date(b.groupKey as string || 0).getTime()
|
|
@@ -148,7 +138,7 @@ const RevenueTrendWidget: React.FC<DashboardWidgetComponentProps<RevenueTrendSet
|
|
|
148
138
|
setLoading(false)
|
|
149
139
|
onRefreshStateChange?.(false)
|
|
150
140
|
}
|
|
151
|
-
}, [hydrated, locale, onRefreshStateChange, t])
|
|
141
|
+
}, [hydrated, fetchWidgetData, locale, onRefreshStateChange, t])
|
|
152
142
|
|
|
153
143
|
React.useEffect(() => {
|
|
154
144
|
refresh().catch(() => {})
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
5
|
-
import {
|
|
5
|
+
import { useWidgetData, type WidgetDataFetcher } from '@open-mercato/ui/backend/dashboard/widgetData'
|
|
6
6
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { BarChart, type BarChartDataItem } from '@open-mercato/ui/backend/charts'
|
|
8
8
|
import { DateRangeSelect, type DateRangePreset } from '@open-mercato/ui/backend/date-range'
|
|
@@ -11,7 +11,7 @@ import { DEFAULT_SETTINGS, hydrateSettings, type SalesByRegionSettings } from '.
|
|
|
11
11
|
import type { WidgetDataResponse } from '../../../services/widgetDataService'
|
|
12
12
|
import { formatCurrencyCompact } from '../../../lib/formatters'
|
|
13
13
|
|
|
14
|
-
async function fetchSalesByRegionData(settings: SalesByRegionSettings): Promise<WidgetDataResponse> {
|
|
14
|
+
async function fetchSalesByRegionData(settings: SalesByRegionSettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {
|
|
15
15
|
const body = {
|
|
16
16
|
entityType: 'sales:orders',
|
|
17
17
|
metric: {
|
|
@@ -28,18 +28,7 @@ async function fetchSalesByRegionData(settings: SalesByRegionSettings): Promise<
|
|
|
28
28
|
},
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
method: 'POST',
|
|
33
|
-
headers: { 'Content-Type': 'application/json' },
|
|
34
|
-
body: JSON.stringify(body),
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
if (!call.ok) {
|
|
38
|
-
const errorMsg = (call.result as Record<string, unknown>)?.error
|
|
39
|
-
throw new Error(typeof errorMsg === 'string' ? errorMsg : 'Failed to fetch sales by region data')
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return call.result as WidgetDataResponse
|
|
31
|
+
return fetchWidgetData<WidgetDataResponse>(body)
|
|
43
32
|
}
|
|
44
33
|
|
|
45
34
|
const SalesByRegionWidget: React.FC<DashboardWidgetComponentProps<SalesByRegionSettings>> = ({
|
|
@@ -55,12 +44,13 @@ const SalesByRegionWidget: React.FC<DashboardWidgetComponentProps<SalesByRegionS
|
|
|
55
44
|
const [loading, setLoading] = React.useState(true)
|
|
56
45
|
const [error, setError] = React.useState<string | null>(null)
|
|
57
46
|
|
|
47
|
+
const fetchWidgetData = useWidgetData()
|
|
58
48
|
const refresh = React.useCallback(async () => {
|
|
59
49
|
onRefreshStateChange?.(true)
|
|
60
50
|
setLoading(true)
|
|
61
51
|
setError(null)
|
|
62
52
|
try {
|
|
63
|
-
const result = await fetchSalesByRegionData(hydrated)
|
|
53
|
+
const result = await fetchSalesByRegionData(hydrated, fetchWidgetData)
|
|
64
54
|
const chartData = result.data.map((item) => ({
|
|
65
55
|
region: String(item.groupKey || t('dashboards.analytics.labels.unknown', 'Unknown')),
|
|
66
56
|
Revenue: item.value ?? 0,
|
|
@@ -73,7 +63,7 @@ const SalesByRegionWidget: React.FC<DashboardWidgetComponentProps<SalesByRegionS
|
|
|
73
63
|
setLoading(false)
|
|
74
64
|
onRefreshStateChange?.(false)
|
|
75
65
|
}
|
|
76
|
-
}, [hydrated, onRefreshStateChange, t])
|
|
66
|
+
}, [hydrated, fetchWidgetData, onRefreshStateChange, t])
|
|
77
67
|
|
|
78
68
|
React.useEffect(() => {
|
|
79
69
|
refresh().catch(() => {})
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
5
|
-
import {
|
|
5
|
+
import { useWidgetData, type WidgetDataFetcher } from '@open-mercato/ui/backend/dashboard/widgetData'
|
|
6
6
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { TopNTable, type TopNTableColumn } from '@open-mercato/ui/backend/charts'
|
|
8
8
|
import { DateRangeSelect, type DateRangePreset } from '@open-mercato/ui/backend/date-range'
|
|
@@ -17,7 +17,7 @@ type CustomerRow = {
|
|
|
17
17
|
revenue: number
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
async function fetchTopCustomersData(settings: TopCustomersSettings): Promise<WidgetDataResponse> {
|
|
20
|
+
async function fetchTopCustomersData(settings: TopCustomersSettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {
|
|
21
21
|
const body = {
|
|
22
22
|
entityType: 'sales:orders',
|
|
23
23
|
metric: {
|
|
@@ -35,18 +35,7 @@ async function fetchTopCustomersData(settings: TopCustomersSettings): Promise<Wi
|
|
|
35
35
|
},
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
method: 'POST',
|
|
40
|
-
headers: { 'Content-Type': 'application/json' },
|
|
41
|
-
body: JSON.stringify(body),
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
if (!call.ok) {
|
|
45
|
-
const errorMsg = (call.result as Record<string, unknown>)?.error
|
|
46
|
-
throw new Error(typeof errorMsg === 'string' ? errorMsg : 'Failed to fetch top customers data')
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return call.result as WidgetDataResponse
|
|
38
|
+
return fetchWidgetData<WidgetDataResponse>(body)
|
|
50
39
|
}
|
|
51
40
|
|
|
52
41
|
function formatCustomerName(name: string | null, unknownLabel: string): string {
|
|
@@ -90,12 +79,13 @@ const TopCustomersWidget: React.FC<DashboardWidgetComponentProps<TopCustomersSet
|
|
|
90
79
|
[t, unknownLabel],
|
|
91
80
|
)
|
|
92
81
|
|
|
82
|
+
const fetchWidgetData = useWidgetData()
|
|
93
83
|
const refresh = React.useCallback(async () => {
|
|
94
84
|
onRefreshStateChange?.(true)
|
|
95
85
|
setLoading(true)
|
|
96
86
|
setError(null)
|
|
97
87
|
try {
|
|
98
|
-
const result = await fetchTopCustomersData(hydrated)
|
|
88
|
+
const result = await fetchTopCustomersData(hydrated, fetchWidgetData)
|
|
99
89
|
const tableData: CustomerRow[] = result.data.map((item, index) => ({
|
|
100
90
|
rank: index + 1,
|
|
101
91
|
customerId: item.groupLabel || String(item.groupKey || t('dashboards.analytics.labels.unknown', 'Unknown')),
|
|
@@ -109,7 +99,7 @@ const TopCustomersWidget: React.FC<DashboardWidgetComponentProps<TopCustomersSet
|
|
|
109
99
|
setLoading(false)
|
|
110
100
|
onRefreshStateChange?.(false)
|
|
111
101
|
}
|
|
112
|
-
}, [hydrated, onRefreshStateChange, t])
|
|
102
|
+
}, [hydrated, fetchWidgetData, onRefreshStateChange, t])
|
|
113
103
|
|
|
114
104
|
React.useEffect(() => {
|
|
115
105
|
refresh().catch(() => {})
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
5
|
-
import {
|
|
5
|
+
import { useWidgetData, type WidgetDataFetcher } from '@open-mercato/ui/backend/dashboard/widgetData'
|
|
6
6
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { BarChart, type BarChartDataItem } from '@open-mercato/ui/backend/charts'
|
|
8
8
|
import { DateRangeSelect, InlineDateRangeSelect, type DateRangePreset } from '@open-mercato/ui/backend/date-range'
|
|
@@ -18,7 +18,7 @@ import { DEFAULT_SETTINGS, hydrateSettings, type TopProductsSettings } from './c
|
|
|
18
18
|
import type { WidgetDataResponse } from '../../../services/widgetDataService'
|
|
19
19
|
import { formatCurrencyCompact } from '../../../lib/formatters'
|
|
20
20
|
|
|
21
|
-
async function fetchTopProductsData(settings: TopProductsSettings): Promise<WidgetDataResponse> {
|
|
21
|
+
async function fetchTopProductsData(settings: TopProductsSettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {
|
|
22
22
|
const body = {
|
|
23
23
|
entityType: 'sales:order_lines',
|
|
24
24
|
metric: {
|
|
@@ -36,18 +36,7 @@ async function fetchTopProductsData(settings: TopProductsSettings): Promise<Widg
|
|
|
36
36
|
},
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
method: 'POST',
|
|
41
|
-
headers: { 'Content-Type': 'application/json' },
|
|
42
|
-
body: JSON.stringify(body),
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
if (!call.ok) {
|
|
46
|
-
const errorMsg = (call.result as Record<string, unknown>)?.error
|
|
47
|
-
throw new Error(typeof errorMsg === 'string' ? errorMsg : 'Failed to fetch top products data')
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return call.result as WidgetDataResponse
|
|
39
|
+
return fetchWidgetData<WidgetDataResponse>(body)
|
|
51
40
|
}
|
|
52
41
|
|
|
53
42
|
function truncateLabel(
|
|
@@ -82,6 +71,7 @@ const TopProductsWidget: React.FC<DashboardWidgetComponentProps<TopProductsSetti
|
|
|
82
71
|
const [error, setError] = React.useState<string | null>(null)
|
|
83
72
|
const fetchingRef = React.useRef(false)
|
|
84
73
|
|
|
74
|
+
const fetchWidgetData = useWidgetData()
|
|
85
75
|
const refresh = React.useCallback(async () => {
|
|
86
76
|
if (fetchingRef.current) return
|
|
87
77
|
fetchingRef.current = true
|
|
@@ -89,7 +79,7 @@ const TopProductsWidget: React.FC<DashboardWidgetComponentProps<TopProductsSetti
|
|
|
89
79
|
setLoading(true)
|
|
90
80
|
setError(null)
|
|
91
81
|
try {
|
|
92
|
-
const result = await fetchTopProductsData(hydrated)
|
|
82
|
+
const result = await fetchTopProductsData(hydrated, fetchWidgetData)
|
|
93
83
|
const chartData = result.data.map((item, index) => ({
|
|
94
84
|
name: truncateLabel(item.groupLabel ?? item.groupKey ?? `Product ${index + 1}`, t),
|
|
95
85
|
Revenue: item.value ?? 0,
|
|
@@ -103,7 +93,7 @@ const TopProductsWidget: React.FC<DashboardWidgetComponentProps<TopProductsSetti
|
|
|
103
93
|
onRefreshStateChange?.(false)
|
|
104
94
|
fetchingRef.current = false
|
|
105
95
|
}
|
|
106
|
-
}, [hydrated, onRefreshStateChange, t])
|
|
96
|
+
}, [hydrated, fetchWidgetData, onRefreshStateChange, t])
|
|
107
97
|
|
|
108
98
|
React.useEffect(() => {
|
|
109
99
|
refresh().catch(() => {})
|
|
@@ -30,7 +30,11 @@ import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
|
30
30
|
import { parseAvailabilityRuleWindow } from '@open-mercato/core/modules/planner/lib/availabilitySchedule'
|
|
31
31
|
import { CrudForm, type CrudField } from '@open-mercato/ui/backend/CrudForm'
|
|
32
32
|
import { Calendar, Clock, List, PencilLine, Plus, Trash2 } from 'lucide-react'
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
resolveRuleSetSelectValue,
|
|
35
|
+
requiresResetConfirmation,
|
|
36
|
+
selectCustomRuleIdsToDelete,
|
|
37
|
+
} from './availabilityRulesEditorState'
|
|
34
38
|
|
|
35
39
|
type AvailabilityRepeat = 'once' | 'daily' | 'weekly'
|
|
36
40
|
type AvailabilitySubjectType = 'member' | 'resource' | 'ruleset'
|
|
@@ -455,7 +459,7 @@ export function AvailabilityRulesEditor({
|
|
|
455
459
|
ruleSetPlaceholder: t(`${labelPrefix}.availability.ruleset.placeholder`, 'Custom schedule'),
|
|
456
460
|
ruleSetCustomize: t(`${labelPrefix}.availability.ruleset.customize`, 'Customize schedule'),
|
|
457
461
|
ruleSetReset: t(`${labelPrefix}.availability.ruleset.reset`, 'Reset to schedule'),
|
|
458
|
-
ruleSetConfirm: t(`${labelPrefix}.availability.ruleset.confirm`, '
|
|
462
|
+
ruleSetConfirm: t(`${labelPrefix}.availability.ruleset.confirm`, 'Resetting to the schedule will delete your custom hours. Continue?'),
|
|
459
463
|
ruleSetLoading: t(`${labelPrefix}.availability.ruleset.loading`, 'Loading schedules...'),
|
|
460
464
|
ruleSetError: t(`${labelPrefix}.availability.ruleset.error`, 'Failed to load schedules.'),
|
|
461
465
|
ruleSetCreateLabel: t(`${labelPrefix}.availability.ruleset.create`, 'New schedule'),
|
|
@@ -957,9 +961,17 @@ export function AvailabilityRulesEditor({
|
|
|
957
961
|
const handleResetToRuleSet = React.useCallback(async () => {
|
|
958
962
|
if (isReadOnly) return
|
|
959
963
|
if (!effectiveRulesetId) return
|
|
964
|
+
if (requiresResetConfirmation(availabilityRules)) {
|
|
965
|
+
const confirmed = await confirm({
|
|
966
|
+
title: listLabels.ruleSetConfirm,
|
|
967
|
+
variant: 'destructive',
|
|
968
|
+
})
|
|
969
|
+
if (!confirmed) return
|
|
970
|
+
}
|
|
960
971
|
try {
|
|
972
|
+
const idsToDelete = selectCustomRuleIdsToDelete('reset', availabilityRules)
|
|
961
973
|
await Promise.all(
|
|
962
|
-
|
|
974
|
+
idsToDelete.map((id) => deleteCrud('planner/availability', id, { errorMessage: listLabels.saveWeeklyError })),
|
|
963
975
|
)
|
|
964
976
|
setCustomOverridesEnabled(false)
|
|
965
977
|
await refreshAvailability()
|
|
@@ -967,19 +979,16 @@ export function AvailabilityRulesEditor({
|
|
|
967
979
|
const message = error instanceof Error ? error.message : listLabels.saveWeeklyError
|
|
968
980
|
flash(message, 'error')
|
|
969
981
|
}
|
|
970
|
-
}, [availabilityRules, effectiveRulesetId, listLabels.saveWeeklyError, refreshAvailability, isReadOnly])
|
|
982
|
+
}, [availabilityRules, confirm, effectiveRulesetId, listLabels.ruleSetConfirm, listLabels.saveWeeklyError, refreshAvailability, isReadOnly])
|
|
971
983
|
|
|
972
984
|
const handleRuleSetChange = React.useCallback(async (nextId: string | null) => {
|
|
973
985
|
if (isReadOnly) return
|
|
974
986
|
if (!onRulesetChange) return
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
variant: 'default',
|
|
979
|
-
})
|
|
980
|
-
if (!confirmed) return
|
|
987
|
+
// Switching preserves saved custom hours (#2325); only an explicit reset clears them.
|
|
988
|
+
const idsToDelete = selectCustomRuleIdsToDelete('switch', availabilityRules)
|
|
989
|
+
if (idsToDelete.length) {
|
|
981
990
|
await Promise.all(
|
|
982
|
-
|
|
991
|
+
idsToDelete.map((id) => deleteCrud('planner/availability', id, { errorMessage: listLabels.saveWeeklyError })),
|
|
983
992
|
)
|
|
984
993
|
}
|
|
985
994
|
setSelectedRulesetId(nextId)
|
|
@@ -993,9 +1002,7 @@ export function AvailabilityRulesEditor({
|
|
|
993
1002
|
}
|
|
994
1003
|
}, [
|
|
995
1004
|
availabilityRules,
|
|
996
|
-
confirm,
|
|
997
1005
|
effectiveRulesetId,
|
|
998
|
-
listLabels.ruleSetConfirm,
|
|
999
1006
|
listLabels.saveWeeklyError,
|
|
1000
1007
|
onRulesetChange,
|
|
1001
1008
|
refreshAvailability,
|
|
@@ -2,6 +2,12 @@ export type AvailabilityRuleSetOption = {
|
|
|
2
2
|
id: string
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
export type AvailabilityRuleRef = {
|
|
6
|
+
id: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type RuleSetTransition = 'switch' | 'reset'
|
|
10
|
+
|
|
5
11
|
export function resolveRuleSetSelectValue(
|
|
6
12
|
ruleSets: AvailabilityRuleSetOption[],
|
|
7
13
|
selectedRulesetId: string | null | undefined,
|
|
@@ -9,3 +15,18 @@ export function resolveRuleSetSelectValue(
|
|
|
9
15
|
if (!selectedRulesetId) return undefined
|
|
10
16
|
return ruleSets.some((ruleSet) => ruleSet.id === selectedRulesetId) ? selectedRulesetId : undefined
|
|
11
17
|
}
|
|
18
|
+
|
|
19
|
+
// Selects which member-level custom rules to delete for a ruleset transition.
|
|
20
|
+
// Switching schedules preserves the member's saved custom hours (#2325): only
|
|
21
|
+
// an explicit "Reset to schedule" discards them so the shared schedule applies.
|
|
22
|
+
export function selectCustomRuleIdsToDelete(
|
|
23
|
+
transition: RuleSetTransition,
|
|
24
|
+
rules: AvailabilityRuleRef[],
|
|
25
|
+
): string[] {
|
|
26
|
+
if (transition === 'switch') return []
|
|
27
|
+
return Array.from(new Set(rules.map((rule) => rule.id)))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function requiresResetConfirmation(rules: AvailabilityRuleRef[]): boolean {
|
|
31
|
+
return rules.length > 0
|
|
32
|
+
}
|