@plutonhq/core-frontend 0.1.32 → 0.1.34

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.
Files changed (135) hide show
  1. package/dist-lib/@types/plans.d.ts +4 -0
  2. package/dist-lib/@types/plans.d.ts.map +1 -1
  3. package/dist-lib/components/Device/DeviceResticSettings/DeviceResticSettings.js +4 -4
  4. package/dist-lib/components/Device/DeviceResticSettings/DeviceResticSettings.js.map +1 -1
  5. package/dist-lib/components/Plan/AddPlan/AddPlan.d.ts.map +1 -1
  6. package/dist-lib/components/Plan/AddPlan/AddPlan.js +29 -24
  7. package/dist-lib/components/Plan/AddPlan/AddPlan.js.map +1 -1
  8. package/dist-lib/components/Plan/BackupProgress/BackupProgress.js +34 -33
  9. package/dist-lib/components/Plan/BackupProgress/BackupProgress.js.map +1 -1
  10. package/dist-lib/components/Plan/FilterPlans/FilterPlans.d.ts +9 -0
  11. package/dist-lib/components/Plan/FilterPlans/FilterPlans.d.ts.map +1 -0
  12. package/dist-lib/components/Plan/FilterPlans/FilterPlans.js +117 -0
  13. package/dist-lib/components/Plan/FilterPlans/FilterPlans.js.map +1 -0
  14. package/dist-lib/components/Plan/FilterPlans/FilterPlans.module.scss.js +20 -0
  15. package/dist-lib/components/Plan/FilterPlans/FilterPlans.module.scss.js.map +1 -0
  16. package/dist-lib/components/Plan/PlanForm/PlanForm.d.ts +4 -2
  17. package/dist-lib/components/Plan/PlanForm/PlanForm.d.ts.map +1 -1
  18. package/dist-lib/components/Plan/PlanForm/PlanForm.js +33 -29
  19. package/dist-lib/components/Plan/PlanForm/PlanForm.js.map +1 -1
  20. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.d.ts +2 -1
  21. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.d.ts.map +1 -1
  22. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.js +85 -57
  23. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.js.map +1 -1
  24. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.module.scss.js +11 -9
  25. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.module.scss.js.map +1 -1
  26. package/dist-lib/components/Plan/PlanItems/PlanItem.js +1 -1
  27. package/dist-lib/components/Plan/PlanItems/PlanItem.js.map +1 -1
  28. package/dist-lib/components/Plan/PlanRepair/PlanRepair.d.ts +9 -0
  29. package/dist-lib/components/Plan/PlanRepair/PlanRepair.d.ts.map +1 -0
  30. package/dist-lib/components/Plan/PlanRepair/PlanRepair.js +262 -0
  31. package/dist-lib/components/Plan/PlanRepair/PlanRepair.js.map +1 -0
  32. package/dist-lib/components/Plan/PlanRepair/PlanRepair.module.scss.js +14 -0
  33. package/dist-lib/components/Plan/PlanRepair/PlanRepair.module.scss.js.map +1 -0
  34. package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.d.ts +4 -2
  35. package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.d.ts.map +1 -1
  36. package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.js +24 -22
  37. package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.js.map +1 -1
  38. package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.d.ts +4 -2
  39. package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.d.ts.map +1 -1
  40. package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.js +39 -28
  41. package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.js.map +1 -1
  42. package/dist-lib/components/Plan/PlanSettings/PlanPerformanceSettings.js +2 -2
  43. package/dist-lib/components/Plan/PlanSettings/PlanPerformanceSettings.js.map +1 -1
  44. package/dist-lib/components/Plan/PlanSettings/PlanSettings.module.scss.js +66 -64
  45. package/dist-lib/components/Plan/PlanSettings/PlanSettings.module.scss.js.map +1 -1
  46. package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.d.ts +7 -0
  47. package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.d.ts.map +1 -0
  48. package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.js +116 -0
  49. package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.js.map +1 -0
  50. package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.module.scss.js +20 -0
  51. package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.module.scss.js.map +1 -0
  52. package/dist-lib/components/Plan/PlanStats/PlanStats.d.ts.map +1 -1
  53. package/dist-lib/components/Plan/PlanStats/PlanStats.js +29 -30
  54. package/dist-lib/components/Plan/PlanStats/PlanStats.js.map +1 -1
  55. package/dist-lib/components/Plan/PlanStats/PlanStats.module.scss.js +16 -14
  56. package/dist-lib/components/Plan/PlanStats/PlanStats.module.scss.js.map +1 -1
  57. package/dist-lib/components/common/FileManager/FileManager.module.scss.js +18 -16
  58. package/dist-lib/components/common/FileManager/FileManager.module.scss.js.map +1 -1
  59. package/dist-lib/components/common/Icon/Icon.d.ts.map +1 -1
  60. package/dist-lib/components/common/Icon/Icon.js +395 -378
  61. package/dist-lib/components/common/Icon/Icon.js.map +1 -1
  62. package/dist-lib/components/common/SortItems/SortItems.d.ts +2 -1
  63. package/dist-lib/components/common/SortItems/SortItems.d.ts.map +1 -1
  64. package/dist-lib/components/common/SortItems/SortItems.js +14 -14
  65. package/dist-lib/components/common/SortItems/SortItems.js.map +1 -1
  66. package/dist-lib/components/common/SortItems/SortItems.module.scss.js +1 -1
  67. package/dist-lib/components/common/form/MultiSelect/MultiSelect.module.scss.js +16 -16
  68. package/dist-lib/components/index.d.ts +2 -0
  69. package/dist-lib/components/index.d.ts.map +1 -1
  70. package/dist-lib/components.js +199 -195
  71. package/dist-lib/components.js.map +1 -1
  72. package/dist-lib/hooks/usePlanSingleActions.d.ts.map +1 -1
  73. package/dist-lib/hooks/usePlanSingleActions.js +22 -19
  74. package/dist-lib/hooks/usePlanSingleActions.js.map +1 -1
  75. package/dist-lib/node_modules/.pnpm/@kurkle_color@0.3.4/node_modules/@kurkle/color/dist/color.esm.js +449 -0
  76. package/dist-lib/node_modules/.pnpm/@kurkle_color@0.3.4/node_modules/@kurkle/color/dist/color.esm.js.map +1 -0
  77. package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chart.js +5219 -0
  78. package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chart.js.map +1 -0
  79. package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chunks/helpers.dataset.js +1691 -0
  80. package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chunks/helpers.dataset.js.map +1 -0
  81. package/dist-lib/node_modules/.pnpm/react-chartjs-2@5.3.1_chart.js@4.5.1_react@18.3.1/node_modules/react-chartjs-2/dist/index.js +93 -0
  82. package/dist-lib/node_modules/.pnpm/react-chartjs-2@5.3.1_chart.js@4.5.1_react@18.3.1/node_modules/react-chartjs-2/dist/index.js.map +1 -0
  83. package/dist-lib/routes/Login/Login.d.ts.map +1 -1
  84. package/dist-lib/routes/Login/Login.js +45 -36
  85. package/dist-lib/routes/Login/Login.js.map +1 -1
  86. package/dist-lib/routes/PlanSingle/PlanSingle.d.ts.map +1 -1
  87. package/dist-lib/routes/PlanSingle/PlanSingle.js +131 -118
  88. package/dist-lib/routes/PlanSingle/PlanSingle.js.map +1 -1
  89. package/dist-lib/routes/Plans/Plans.d.ts.map +1 -1
  90. package/dist-lib/routes/Plans/Plans.js +77 -51
  91. package/dist-lib/routes/Plans/Plans.js.map +1 -1
  92. package/dist-lib/services/plans.d.ts +33 -5
  93. package/dist-lib/services/plans.d.ts.map +1 -1
  94. package/dist-lib/services/plans.js +92 -67
  95. package/dist-lib/services/plans.js.map +1 -1
  96. package/dist-lib/services.js +93 -91
  97. package/dist-lib/styles/core-frontend.css +1 -1
  98. package/dist-lib/utils/helpers.d.ts +2 -0
  99. package/dist-lib/utils/helpers.d.ts.map +1 -1
  100. package/dist-lib/utils/helpers.js +68 -42
  101. package/dist-lib/utils/helpers.js.map +1 -1
  102. package/dist-lib/utils.js +36 -34
  103. package/package.json +3 -1
  104. package/src/@types/plans.ts +5 -0
  105. package/src/components/Device/DeviceResticSettings/DeviceResticSettings.tsx +2 -2
  106. package/src/components/Plan/AddPlan/AddPlan.tsx +22 -16
  107. package/src/components/Plan/BackupProgress/BackupProgress.tsx +1 -1
  108. package/src/components/Plan/FilterPlans/FilterPlans.module.scss +65 -0
  109. package/src/components/Plan/FilterPlans/FilterPlans.tsx +126 -0
  110. package/src/components/Plan/PlanForm/PlanForm.tsx +7 -1
  111. package/src/components/Plan/PlanIntegrity/PlanIntegrity.module.scss +19 -0
  112. package/src/components/Plan/PlanIntegrity/PlanIntegrity.tsx +40 -3
  113. package/src/components/Plan/PlanItems/PlanItem.tsx +1 -1
  114. package/src/components/Plan/PlanRepair/PlanRepair.module.scss +53 -0
  115. package/src/components/Plan/PlanRepair/PlanRepair.tsx +243 -0
  116. package/src/components/Plan/PlanSettings/PlanAdvancedSettings.tsx +6 -2
  117. package/src/components/Plan/PlanSettings/PlanGeneralSettings.tsx +14 -2
  118. package/src/components/Plan/PlanSettings/PlanPerformanceSettings.tsx +2 -2
  119. package/src/components/Plan/PlanSettings/PlanSettings.module.scss +8 -0
  120. package/src/components/Plan/PlanSizeChart/PlanSizeChart.module.scss +76 -0
  121. package/src/components/Plan/PlanSizeChart/PlanSizeChart.tsx +166 -0
  122. package/src/components/Plan/PlanStats/PlanStats.module.scss +16 -2
  123. package/src/components/Plan/PlanStats/PlanStats.tsx +8 -11
  124. package/src/components/common/FileManager/FileManager.module.scss +7 -0
  125. package/src/components/common/Icon/Icon.tsx +21 -0
  126. package/src/components/common/SortItems/SortItems.module.scss +3 -2
  127. package/src/components/common/SortItems/SortItems.tsx +6 -3
  128. package/src/components/common/form/MultiSelect/MultiSelect.module.scss +1 -0
  129. package/src/components/index.ts +2 -0
  130. package/src/hooks/usePlanSingleActions.tsx +26 -23
  131. package/src/routes/Login/Login.tsx +8 -2
  132. package/src/routes/PlanSingle/PlanSingle.tsx +17 -0
  133. package/src/routes/Plans/Plans.tsx +70 -35
  134. package/src/services/plans.ts +40 -4
  135. package/src/utils/helpers.ts +25 -0
@@ -1,9 +1,8 @@
1
- import { useEffect, useMemo, useState } from 'react';
1
+ import { useEffect, useState } from 'react';
2
2
  import classes from './Plans.module.scss';
3
3
  import PageHeader from '../../components/common/PageHeader/PageHeader';
4
4
  import PlanItem from '../../components/Plan/PlanItems/PlanItem';
5
5
  import AddPlan from '../../components/Plan/AddPlan/AddPlan';
6
- import TagsFilter from '../../components/common/TagsFilter/TagsFilter';
7
6
  import SearchItems from '../../components/common/SearchItems/SearchItems';
8
7
  import SortItems from '../../components/common/SortItems/SortItems';
9
8
  import { useGetPlans } from '../../services/plans';
@@ -11,14 +10,14 @@ import { Plan } from '../../@types/plans';
11
10
  import { useComponentOverride } from '../../context/ComponentOverrideContext';
12
11
  import ItemsLayout from '../../components/common/ItemsLayout/ItemsLayout';
13
12
  import SkeletonItems from '../../components/Skeleton/SkeletonItems';
13
+ import { getIntervalMinutes } from '../../utils';
14
+ import FilterPlans, { PlanFilterTypes } from '../../components/Plan/FilterPlans/FilterPlans';
14
15
 
15
16
  const Plans = () => {
16
17
  const [showSidePanel, setShowSidePanel] = useState(false);
17
18
  const [plans, setPlans] = useState<Plan[]>([]);
18
19
  const [plansLayout, setPlansLayout] = useState<'list' | 'grid'>(() => (localStorage.getItem('plans_layout') === 'grid' ? 'grid' : 'list'));
19
20
  const { data, isLoading } = useGetPlans();
20
- // const data = { result: [] };
21
- // const isLoading = true;
22
21
 
23
22
  const fetchedPlans: Plan[] = data?.result || [];
24
23
 
@@ -27,33 +26,19 @@ const Plans = () => {
27
26
  useEffect(() => {
28
27
  if (data?.result) {
29
28
  setPlans(data.result);
29
+ const sortSetting = localStorage.getItem('plans_sort');
30
+ const filterSettingsRaw = localStorage.getItem('plans_filter');
31
+ const filterSettings = filterSettingsRaw ? JSON.parse(filterSettingsRaw) : null;
32
+ if (filterSettings) {
33
+ applyFilters(filterSettings, data.result);
34
+ }
35
+ if (sortSetting) {
36
+ sortPlans(sortSetting, data.result);
37
+ }
30
38
  }
31
39
  }, [data]);
32
40
 
33
- console.log('plans :', plans);
34
-
35
- const allTags = useMemo(() => {
36
- const tags: string[] = [];
37
- if (plans && plans.length > 0) {
38
- plans.forEach((plan) => {
39
- plan.tags.forEach((tag) => {
40
- if (!tags.includes(tag)) {
41
- tags.push(tag);
42
- }
43
- });
44
- });
45
- }
46
-
47
- return tags;
48
- }, [plans]);
49
-
50
- const filterByTags = (tag: string) => {
51
- if (tag) {
52
- setPlans(fetchedPlans.filter((p) => p.tags.includes(tag)));
53
- } else {
54
- setPlans(fetchedPlans);
55
- }
56
- };
41
+ // console.log('plans :', plans);
57
42
 
58
43
  const searchPlans = (term: string) => {
59
44
  if (term) {
@@ -63,28 +48,73 @@ const Plans = () => {
63
48
  }
64
49
  };
65
50
 
66
- const sortPlans = (sortBy: string) => {
51
+ const sortPlans = (sortBy: string, plansToSort?: Plan[]) => {
52
+ const thePlans = plansToSort && plansToSort.length > 0 ? plansToSort : plans;
67
53
  switch (sortBy) {
68
54
  case 'title_asc':
69
- setPlans([...plans].sort((a, b) => a.title.localeCompare(b.title)));
55
+ setPlans([...thePlans].sort((a, b) => a.title.localeCompare(b.title)));
70
56
  break;
71
57
  case 'title_desc':
72
- setPlans([...plans].sort((a, b) => b.title.localeCompare(a.title)));
58
+ setPlans([...thePlans].sort((a, b) => b.title.localeCompare(a.title)));
73
59
  break;
74
60
  case 'created_asc':
75
- setPlans([...plans].sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt)));
61
+ setPlans([...thePlans].sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt)));
76
62
  break;
77
63
  case 'created_desc':
78
- setPlans([...plans].sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt)));
64
+ setPlans([...thePlans].sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt)));
65
+ break;
66
+ case 'size_asc':
67
+ setPlans([...thePlans].sort((a, b) => a.stats.size - b.stats.size));
68
+ break;
69
+ case 'size_desc':
70
+ setPlans([...thePlans].sort((a, b) => b.stats.size - a.stats.size));
71
+ break;
72
+ case 'run_frequency_asc':
73
+ setPlans([...thePlans].sort((a, b) => getIntervalMinutes(a.settings.interval) - getIntervalMinutes(b.settings.interval)));
74
+ break;
75
+ case 'run_frequency_desc':
76
+ setPlans([...thePlans].sort((a, b) => getIntervalMinutes(b.settings.interval) - getIntervalMinutes(a.settings.interval)));
79
77
  break;
80
78
  case '':
81
- setPlans([...plans]);
79
+ setPlans([...fetchedPlans]);
82
80
  break;
83
81
  default:
84
82
  break;
85
83
  }
86
84
  };
87
85
 
86
+ const applyFilters = (filter: Record<PlanFilterTypes, string[]> | null, plansToSort?: Plan[]) => {
87
+ if (!filter) {
88
+ setPlans(fetchedPlans);
89
+ const sortSetting = localStorage.getItem('plans_sort');
90
+ if (sortSetting) {
91
+ sortPlans(sortSetting, fetchedPlans);
92
+ }
93
+ return;
94
+ } else {
95
+ let filteredPlans = plansToSort && plansToSort.length > 0 ? plansToSort : plans;
96
+
97
+ if (filter.devices.length > 0) {
98
+ filteredPlans = filteredPlans.filter((plan) => filter.devices.includes(plan.device.id));
99
+ }
100
+ if (filter.methods.length > 0) {
101
+ filteredPlans = filteredPlans.filter((plan) => filter.methods.includes(plan.method));
102
+ }
103
+ if (filter.storages.length > 0) {
104
+ filteredPlans = filteredPlans.filter((plan) => filter.storages.includes(plan.storage.id));
105
+ }
106
+ if (filter.tags.length > 0) {
107
+ filteredPlans = filteredPlans.filter((plan) => plan.tags.some((tag) => filter.tags.includes(tag)));
108
+ }
109
+
110
+ setPlans(filteredPlans);
111
+ const sortSetting = localStorage.getItem('plans_sort');
112
+ if (sortSetting) {
113
+ sortPlans(sortSetting, filteredPlans);
114
+ }
115
+ }
116
+ };
117
+
88
118
  return (
89
119
  <div className={classes.plans}>
90
120
  <PageHeader
@@ -94,17 +124,22 @@ const Plans = () => {
94
124
  buttonAction={() => setShowSidePanel(true)}
95
125
  rightSection={
96
126
  <>
97
- <TagsFilter tags={allTags} onSelect={filterByTags} />
98
127
  <SearchItems onSearch={(term) => searchPlans(term)} itemName="Plans" />
99
128
  <SortItems
129
+ id={'plans_sort'}
100
130
  options={[
101
131
  { label: 'Title (A to Z)', value: 'title_asc' },
102
132
  { label: 'Title (Z to A)', value: 'title_desc' },
133
+ { label: 'Size (Smallest First)', value: 'size_asc' },
134
+ { label: 'Size (Largest First)', value: 'size_desc' },
103
135
  { label: 'Date Created (Oldest First)', value: 'created_asc' },
104
136
  { label: 'Date Created (Newest First)', value: 'created_desc' },
137
+ { label: 'Run Frequency (Shortest First)', value: 'run_frequency_asc' },
138
+ { label: 'Run Frequency (Longest First)', value: 'run_frequency_desc' },
105
139
  ]}
106
140
  onSort={(sortBy) => sortPlans(sortBy)}
107
141
  />
142
+ <FilterPlans plans={fetchedPlans} onUpdate={(filterSettings) => applyFilters(filterSettings)} />
108
143
  <ItemsLayout onChange={(value) => setPlansLayout(value)} type="plans" value={plansLayout} />
109
144
  </>
110
145
  }
@@ -1,7 +1,13 @@
1
1
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
2
2
  import { toast } from 'react-toastify';
3
3
  import { API_URL } from '../utils/constants';
4
- import { NewPlanSettings, Plan, PlanNotification } from '../@types/plans';
4
+ import { NewPlanSettings, Plan, PlanAddRunSettings, PlanNotification } from '../@types/plans';
5
+
6
+ type BackupRunConfig = {
7
+ skipDryRun?: boolean;
8
+ skipPrune?: boolean;
9
+ ignoreErrors?: boolean;
10
+ };
5
11
 
6
12
  // Get All Plans
7
13
  export async function getAllPlans() {
@@ -137,14 +143,14 @@ export function useGetDownloadLogs() {
137
143
  }
138
144
 
139
145
  // Create New Plan
140
- export async function createPlan(newPlan: NewPlanSettings) {
146
+ export async function createPlan({ newPlan, runSettings }: { newPlan: NewPlanSettings; runSettings: PlanAddRunSettings }) {
141
147
  const header = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
142
148
  console.log('newPlan :', newPlan);
143
149
  const res = await fetch(`${API_URL}/plans`, {
144
150
  method: 'POST',
145
151
  credentials: 'include',
146
152
  headers: header,
147
- body: JSON.stringify(newPlan),
153
+ body: JSON.stringify({ plan: newPlan, runSettings }),
148
154
  });
149
155
  const data = await res.json();
150
156
  if (!data.success) {
@@ -227,12 +233,13 @@ export function useDeletePlan() {
227
233
  }
228
234
 
229
235
  // Perform Backup
230
- export async function performBackup(id: string) {
236
+ export async function performBackup({ id, runConfig }: { id: string; runConfig?: BackupRunConfig }) {
231
237
  const header = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
232
238
  const res = await fetch(`${API_URL}/plans/${id}/action/backup`, {
233
239
  method: 'POST',
234
240
  credentials: 'include',
235
241
  headers: header,
242
+ body: JSON.stringify({ runConfig }),
236
243
  });
237
244
  const data = await res.json();
238
245
  if (!data.success) {
@@ -468,6 +475,35 @@ export function useCheckPlanIntegrity() {
468
475
  });
469
476
  }
470
477
 
478
+ export async function repairBackupPlan({ planId, type, replicationId }: { planId: string; type: string; replicationId?: string }) {
479
+ // const header = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
480
+ const res = await fetch(`${API_URL}/plans/${planId}/action/repair?type=${type}${replicationId ? `&replicationId=${replicationId}` : ''}`, {
481
+ method: 'POST',
482
+ credentials: 'include',
483
+ // headers: header,
484
+ });
485
+ // Check if response is ok
486
+ const data = await res.json();
487
+ if (!data.success) {
488
+ throw new Error(data.error);
489
+ }
490
+ return data;
491
+ }
492
+
493
+ export function useRepairBackupPlan() {
494
+ const queryClient = useQueryClient();
495
+ return useMutation({
496
+ mutationFn: repairBackupPlan,
497
+ onSuccess: (res, payload) => {
498
+ queryClient.invalidateQueries({ queryKey: ['plan', payload.planId] });
499
+ console.log('res :', payload, res);
500
+ },
501
+ onError: (error, payload) => {
502
+ console.error('Error repairing backup plan for planId:', payload.planId, error);
503
+ },
504
+ });
505
+ }
506
+
471
507
  export async function sendTestNotificationRequest(payload: {
472
508
  planId: string;
473
509
  notificationCase: string;
@@ -30,6 +30,8 @@ export const isMobile = (): boolean => {
30
30
  return false;
31
31
  };
32
32
 
33
+ export const isDarkMode = window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
34
+
33
35
  const formatter = new Intl.RelativeTimeFormat('en');
34
36
  export const timeAgo = (input: Date) => {
35
37
  const date = input instanceof Date ? input : new Date(input);
@@ -503,3 +505,26 @@ export const secondsToMinutes = (seconds: number) => {
503
505
  }
504
506
  return seconds > 60 ? `${Math.round(seconds / 60)} min` : `${seconds} sec`;
505
507
  };
508
+
509
+ export const getIntervalMinutes = (interval: PlanInterval): number => {
510
+ switch (interval.type) {
511
+ case 'minutes':
512
+ return interval.minutes || 5;
513
+ case 'hours':
514
+ return parseInt(interval.hours || '1') * 60;
515
+ case 'hourly':
516
+ return 60;
517
+ case 'daily':
518
+ return 1440;
519
+ case 'days': {
520
+ const dayCount = interval.days?.split(',').filter(Boolean).length || 1;
521
+ return Math.round((7 / dayCount) * 1440);
522
+ }
523
+ case 'weekly':
524
+ return 10080;
525
+ case 'monthly':
526
+ return 43200;
527
+ default:
528
+ return 1440;
529
+ }
530
+ };