@djangocfg/ui-tools 2.1.130 → 2.1.132
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/README.md +57 -1
- package/dist/CronScheduler.client-REXEMXZN.mjs +67 -0
- package/dist/CronScheduler.client-REXEMXZN.mjs.map +1 -0
- package/dist/CronScheduler.client-YJ2SHYNH.cjs +72 -0
- package/dist/CronScheduler.client-YJ2SHYNH.cjs.map +1 -0
- package/dist/chunk-6G72N466.mjs +995 -0
- package/dist/chunk-6G72N466.mjs.map +1 -0
- package/dist/chunk-74JT4UIM.cjs +1015 -0
- package/dist/chunk-74JT4UIM.cjs.map +1 -0
- package/dist/index.cjs +109 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +280 -1
- package/dist/index.d.ts +280 -1
- package/dist/index.mjs +32 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
- package/src/index.ts +5 -0
- package/src/tools/CronScheduler/CronScheduler.client.tsx +140 -0
- package/src/tools/CronScheduler/CronScheduler.story.tsx +220 -0
- package/src/tools/CronScheduler/components/CronCheatsheet.tsx +101 -0
- package/src/tools/CronScheduler/components/CustomInput.tsx +67 -0
- package/src/tools/CronScheduler/components/DayChips.tsx +130 -0
- package/src/tools/CronScheduler/components/MonthDayGrid.tsx +143 -0
- package/src/tools/CronScheduler/components/SchedulePreview.tsx +103 -0
- package/src/tools/CronScheduler/components/ScheduleTypeSelector.tsx +57 -0
- package/src/tools/CronScheduler/components/TimeSelector.tsx +132 -0
- package/src/tools/CronScheduler/components/index.ts +24 -0
- package/src/tools/CronScheduler/context/CronSchedulerContext.tsx +242 -0
- package/src/tools/CronScheduler/context/hooks.ts +86 -0
- package/src/tools/CronScheduler/context/index.ts +18 -0
- package/src/tools/CronScheduler/index.tsx +91 -0
- package/src/tools/CronScheduler/lazy.tsx +67 -0
- package/src/tools/CronScheduler/types/index.ts +112 -0
- package/src/tools/CronScheduler/utils/cron-builder.ts +100 -0
- package/src/tools/CronScheduler/utils/cron-humanize.ts +218 -0
- package/src/tools/CronScheduler/utils/cron-parser.ts +188 -0
- package/src/tools/CronScheduler/utils/index.ts +12 -0
- package/src/tools/index.ts +36 -0
|
@@ -0,0 +1,995 @@
|
|
|
1
|
+
import { __name } from './chunk-CGILA3WO.mjs';
|
|
2
|
+
import { createContext, useRef, useState, useEffect, useMemo, useCallback, useContext } from 'react';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
import { Tabs, TabsList, TabsTrigger, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Input, Popover, PopoverTrigger, PopoverContent } from '@djangocfg/ui-core/components';
|
|
5
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
6
|
+
import { Clock, CheckCircle2, AlertCircle, HelpCircle, Calendar, Check, Copy } from 'lucide-react';
|
|
7
|
+
|
|
8
|
+
// src/tools/CronScheduler/utils/cron-builder.ts
|
|
9
|
+
function buildCron(state) {
|
|
10
|
+
const { type, hour, minute, weekDays, monthDays, customCron } = state;
|
|
11
|
+
const h = Math.max(0, Math.min(23, hour));
|
|
12
|
+
const m = Math.max(0, Math.min(59, minute));
|
|
13
|
+
switch (type) {
|
|
14
|
+
case "daily":
|
|
15
|
+
return `${m} ${h} * * *`;
|
|
16
|
+
case "weekly": {
|
|
17
|
+
const sortedDays = [...weekDays].sort((a, b) => a - b);
|
|
18
|
+
const daysStr = sortedDays.length > 0 ? formatNumberList(sortedDays) : "*";
|
|
19
|
+
return `${m} ${h} * * ${daysStr}`;
|
|
20
|
+
}
|
|
21
|
+
case "monthly": {
|
|
22
|
+
const sortedDays = [...monthDays].sort((a, b) => a - b);
|
|
23
|
+
const daysStr = sortedDays.length > 0 ? formatNumberList(sortedDays) : "1";
|
|
24
|
+
return `${m} ${h} ${daysStr} * *`;
|
|
25
|
+
}
|
|
26
|
+
case "custom":
|
|
27
|
+
return customCron.trim() || "* * * * *";
|
|
28
|
+
default:
|
|
29
|
+
return "0 0 * * *";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
__name(buildCron, "buildCron");
|
|
33
|
+
function formatNumberList(nums) {
|
|
34
|
+
if (nums.length === 0) return "";
|
|
35
|
+
if (nums.length === 1) return nums[0].toString();
|
|
36
|
+
const sorted = [...nums].sort((a, b) => a - b);
|
|
37
|
+
const ranges = [];
|
|
38
|
+
let start = sorted[0];
|
|
39
|
+
let end = sorted[0];
|
|
40
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
41
|
+
if (sorted[i] === end + 1) {
|
|
42
|
+
end = sorted[i];
|
|
43
|
+
} else {
|
|
44
|
+
ranges.push(start === end ? `${start}` : `${start}-${end}`);
|
|
45
|
+
start = sorted[i];
|
|
46
|
+
end = sorted[i];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
ranges.push(start === end ? `${start}` : `${start}-${end}`);
|
|
50
|
+
return ranges.join(",");
|
|
51
|
+
}
|
|
52
|
+
__name(formatNumberList, "formatNumberList");
|
|
53
|
+
|
|
54
|
+
// src/tools/CronScheduler/utils/cron-parser.ts
|
|
55
|
+
function parseCron(cron) {
|
|
56
|
+
if (!cron || typeof cron !== "string") return null;
|
|
57
|
+
const parts = cron.trim().split(/\s+/);
|
|
58
|
+
if (parts.length !== 5) return null;
|
|
59
|
+
const [minutePart, hourPart, dayOfMonthPart, monthPart, dayOfWeekPart] = parts;
|
|
60
|
+
const minute = parseField(minutePart, 0, 59);
|
|
61
|
+
if (minute === null && minutePart !== "*") return null;
|
|
62
|
+
const hour = parseField(hourPart, 0, 23);
|
|
63
|
+
if (hour === null && hourPart !== "*") return null;
|
|
64
|
+
const result = detectScheduleType(
|
|
65
|
+
minutePart,
|
|
66
|
+
hourPart,
|
|
67
|
+
dayOfMonthPart,
|
|
68
|
+
monthPart,
|
|
69
|
+
dayOfWeekPart
|
|
70
|
+
);
|
|
71
|
+
return {
|
|
72
|
+
type: result.type,
|
|
73
|
+
hour: hour ?? 0,
|
|
74
|
+
minute: minute ?? 0,
|
|
75
|
+
weekDays: result.weekDays,
|
|
76
|
+
monthDays: result.monthDays,
|
|
77
|
+
customCron: cron,
|
|
78
|
+
isValid: true
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
__name(parseCron, "parseCron");
|
|
82
|
+
function detectScheduleType(_minutePart, _hourPart, dayOfMonthPart, monthPart, dayOfWeekPart) {
|
|
83
|
+
let type = "custom";
|
|
84
|
+
let weekDays = [1, 2, 3, 4, 5];
|
|
85
|
+
let monthDays = [1];
|
|
86
|
+
if (dayOfMonthPart === "*" && monthPart === "*" && dayOfWeekPart === "*") {
|
|
87
|
+
type = "daily";
|
|
88
|
+
} else if (dayOfMonthPart === "*" && monthPart === "*" && dayOfWeekPart !== "*") {
|
|
89
|
+
type = "weekly";
|
|
90
|
+
weekDays = parseWeekDays(dayOfWeekPart);
|
|
91
|
+
} else if (dayOfMonthPart !== "*" && monthPart === "*" && dayOfWeekPart === "*") {
|
|
92
|
+
type = "monthly";
|
|
93
|
+
monthDays = parseMonthDays(dayOfMonthPart);
|
|
94
|
+
}
|
|
95
|
+
return { type, weekDays, monthDays };
|
|
96
|
+
}
|
|
97
|
+
__name(detectScheduleType, "detectScheduleType");
|
|
98
|
+
function parseField(part, min, max) {
|
|
99
|
+
if (/^\d+$/.test(part)) {
|
|
100
|
+
const num = parseInt(part, 10);
|
|
101
|
+
if (num >= min && num <= max) return num;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
__name(parseField, "parseField");
|
|
106
|
+
function parseWeekDays(part) {
|
|
107
|
+
const result = [];
|
|
108
|
+
if (part.includes("-")) {
|
|
109
|
+
const [start, end] = part.split("-").map((s) => parseInt(s, 10));
|
|
110
|
+
if (!isNaN(start) && !isNaN(end)) {
|
|
111
|
+
for (let i = start; i <= end && i <= 6; i++) {
|
|
112
|
+
if (i >= 0) result.push(i);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return result.length > 0 ? result : [1, 2, 3, 4, 5];
|
|
116
|
+
}
|
|
117
|
+
for (const segment of part.split(",")) {
|
|
118
|
+
const num = parseInt(segment.trim(), 10);
|
|
119
|
+
if (!isNaN(num) && num >= 0 && num <= 6) {
|
|
120
|
+
result.push(num);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return result.length > 0 ? result : [1, 2, 3, 4, 5];
|
|
124
|
+
}
|
|
125
|
+
__name(parseWeekDays, "parseWeekDays");
|
|
126
|
+
function parseMonthDays(part) {
|
|
127
|
+
const result = [];
|
|
128
|
+
if (part.includes("-")) {
|
|
129
|
+
const [start, end] = part.split("-").map((s) => parseInt(s, 10));
|
|
130
|
+
if (!isNaN(start) && !isNaN(end)) {
|
|
131
|
+
for (let i = start; i <= end && i <= 31; i++) {
|
|
132
|
+
if (i >= 1) result.push(i);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return result.length > 0 ? result : [1];
|
|
136
|
+
}
|
|
137
|
+
for (const segment of part.split(",")) {
|
|
138
|
+
const num = parseInt(segment.trim(), 10);
|
|
139
|
+
if (!isNaN(num) && num >= 1 && num <= 31) {
|
|
140
|
+
result.push(num);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return result.length > 0 ? result : [1];
|
|
144
|
+
}
|
|
145
|
+
__name(parseMonthDays, "parseMonthDays");
|
|
146
|
+
function isValidCron(cron) {
|
|
147
|
+
if (!cron || typeof cron !== "string") return false;
|
|
148
|
+
const parts = cron.trim().split(/\s+/);
|
|
149
|
+
if (parts.length !== 5) return false;
|
|
150
|
+
const patterns = [
|
|
151
|
+
/^(\*|\d{1,2}|\d{1,2}-\d{1,2}|\d{1,2}(,\d{1,2})*|\*\/\d{1,2})$/,
|
|
152
|
+
// minute
|
|
153
|
+
/^(\*|\d{1,2}|\d{1,2}-\d{1,2}|\d{1,2}(,\d{1,2})*|\*\/\d{1,2})$/,
|
|
154
|
+
// hour
|
|
155
|
+
/^(\*|\d{1,2}|\d{1,2}-\d{1,2}|\d{1,2}(,\d{1,2})*|\*\/\d{1,2})$/,
|
|
156
|
+
// day of month
|
|
157
|
+
/^(\*|\d{1,2}|\d{1,2}-\d{1,2}|\d{1,2}(,\d{1,2})*|\*\/\d{1,2})$/,
|
|
158
|
+
// month
|
|
159
|
+
/^(\*|\d{1}|\d{1}-\d{1}|\d{1}(,\d{1})*|\*\/\d{1,2})$/
|
|
160
|
+
// day of week
|
|
161
|
+
];
|
|
162
|
+
return parts.every((part, i) => patterns[i].test(part));
|
|
163
|
+
}
|
|
164
|
+
__name(isValidCron, "isValidCron");
|
|
165
|
+
|
|
166
|
+
// src/tools/CronScheduler/utils/cron-humanize.ts
|
|
167
|
+
var WEEKDAY_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
168
|
+
function humanizeCron(cron) {
|
|
169
|
+
if (!cron || typeof cron !== "string") return "Invalid schedule";
|
|
170
|
+
const parts = cron.trim().split(/\s+/);
|
|
171
|
+
if (parts.length !== 5) return "Invalid schedule";
|
|
172
|
+
const [minutePart, hourPart, dayOfMonthPart, monthPart, dayOfWeekPart] = parts;
|
|
173
|
+
const minute = parseInt(minutePart, 10);
|
|
174
|
+
const hour = parseInt(hourPart, 10);
|
|
175
|
+
const timeStr = formatTime(hour, minute);
|
|
176
|
+
if (minutePart === "*" && hourPart === "*" && dayOfMonthPart === "*" && monthPart === "*" && dayOfWeekPart === "*") {
|
|
177
|
+
return "Every minute";
|
|
178
|
+
}
|
|
179
|
+
if (minutePart.startsWith("*/")) {
|
|
180
|
+
const interval = parseInt(minutePart.slice(2), 10);
|
|
181
|
+
if (hourPart === "*") {
|
|
182
|
+
return `Every ${interval} minute${interval > 1 ? "s" : ""}`;
|
|
183
|
+
}
|
|
184
|
+
return `Every ${interval} minute${interval > 1 ? "s" : ""} at hour ${hour}`;
|
|
185
|
+
}
|
|
186
|
+
if (!isNaN(minute) && hourPart === "*" && dayOfMonthPart === "*" && monthPart === "*" && dayOfWeekPart === "*") {
|
|
187
|
+
return `Every hour at minute ${minute}`;
|
|
188
|
+
}
|
|
189
|
+
if (dayOfMonthPart === "*" && monthPart === "*" && dayOfWeekPart === "*") {
|
|
190
|
+
return `Every day at ${timeStr}`;
|
|
191
|
+
}
|
|
192
|
+
if (dayOfMonthPart === "*" && monthPart === "*" && dayOfWeekPart !== "*") {
|
|
193
|
+
const days = parseWeekDays2(dayOfWeekPart);
|
|
194
|
+
if (days.length === 7) {
|
|
195
|
+
return `Every day at ${timeStr}`;
|
|
196
|
+
}
|
|
197
|
+
if (days.length === 5 && isWeekdays(days)) {
|
|
198
|
+
return `Weekdays at ${timeStr}`;
|
|
199
|
+
}
|
|
200
|
+
if (days.length === 2 && isWeekend(days)) {
|
|
201
|
+
return `Weekends at ${timeStr}`;
|
|
202
|
+
}
|
|
203
|
+
const dayNames = days.map((d) => WEEKDAY_SHORT[d]).join(", ");
|
|
204
|
+
return `${dayNames} at ${timeStr}`;
|
|
205
|
+
}
|
|
206
|
+
if (dayOfMonthPart !== "*" && monthPart === "*" && dayOfWeekPart === "*") {
|
|
207
|
+
const days = parseMonthDays2(dayOfMonthPart);
|
|
208
|
+
if (days.length === 1) {
|
|
209
|
+
return `${ordinal(days[0])} of every month at ${timeStr}`;
|
|
210
|
+
}
|
|
211
|
+
if (days.length <= 3) {
|
|
212
|
+
const dayStr = days.map((d) => ordinal(d)).join(", ");
|
|
213
|
+
return `${dayStr} of every month at ${timeStr}`;
|
|
214
|
+
}
|
|
215
|
+
return `${days.length} days per month at ${timeStr}`;
|
|
216
|
+
}
|
|
217
|
+
return `Custom schedule`;
|
|
218
|
+
}
|
|
219
|
+
__name(humanizeCron, "humanizeCron");
|
|
220
|
+
function formatTime(hour, minute) {
|
|
221
|
+
const h = isNaN(hour) ? 0 : Math.max(0, Math.min(23, hour));
|
|
222
|
+
const m = isNaN(minute) ? 0 : Math.max(0, Math.min(59, minute));
|
|
223
|
+
return `${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}`;
|
|
224
|
+
}
|
|
225
|
+
__name(formatTime, "formatTime");
|
|
226
|
+
function parseWeekDays2(part) {
|
|
227
|
+
const result = [];
|
|
228
|
+
if (part.includes("-") && !part.includes(",")) {
|
|
229
|
+
const [start, end] = part.split("-").map((s) => parseInt(s, 10));
|
|
230
|
+
if (!isNaN(start) && !isNaN(end)) {
|
|
231
|
+
for (let i = start; i <= end && i <= 6; i++) {
|
|
232
|
+
if (i >= 0) result.push(i);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
for (const segment of part.split(",")) {
|
|
238
|
+
if (segment.includes("-")) {
|
|
239
|
+
const [start, end] = segment.split("-").map((s) => parseInt(s, 10));
|
|
240
|
+
if (!isNaN(start) && !isNaN(end)) {
|
|
241
|
+
for (let i = start; i <= end && i <= 6; i++) {
|
|
242
|
+
if (i >= 0) result.push(i);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
const num = parseInt(segment.trim(), 10);
|
|
247
|
+
if (!isNaN(num) && num >= 0 && num <= 6) {
|
|
248
|
+
result.push(num);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return result.sort((a, b) => a - b);
|
|
253
|
+
}
|
|
254
|
+
__name(parseWeekDays2, "parseWeekDays");
|
|
255
|
+
function parseMonthDays2(part) {
|
|
256
|
+
const result = [];
|
|
257
|
+
if (part.includes("-") && !part.includes(",")) {
|
|
258
|
+
const [start, end] = part.split("-").map((s) => parseInt(s, 10));
|
|
259
|
+
if (!isNaN(start) && !isNaN(end)) {
|
|
260
|
+
for (let i = start; i <= end && i <= 31; i++) {
|
|
261
|
+
if (i >= 1) result.push(i);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
for (const segment of part.split(",")) {
|
|
267
|
+
const num = parseInt(segment.trim(), 10);
|
|
268
|
+
if (!isNaN(num) && num >= 1 && num <= 31) {
|
|
269
|
+
result.push(num);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return result.sort((a, b) => a - b);
|
|
273
|
+
}
|
|
274
|
+
__name(parseMonthDays2, "parseMonthDays");
|
|
275
|
+
function isWeekdays(days) {
|
|
276
|
+
const sorted = [...days].sort((a, b) => a - b);
|
|
277
|
+
return sorted.join(",") === "1,2,3,4,5";
|
|
278
|
+
}
|
|
279
|
+
__name(isWeekdays, "isWeekdays");
|
|
280
|
+
function isWeekend(days) {
|
|
281
|
+
const sorted = [...days].sort((a, b) => a - b);
|
|
282
|
+
return sorted.join(",") === "0,6";
|
|
283
|
+
}
|
|
284
|
+
__name(isWeekend, "isWeekend");
|
|
285
|
+
function ordinal(n) {
|
|
286
|
+
const s = ["th", "st", "nd", "rd"];
|
|
287
|
+
const v = n % 100;
|
|
288
|
+
return n + (s[(v - 20) % 10] || s[v] || s[0]);
|
|
289
|
+
}
|
|
290
|
+
__name(ordinal, "ordinal");
|
|
291
|
+
var CronSchedulerContext = createContext(null);
|
|
292
|
+
var DEFAULT_STATE = {
|
|
293
|
+
type: "daily",
|
|
294
|
+
hour: 9,
|
|
295
|
+
minute: 0,
|
|
296
|
+
weekDays: [1, 2, 3, 4, 5],
|
|
297
|
+
// Mon-Fri
|
|
298
|
+
monthDays: [1],
|
|
299
|
+
customCron: "* * * * *",
|
|
300
|
+
isValid: true
|
|
301
|
+
};
|
|
302
|
+
function CronSchedulerProvider({
|
|
303
|
+
children,
|
|
304
|
+
value,
|
|
305
|
+
onChange,
|
|
306
|
+
defaultType = "daily"
|
|
307
|
+
}) {
|
|
308
|
+
const isInitialMount = useRef(true);
|
|
309
|
+
const onChangeRef = useRef(onChange);
|
|
310
|
+
onChangeRef.current = onChange;
|
|
311
|
+
const [state, setState] = useState(() => {
|
|
312
|
+
if (value) {
|
|
313
|
+
const parsed = parseCron(value);
|
|
314
|
+
if (parsed) return parsed;
|
|
315
|
+
}
|
|
316
|
+
return { ...DEFAULT_STATE, type: defaultType };
|
|
317
|
+
});
|
|
318
|
+
useEffect(() => {
|
|
319
|
+
if (value) {
|
|
320
|
+
const parsed = parseCron(value);
|
|
321
|
+
if (parsed) {
|
|
322
|
+
const currentCron = buildCron(state);
|
|
323
|
+
if (value !== currentCron) {
|
|
324
|
+
setState(parsed);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}, [value]);
|
|
329
|
+
const cronExpression = useMemo(() => buildCron(state), [state]);
|
|
330
|
+
const humanDescription = useMemo(() => humanizeCron(cronExpression), [cronExpression]);
|
|
331
|
+
useEffect(() => {
|
|
332
|
+
if (isInitialMount.current) {
|
|
333
|
+
isInitialMount.current = false;
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
onChangeRef.current?.(cronExpression);
|
|
337
|
+
}, [cronExpression]);
|
|
338
|
+
const setType = useCallback((type) => {
|
|
339
|
+
setState((s) => ({ ...s, type }));
|
|
340
|
+
}, []);
|
|
341
|
+
const setTime = useCallback((hour, minute) => {
|
|
342
|
+
setState((s) => ({
|
|
343
|
+
...s,
|
|
344
|
+
hour: Math.max(0, Math.min(23, hour)),
|
|
345
|
+
minute: Math.max(0, Math.min(59, minute))
|
|
346
|
+
}));
|
|
347
|
+
}, []);
|
|
348
|
+
const toggleWeekDay = useCallback((day) => {
|
|
349
|
+
setState((s) => {
|
|
350
|
+
const hasDay = s.weekDays.includes(day);
|
|
351
|
+
if (hasDay && s.weekDays.length === 1) return s;
|
|
352
|
+
const newDays = hasDay ? s.weekDays.filter((d) => d !== day) : [...s.weekDays, day];
|
|
353
|
+
return {
|
|
354
|
+
...s,
|
|
355
|
+
weekDays: newDays.sort((a, b) => a - b)
|
|
356
|
+
};
|
|
357
|
+
});
|
|
358
|
+
}, []);
|
|
359
|
+
const setWeekDays = useCallback((days) => {
|
|
360
|
+
setState((s) => ({
|
|
361
|
+
...s,
|
|
362
|
+
weekDays: days.length > 0 ? [...days].sort((a, b) => a - b) : [1]
|
|
363
|
+
// Default to Monday if empty
|
|
364
|
+
}));
|
|
365
|
+
}, []);
|
|
366
|
+
const toggleMonthDay = useCallback((day) => {
|
|
367
|
+
setState((s) => {
|
|
368
|
+
const hasDay = s.monthDays.includes(day);
|
|
369
|
+
if (hasDay && s.monthDays.length === 1) return s;
|
|
370
|
+
const newDays = hasDay ? s.monthDays.filter((d) => d !== day) : [...s.monthDays, day];
|
|
371
|
+
return {
|
|
372
|
+
...s,
|
|
373
|
+
monthDays: newDays.sort((a, b) => a - b)
|
|
374
|
+
};
|
|
375
|
+
});
|
|
376
|
+
}, []);
|
|
377
|
+
const setMonthDays = useCallback((days) => {
|
|
378
|
+
setState((s) => ({
|
|
379
|
+
...s,
|
|
380
|
+
monthDays: days.length > 0 ? [...days].sort((a, b) => a - b) : [1]
|
|
381
|
+
// Default to 1st if empty
|
|
382
|
+
}));
|
|
383
|
+
}, []);
|
|
384
|
+
const setCustomCron = useCallback((customCron) => {
|
|
385
|
+
const parsed = parseCron(customCron);
|
|
386
|
+
setState((s) => ({
|
|
387
|
+
...s,
|
|
388
|
+
customCron,
|
|
389
|
+
isValid: parsed !== null
|
|
390
|
+
}));
|
|
391
|
+
}, []);
|
|
392
|
+
const reset = useCallback(() => {
|
|
393
|
+
setState({ ...DEFAULT_STATE, type: defaultType });
|
|
394
|
+
}, [defaultType]);
|
|
395
|
+
const contextValue = useMemo(
|
|
396
|
+
() => ({
|
|
397
|
+
// State
|
|
398
|
+
...state,
|
|
399
|
+
// Computed
|
|
400
|
+
cronExpression,
|
|
401
|
+
humanDescription,
|
|
402
|
+
// Actions
|
|
403
|
+
setType,
|
|
404
|
+
setTime,
|
|
405
|
+
toggleWeekDay,
|
|
406
|
+
setWeekDays,
|
|
407
|
+
toggleMonthDay,
|
|
408
|
+
setMonthDays,
|
|
409
|
+
setCustomCron,
|
|
410
|
+
reset
|
|
411
|
+
}),
|
|
412
|
+
[
|
|
413
|
+
state,
|
|
414
|
+
cronExpression,
|
|
415
|
+
humanDescription,
|
|
416
|
+
setType,
|
|
417
|
+
setTime,
|
|
418
|
+
toggleWeekDay,
|
|
419
|
+
setWeekDays,
|
|
420
|
+
toggleMonthDay,
|
|
421
|
+
setMonthDays,
|
|
422
|
+
setCustomCron,
|
|
423
|
+
reset
|
|
424
|
+
]
|
|
425
|
+
);
|
|
426
|
+
return /* @__PURE__ */ jsx(CronSchedulerContext.Provider, { value: contextValue, children });
|
|
427
|
+
}
|
|
428
|
+
__name(CronSchedulerProvider, "CronSchedulerProvider");
|
|
429
|
+
function useCronSchedulerContext() {
|
|
430
|
+
const context = useContext(CronSchedulerContext);
|
|
431
|
+
if (!context) {
|
|
432
|
+
throw new Error(
|
|
433
|
+
"useCronSchedulerContext must be used within CronSchedulerProvider"
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
return context;
|
|
437
|
+
}
|
|
438
|
+
__name(useCronSchedulerContext, "useCronSchedulerContext");
|
|
439
|
+
function useCronType() {
|
|
440
|
+
const { type, setType } = useCronSchedulerContext();
|
|
441
|
+
return useMemo(() => ({ type, setType }), [type, setType]);
|
|
442
|
+
}
|
|
443
|
+
__name(useCronType, "useCronType");
|
|
444
|
+
function useCronTime() {
|
|
445
|
+
const { hour, minute, setTime } = useCronSchedulerContext();
|
|
446
|
+
return useMemo(() => ({ hour, minute, setTime }), [hour, minute, setTime]);
|
|
447
|
+
}
|
|
448
|
+
__name(useCronTime, "useCronTime");
|
|
449
|
+
function useCronWeekDays() {
|
|
450
|
+
const { weekDays, toggleWeekDay, setWeekDays } = useCronSchedulerContext();
|
|
451
|
+
return useMemo(
|
|
452
|
+
() => ({ weekDays, toggleWeekDay, setWeekDays }),
|
|
453
|
+
[weekDays, toggleWeekDay, setWeekDays]
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
__name(useCronWeekDays, "useCronWeekDays");
|
|
457
|
+
function useCronMonthDays() {
|
|
458
|
+
const { monthDays, toggleMonthDay, setMonthDays } = useCronSchedulerContext();
|
|
459
|
+
return useMemo(
|
|
460
|
+
() => ({ monthDays, toggleMonthDay, setMonthDays }),
|
|
461
|
+
[monthDays, toggleMonthDay, setMonthDays]
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
__name(useCronMonthDays, "useCronMonthDays");
|
|
465
|
+
function useCronCustom() {
|
|
466
|
+
const { customCron, isValid, setCustomCron } = useCronSchedulerContext();
|
|
467
|
+
return useMemo(
|
|
468
|
+
() => ({ customCron, isValid, setCustomCron }),
|
|
469
|
+
[customCron, isValid, setCustomCron]
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
__name(useCronCustom, "useCronCustom");
|
|
473
|
+
function useCronPreview() {
|
|
474
|
+
const { cronExpression, humanDescription, isValid } = useCronSchedulerContext();
|
|
475
|
+
return useMemo(
|
|
476
|
+
() => ({ cronExpression, humanDescription, isValid }),
|
|
477
|
+
[cronExpression, humanDescription, isValid]
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
__name(useCronPreview, "useCronPreview");
|
|
481
|
+
function useCronScheduler() {
|
|
482
|
+
return useCronSchedulerContext();
|
|
483
|
+
}
|
|
484
|
+
__name(useCronScheduler, "useCronScheduler");
|
|
485
|
+
var SCHEDULE_TYPES = [
|
|
486
|
+
{ value: "daily", label: "Daily" },
|
|
487
|
+
{ value: "weekly", label: "Weekly" },
|
|
488
|
+
{ value: "monthly", label: "Monthly" },
|
|
489
|
+
{ value: "custom", label: "Custom" }
|
|
490
|
+
];
|
|
491
|
+
function ScheduleTypeSelector({
|
|
492
|
+
disabled,
|
|
493
|
+
className
|
|
494
|
+
}) {
|
|
495
|
+
const { type, setType } = useCronType();
|
|
496
|
+
return /* @__PURE__ */ jsx(
|
|
497
|
+
Tabs,
|
|
498
|
+
{
|
|
499
|
+
value: type,
|
|
500
|
+
onValueChange: (v) => setType(v),
|
|
501
|
+
className: cn("w-full", className),
|
|
502
|
+
children: /* @__PURE__ */ jsx(TabsList, { className: "grid w-full grid-cols-4 h-9 p-0.5", children: SCHEDULE_TYPES.map(({ value, label }) => /* @__PURE__ */ jsx(
|
|
503
|
+
TabsTrigger,
|
|
504
|
+
{
|
|
505
|
+
value,
|
|
506
|
+
disabled,
|
|
507
|
+
className: cn(
|
|
508
|
+
"text-xs font-medium px-2 py-1.5",
|
|
509
|
+
"data-[state=active]:shadow-sm",
|
|
510
|
+
"transition-all duration-150"
|
|
511
|
+
),
|
|
512
|
+
children: label
|
|
513
|
+
},
|
|
514
|
+
value
|
|
515
|
+
)) })
|
|
516
|
+
}
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
__name(ScheduleTypeSelector, "ScheduleTypeSelector");
|
|
520
|
+
var HOURS = Array.from({ length: 24 }, (_, i) => i);
|
|
521
|
+
var MINUTES = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55];
|
|
522
|
+
function TimeSelector({
|
|
523
|
+
format = "24h",
|
|
524
|
+
disabled,
|
|
525
|
+
className
|
|
526
|
+
}) {
|
|
527
|
+
const { hour, minute, setTime } = useCronTime();
|
|
528
|
+
const is24h = format === "24h";
|
|
529
|
+
const displayHour = is24h ? hour : hour % 12 || 12;
|
|
530
|
+
const isPM = hour >= 12;
|
|
531
|
+
const handleHourChange = /* @__PURE__ */ __name((value) => {
|
|
532
|
+
let newHour = parseInt(value, 10);
|
|
533
|
+
if (!is24h) {
|
|
534
|
+
if (isPM && newHour !== 12) newHour += 12;
|
|
535
|
+
else if (!isPM && newHour === 12) newHour = 0;
|
|
536
|
+
}
|
|
537
|
+
setTime(newHour, minute);
|
|
538
|
+
}, "handleHourChange");
|
|
539
|
+
const handleMinuteChange = /* @__PURE__ */ __name((value) => {
|
|
540
|
+
setTime(hour, parseInt(value, 10));
|
|
541
|
+
}, "handleMinuteChange");
|
|
542
|
+
const handlePeriodChange = /* @__PURE__ */ __name((value) => {
|
|
543
|
+
const newIsPM = value === "PM";
|
|
544
|
+
let newHour = hour;
|
|
545
|
+
if (newIsPM && hour < 12) newHour = hour + 12;
|
|
546
|
+
else if (!newIsPM && hour >= 12) newHour = hour - 12;
|
|
547
|
+
setTime(newHour, minute);
|
|
548
|
+
}, "handlePeriodChange");
|
|
549
|
+
const hours = is24h ? HOURS : Array.from({ length: 12 }, (_, i) => i + 1);
|
|
550
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-3", className), children: [
|
|
551
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [
|
|
552
|
+
/* @__PURE__ */ jsx(Clock, { className: "h-4 w-4" }),
|
|
553
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm", children: "Run at" })
|
|
554
|
+
] }),
|
|
555
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 flex-1", children: [
|
|
556
|
+
/* @__PURE__ */ jsxs(
|
|
557
|
+
Select,
|
|
558
|
+
{
|
|
559
|
+
value: displayHour.toString(),
|
|
560
|
+
onValueChange: handleHourChange,
|
|
561
|
+
disabled,
|
|
562
|
+
children: [
|
|
563
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "w-[70px] h-9", children: /* @__PURE__ */ jsx(SelectValue, { children: displayHour.toString().padStart(2, "0") }) }),
|
|
564
|
+
/* @__PURE__ */ jsx(SelectContent, { className: "max-h-48", children: hours.map((h) => /* @__PURE__ */ jsx(SelectItem, { value: h.toString(), children: h.toString().padStart(2, "0") }, h)) })
|
|
565
|
+
]
|
|
566
|
+
}
|
|
567
|
+
),
|
|
568
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-medium", children: ":" }),
|
|
569
|
+
/* @__PURE__ */ jsxs(
|
|
570
|
+
Select,
|
|
571
|
+
{
|
|
572
|
+
value: minute.toString(),
|
|
573
|
+
onValueChange: handleMinuteChange,
|
|
574
|
+
disabled,
|
|
575
|
+
children: [
|
|
576
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "w-[70px] h-9", children: /* @__PURE__ */ jsx(SelectValue, { children: minute.toString().padStart(2, "0") }) }),
|
|
577
|
+
/* @__PURE__ */ jsx(SelectContent, { className: "max-h-48", children: MINUTES.map((m) => /* @__PURE__ */ jsx(SelectItem, { value: m.toString(), children: m.toString().padStart(2, "0") }, m)) })
|
|
578
|
+
]
|
|
579
|
+
}
|
|
580
|
+
),
|
|
581
|
+
!is24h && /* @__PURE__ */ jsxs(
|
|
582
|
+
Select,
|
|
583
|
+
{
|
|
584
|
+
value: isPM ? "PM" : "AM",
|
|
585
|
+
onValueChange: handlePeriodChange,
|
|
586
|
+
disabled,
|
|
587
|
+
children: [
|
|
588
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "w-[70px] h-9", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
589
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
590
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "AM", children: "AM" }),
|
|
591
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "PM", children: "PM" })
|
|
592
|
+
] })
|
|
593
|
+
]
|
|
594
|
+
}
|
|
595
|
+
)
|
|
596
|
+
] })
|
|
597
|
+
] });
|
|
598
|
+
}
|
|
599
|
+
__name(TimeSelector, "TimeSelector");
|
|
600
|
+
var DAYS = [
|
|
601
|
+
{ value: 1, label: "Mon" },
|
|
602
|
+
{ value: 2, label: "Tue" },
|
|
603
|
+
{ value: 3, label: "Wed" },
|
|
604
|
+
{ value: 4, label: "Thu" },
|
|
605
|
+
{ value: 5, label: "Fri" },
|
|
606
|
+
{ value: 6, label: "Sat" },
|
|
607
|
+
{ value: 0, label: "Sun" }
|
|
608
|
+
];
|
|
609
|
+
function DayChips({
|
|
610
|
+
disabled,
|
|
611
|
+
showPresets = true,
|
|
612
|
+
className
|
|
613
|
+
}) {
|
|
614
|
+
const { weekDays, toggleWeekDay, setWeekDays } = useCronWeekDays();
|
|
615
|
+
const isWeekdays2 = weekDays.length === 5 && [1, 2, 3, 4, 5].every((d) => weekDays.includes(d));
|
|
616
|
+
const isWeekend2 = weekDays.length === 2 && [0, 6].every((d) => weekDays.includes(d));
|
|
617
|
+
const isEveryday = weekDays.length === 7;
|
|
618
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
|
|
619
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-1", children: DAYS.map(({ value, label }) => {
|
|
620
|
+
const isSelected = weekDays.includes(value);
|
|
621
|
+
const isWeekend3 = value === 0 || value === 6;
|
|
622
|
+
return /* @__PURE__ */ jsx(
|
|
623
|
+
"button",
|
|
624
|
+
{
|
|
625
|
+
type: "button",
|
|
626
|
+
disabled,
|
|
627
|
+
onClick: () => toggleWeekDay(value),
|
|
628
|
+
"aria-pressed": isSelected,
|
|
629
|
+
className: cn(
|
|
630
|
+
"flex flex-col items-center justify-center",
|
|
631
|
+
"py-2.5 rounded-lg text-xs font-medium",
|
|
632
|
+
"transition-all duration-150",
|
|
633
|
+
"focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50",
|
|
634
|
+
"active:scale-[0.97]",
|
|
635
|
+
isSelected ? "bg-primary text-primary-foreground shadow-sm" : cn(
|
|
636
|
+
"bg-muted/50 hover:bg-muted",
|
|
637
|
+
isWeekend3 ? "text-muted-foreground/70" : "text-muted-foreground"
|
|
638
|
+
),
|
|
639
|
+
disabled && "opacity-50 cursor-not-allowed pointer-events-none"
|
|
640
|
+
),
|
|
641
|
+
children: /* @__PURE__ */ jsx("span", { children: label })
|
|
642
|
+
},
|
|
643
|
+
value
|
|
644
|
+
);
|
|
645
|
+
}) }),
|
|
646
|
+
showPresets && /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
647
|
+
/* @__PURE__ */ jsx(
|
|
648
|
+
PresetButton,
|
|
649
|
+
{
|
|
650
|
+
label: "Weekdays",
|
|
651
|
+
isActive: isWeekdays2,
|
|
652
|
+
onClick: () => setWeekDays([1, 2, 3, 4, 5]),
|
|
653
|
+
disabled
|
|
654
|
+
}
|
|
655
|
+
),
|
|
656
|
+
/* @__PURE__ */ jsx(
|
|
657
|
+
PresetButton,
|
|
658
|
+
{
|
|
659
|
+
label: "Weekends",
|
|
660
|
+
isActive: isWeekend2,
|
|
661
|
+
onClick: () => setWeekDays([0, 6]),
|
|
662
|
+
disabled
|
|
663
|
+
}
|
|
664
|
+
),
|
|
665
|
+
/* @__PURE__ */ jsx(
|
|
666
|
+
PresetButton,
|
|
667
|
+
{
|
|
668
|
+
label: "Every day",
|
|
669
|
+
isActive: isEveryday,
|
|
670
|
+
onClick: () => setWeekDays([0, 1, 2, 3, 4, 5, 6]),
|
|
671
|
+
disabled
|
|
672
|
+
}
|
|
673
|
+
)
|
|
674
|
+
] })
|
|
675
|
+
] });
|
|
676
|
+
}
|
|
677
|
+
__name(DayChips, "DayChips");
|
|
678
|
+
function PresetButton({ label, isActive, onClick, disabled }) {
|
|
679
|
+
return /* @__PURE__ */ jsx(
|
|
680
|
+
"button",
|
|
681
|
+
{
|
|
682
|
+
type: "button",
|
|
683
|
+
disabled,
|
|
684
|
+
onClick,
|
|
685
|
+
className: cn(
|
|
686
|
+
"flex-1 px-3 py-1.5 rounded-md text-xs font-medium",
|
|
687
|
+
"transition-colors duration-150",
|
|
688
|
+
"focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50",
|
|
689
|
+
isActive ? "bg-primary/15 text-primary border border-primary/30" : "bg-muted/30 text-muted-foreground hover:bg-muted/50 border border-transparent",
|
|
690
|
+
disabled && "opacity-50 cursor-not-allowed"
|
|
691
|
+
),
|
|
692
|
+
children: label
|
|
693
|
+
}
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
__name(PresetButton, "PresetButton");
|
|
697
|
+
Array.from({ length: 31 }, (_, i) => i + 1);
|
|
698
|
+
var GRID_SIZE = 35;
|
|
699
|
+
function MonthDayGrid({
|
|
700
|
+
disabled,
|
|
701
|
+
showPresets = true,
|
|
702
|
+
className
|
|
703
|
+
}) {
|
|
704
|
+
const { monthDays, toggleMonthDay, setMonthDays } = useCronMonthDays();
|
|
705
|
+
const is1st = monthDays.length === 1 && monthDays[0] === 1;
|
|
706
|
+
const is15th = monthDays.length === 1 && monthDays[0] === 15;
|
|
707
|
+
const is1stAnd15th = monthDays.length === 2 && monthDays.includes(1) && monthDays.includes(15);
|
|
708
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
|
|
709
|
+
showPresets && /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
710
|
+
/* @__PURE__ */ jsx(
|
|
711
|
+
PresetButton2,
|
|
712
|
+
{
|
|
713
|
+
label: "1st",
|
|
714
|
+
isActive: is1st,
|
|
715
|
+
onClick: () => setMonthDays([1]),
|
|
716
|
+
disabled
|
|
717
|
+
}
|
|
718
|
+
),
|
|
719
|
+
/* @__PURE__ */ jsx(
|
|
720
|
+
PresetButton2,
|
|
721
|
+
{
|
|
722
|
+
label: "15th",
|
|
723
|
+
isActive: is15th,
|
|
724
|
+
onClick: () => setMonthDays([15]),
|
|
725
|
+
disabled
|
|
726
|
+
}
|
|
727
|
+
),
|
|
728
|
+
/* @__PURE__ */ jsx(
|
|
729
|
+
PresetButton2,
|
|
730
|
+
{
|
|
731
|
+
label: "1st & 15th",
|
|
732
|
+
isActive: is1stAnd15th,
|
|
733
|
+
onClick: () => setMonthDays([1, 15]),
|
|
734
|
+
disabled
|
|
735
|
+
}
|
|
736
|
+
)
|
|
737
|
+
] }),
|
|
738
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-1", children: Array.from({ length: GRID_SIZE }, (_, i) => {
|
|
739
|
+
const day = i + 1;
|
|
740
|
+
const isValidDay = day <= 31;
|
|
741
|
+
const isSelected = isValidDay && monthDays.includes(day);
|
|
742
|
+
const isPartialMonth = day > 28;
|
|
743
|
+
if (!isValidDay) {
|
|
744
|
+
return /* @__PURE__ */ jsx("div", { className: "aspect-square" }, i);
|
|
745
|
+
}
|
|
746
|
+
return /* @__PURE__ */ jsx(
|
|
747
|
+
"button",
|
|
748
|
+
{
|
|
749
|
+
type: "button",
|
|
750
|
+
disabled,
|
|
751
|
+
onClick: () => toggleMonthDay(day),
|
|
752
|
+
"aria-pressed": isSelected,
|
|
753
|
+
"aria-label": `Day ${day}`,
|
|
754
|
+
className: cn(
|
|
755
|
+
"aspect-square flex items-center justify-center",
|
|
756
|
+
"rounded-lg text-sm font-medium",
|
|
757
|
+
"transition-all duration-150",
|
|
758
|
+
"focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50",
|
|
759
|
+
"active:scale-[0.95]",
|
|
760
|
+
isSelected ? "bg-primary text-primary-foreground shadow-sm" : cn(
|
|
761
|
+
"bg-muted/30 hover:bg-muted/60",
|
|
762
|
+
isPartialMonth ? "text-muted-foreground/50" : "text-muted-foreground"
|
|
763
|
+
),
|
|
764
|
+
disabled && "opacity-50 cursor-not-allowed pointer-events-none"
|
|
765
|
+
),
|
|
766
|
+
children: day
|
|
767
|
+
},
|
|
768
|
+
day
|
|
769
|
+
);
|
|
770
|
+
}) }),
|
|
771
|
+
monthDays.length > 1 && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground text-center", children: [
|
|
772
|
+
monthDays.length,
|
|
773
|
+
" days selected"
|
|
774
|
+
] })
|
|
775
|
+
] });
|
|
776
|
+
}
|
|
777
|
+
__name(MonthDayGrid, "MonthDayGrid");
|
|
778
|
+
function PresetButton2({ label, isActive, onClick, disabled }) {
|
|
779
|
+
return /* @__PURE__ */ jsx(
|
|
780
|
+
"button",
|
|
781
|
+
{
|
|
782
|
+
type: "button",
|
|
783
|
+
disabled,
|
|
784
|
+
onClick,
|
|
785
|
+
className: cn(
|
|
786
|
+
"flex-1 px-3 py-1.5 rounded-md text-xs font-medium",
|
|
787
|
+
"transition-colors duration-150",
|
|
788
|
+
"focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50",
|
|
789
|
+
isActive ? "bg-primary/15 text-primary border border-primary/30" : "bg-muted/30 text-muted-foreground hover:bg-muted/50 border border-transparent",
|
|
790
|
+
disabled && "opacity-50 cursor-not-allowed"
|
|
791
|
+
),
|
|
792
|
+
children: label
|
|
793
|
+
}
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
__name(PresetButton2, "PresetButton");
|
|
797
|
+
function CustomInput({ disabled, className }) {
|
|
798
|
+
const { customCron, isValid, setCustomCron } = useCronCustom();
|
|
799
|
+
const [localValue, setLocalValue] = useState(customCron);
|
|
800
|
+
const [localValid, setLocalValid] = useState(isValid);
|
|
801
|
+
useEffect(() => {
|
|
802
|
+
setLocalValue(customCron);
|
|
803
|
+
setLocalValid(isValid);
|
|
804
|
+
}, [customCron, isValid]);
|
|
805
|
+
const handleChange = /* @__PURE__ */ __name((e) => {
|
|
806
|
+
const value = e.target.value;
|
|
807
|
+
setLocalValue(value);
|
|
808
|
+
const valid = isValidCron(value);
|
|
809
|
+
setLocalValid(valid);
|
|
810
|
+
setCustomCron(value);
|
|
811
|
+
}, "handleChange");
|
|
812
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", className), children: [
|
|
813
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Cron expression" }),
|
|
814
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
815
|
+
/* @__PURE__ */ jsx(
|
|
816
|
+
Input,
|
|
817
|
+
{
|
|
818
|
+
type: "text",
|
|
819
|
+
value: localValue,
|
|
820
|
+
onChange: handleChange,
|
|
821
|
+
disabled,
|
|
822
|
+
placeholder: "* * * * *",
|
|
823
|
+
className: cn(
|
|
824
|
+
"font-mono text-base pr-10 h-11",
|
|
825
|
+
!localValid && localValue.trim() && "border-destructive focus-visible:ring-destructive/50"
|
|
826
|
+
)
|
|
827
|
+
}
|
|
828
|
+
),
|
|
829
|
+
/* @__PURE__ */ jsx("div", { className: "absolute right-3 top-1/2 -translate-y-1/2", children: localValue.trim() && (localValid ? /* @__PURE__ */ jsx(CheckCircle2, { className: "h-5 w-5 text-green-500" }) : /* @__PURE__ */ jsx(AlertCircle, { className: "h-5 w-5 text-destructive" })) })
|
|
830
|
+
] })
|
|
831
|
+
] });
|
|
832
|
+
}
|
|
833
|
+
__name(CustomInput, "CustomInput");
|
|
834
|
+
function CronCheatsheet({ className }) {
|
|
835
|
+
return /* @__PURE__ */ jsxs(Popover, { children: [
|
|
836
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
837
|
+
"button",
|
|
838
|
+
{
|
|
839
|
+
type: "button",
|
|
840
|
+
className: cn(
|
|
841
|
+
"p-1 rounded hover:bg-muted/50 transition-colors",
|
|
842
|
+
className
|
|
843
|
+
),
|
|
844
|
+
"aria-label": "Cron syntax help",
|
|
845
|
+
children: /* @__PURE__ */ jsx(HelpCircle, { className: "h-4 w-4 text-muted-foreground" })
|
|
846
|
+
}
|
|
847
|
+
) }),
|
|
848
|
+
/* @__PURE__ */ jsx(PopoverContent, { className: "w-72 p-3", align: "end", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
849
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium text-sm", children: "Cron Format" }),
|
|
850
|
+
/* @__PURE__ */ jsx("code", { className: "block text-xs bg-muted px-2 py-1.5 rounded font-mono text-center", children: "min hour day month weekday" }),
|
|
851
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-x-4 gap-y-1 text-xs", children: [
|
|
852
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
853
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "minute" }),
|
|
854
|
+
/* @__PURE__ */ jsx("span", { children: "0-59" })
|
|
855
|
+
] }),
|
|
856
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
857
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "hour" }),
|
|
858
|
+
/* @__PURE__ */ jsx("span", { children: "0-23" })
|
|
859
|
+
] }),
|
|
860
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
861
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "day" }),
|
|
862
|
+
/* @__PURE__ */ jsx("span", { children: "1-31" })
|
|
863
|
+
] }),
|
|
864
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
865
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "month" }),
|
|
866
|
+
/* @__PURE__ */ jsx("span", { children: "1-12" })
|
|
867
|
+
] }),
|
|
868
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between col-span-2", children: [
|
|
869
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "weekday" }),
|
|
870
|
+
/* @__PURE__ */ jsx("span", { children: "0-6 (Sun-Sat)" })
|
|
871
|
+
] })
|
|
872
|
+
] }),
|
|
873
|
+
/* @__PURE__ */ jsxs("div", { className: "pt-2 border-t border-border space-y-1.5", children: [
|
|
874
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: "Special characters" }),
|
|
875
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-1.5 text-xs", children: [
|
|
876
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
877
|
+
/* @__PURE__ */ jsx("code", { className: "bg-muted px-1 rounded", children: "*" }),
|
|
878
|
+
" any value"
|
|
879
|
+
] }),
|
|
880
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
881
|
+
/* @__PURE__ */ jsx("code", { className: "bg-muted px-1 rounded", children: "," }),
|
|
882
|
+
" list (1,3,5)"
|
|
883
|
+
] }),
|
|
884
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
885
|
+
/* @__PURE__ */ jsx("code", { className: "bg-muted px-1 rounded", children: "-" }),
|
|
886
|
+
" range (1-5)"
|
|
887
|
+
] }),
|
|
888
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
889
|
+
/* @__PURE__ */ jsx("code", { className: "bg-muted px-1 rounded", children: "/" }),
|
|
890
|
+
" step (*/15)"
|
|
891
|
+
] })
|
|
892
|
+
] })
|
|
893
|
+
] }),
|
|
894
|
+
/* @__PURE__ */ jsxs("div", { className: "pt-2 border-t border-border space-y-1.5", children: [
|
|
895
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: "Examples" }),
|
|
896
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1 text-xs", children: [
|
|
897
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between font-mono", children: [
|
|
898
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "0 9 * * *" }),
|
|
899
|
+
/* @__PURE__ */ jsx("span", { children: "daily at 9am" })
|
|
900
|
+
] }),
|
|
901
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between font-mono", children: [
|
|
902
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "0 9 * * 1-5" }),
|
|
903
|
+
/* @__PURE__ */ jsx("span", { children: "weekdays 9am" })
|
|
904
|
+
] }),
|
|
905
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between font-mono", children: [
|
|
906
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "*/15 * * * *" }),
|
|
907
|
+
/* @__PURE__ */ jsx("span", { children: "every 15 min" })
|
|
908
|
+
] }),
|
|
909
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between font-mono", children: [
|
|
910
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "0 0 1 * *" }),
|
|
911
|
+
/* @__PURE__ */ jsx("span", { children: "1st of month" })
|
|
912
|
+
] })
|
|
913
|
+
] })
|
|
914
|
+
] })
|
|
915
|
+
] }) })
|
|
916
|
+
] });
|
|
917
|
+
}
|
|
918
|
+
__name(CronCheatsheet, "CronCheatsheet");
|
|
919
|
+
function SchedulePreview({
|
|
920
|
+
showCronExpression = true,
|
|
921
|
+
allowCopy = false,
|
|
922
|
+
className
|
|
923
|
+
}) {
|
|
924
|
+
const { cronExpression, humanDescription, isValid } = useCronPreview();
|
|
925
|
+
const [copied, setCopied] = useState(false);
|
|
926
|
+
const handleCopy = useCallback(async () => {
|
|
927
|
+
try {
|
|
928
|
+
await navigator.clipboard.writeText(cronExpression);
|
|
929
|
+
setCopied(true);
|
|
930
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
931
|
+
} catch (err) {
|
|
932
|
+
console.error("Failed to copy:", err);
|
|
933
|
+
}
|
|
934
|
+
}, [cronExpression]);
|
|
935
|
+
return /* @__PURE__ */ jsxs(
|
|
936
|
+
"div",
|
|
937
|
+
{
|
|
938
|
+
className: cn(
|
|
939
|
+
"px-3 py-2.5 rounded-lg border",
|
|
940
|
+
"bg-muted/30 border-border/50",
|
|
941
|
+
!isValid && "border-destructive/30 bg-destructive/5",
|
|
942
|
+
className
|
|
943
|
+
),
|
|
944
|
+
children: [
|
|
945
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
946
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
947
|
+
/* @__PURE__ */ jsx(
|
|
948
|
+
Calendar,
|
|
949
|
+
{
|
|
950
|
+
className: cn(
|
|
951
|
+
"h-4 w-4 shrink-0",
|
|
952
|
+
isValid ? "text-primary" : "text-destructive"
|
|
953
|
+
)
|
|
954
|
+
}
|
|
955
|
+
),
|
|
956
|
+
/* @__PURE__ */ jsx(
|
|
957
|
+
"span",
|
|
958
|
+
{
|
|
959
|
+
className: cn(
|
|
960
|
+
"text-sm",
|
|
961
|
+
isValid ? "text-foreground" : "text-destructive"
|
|
962
|
+
),
|
|
963
|
+
children: humanDescription
|
|
964
|
+
}
|
|
965
|
+
)
|
|
966
|
+
] }),
|
|
967
|
+
allowCopy && /* @__PURE__ */ jsx(
|
|
968
|
+
"button",
|
|
969
|
+
{
|
|
970
|
+
type: "button",
|
|
971
|
+
onClick: handleCopy,
|
|
972
|
+
disabled: !isValid,
|
|
973
|
+
className: cn(
|
|
974
|
+
"p-1 rounded transition-colors duration-150",
|
|
975
|
+
"hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50",
|
|
976
|
+
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
977
|
+
),
|
|
978
|
+
title: copied ? "Copied!" : "Copy cron expression",
|
|
979
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-green-500" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
980
|
+
}
|
|
981
|
+
)
|
|
982
|
+
] }),
|
|
983
|
+
showCronExpression && /* @__PURE__ */ jsxs("div", { className: "mt-1.5 pt-1.5 border-t border-border/30 flex items-center justify-between", children: [
|
|
984
|
+
/* @__PURE__ */ jsx("code", { className: "text-xs font-mono text-muted-foreground", children: cronExpression }),
|
|
985
|
+
/* @__PURE__ */ jsx(CronCheatsheet, {})
|
|
986
|
+
] })
|
|
987
|
+
]
|
|
988
|
+
}
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
__name(SchedulePreview, "SchedulePreview");
|
|
992
|
+
|
|
993
|
+
export { CronSchedulerProvider, CustomInput, DayChips, MonthDayGrid, SchedulePreview, ScheduleTypeSelector, TimeSelector, buildCron, humanizeCron, isValidCron, parseCron, useCronCustom, useCronMonthDays, useCronPreview, useCronScheduler, useCronSchedulerContext, useCronTime, useCronType, useCronWeekDays };
|
|
994
|
+
//# sourceMappingURL=chunk-6G72N466.mjs.map
|
|
995
|
+
//# sourceMappingURL=chunk-6G72N466.mjs.map
|