mirror of
https://github.com/freedbygrace/ActiveDirectoryManager.git
synced 2026-08-21 15:47:41 +00:00
Enhance dynamic group scheduling with advanced options. Added features include custom scheduling, priority setting, and a user-friendly interface for managing cron jobs.
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 705f2157-ef97-4fbd-89e4-8c7f2ecaea90 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7ed01c5f-a82d-405a-b728-b2e3d127c60c/82335acf-8186-4635-b5cf-9e30a3448eec.jpg
This commit is contained in:
@@ -7,7 +7,11 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
|||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Trash, Plus, Calendar, Clock, RotateCcw } from 'lucide-react';
|
import { Trash, Plus, Calendar, Clock, RotateCcw, HelpCircle, Copy, Edit, AlertCircle } from 'lucide-react';
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
|
import { Slider } from '@/components/ui/slider';
|
||||||
|
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion';
|
||||||
import {
|
import {
|
||||||
ScheduleFrequency,
|
ScheduleFrequency,
|
||||||
Weekday,
|
Weekday,
|
||||||
@@ -15,6 +19,7 @@ import {
|
|||||||
weekdays,
|
weekdays,
|
||||||
InsertScheduleRule
|
InsertScheduleRule
|
||||||
} from '@shared/schema';
|
} from '@shared/schema';
|
||||||
|
import { useToast } from '@/hooks/use-toast';
|
||||||
|
|
||||||
interface CronJobBuilderProps {
|
interface CronJobBuilderProps {
|
||||||
schedules: ScheduleItem[];
|
schedules: ScheduleItem[];
|
||||||
@@ -34,6 +39,14 @@ export interface ScheduleItem {
|
|||||||
hour: number;
|
hour: number;
|
||||||
minute: number;
|
minute: number;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
priority?: number;
|
||||||
|
customSchedule?: boolean;
|
||||||
|
months?: number[];
|
||||||
|
daysOfWeek?: Weekday[];
|
||||||
|
daysOfMonth?: number[];
|
||||||
|
hours?: number[];
|
||||||
|
minutes?: number[];
|
||||||
|
mode?: 'basic' | 'advanced';
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialSchedule: ScheduleItem = {
|
const initialSchedule: ScheduleItem = {
|
||||||
@@ -43,7 +56,15 @@ const initialSchedule: ScheduleItem = {
|
|||||||
cronExpression: '0 0 * * *',
|
cronExpression: '0 0 * * *',
|
||||||
hour: 0,
|
hour: 0,
|
||||||
minute: 0,
|
minute: 0,
|
||||||
enabled: true
|
enabled: true,
|
||||||
|
priority: 0,
|
||||||
|
customSchedule: false,
|
||||||
|
months: [],
|
||||||
|
daysOfWeek: [],
|
||||||
|
daysOfMonth: [],
|
||||||
|
hours: [],
|
||||||
|
minutes: [],
|
||||||
|
mode: 'basic'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CronJobBuilder: React.FC<CronJobBuilderProps> = ({
|
export const CronJobBuilder: React.FC<CronJobBuilderProps> = ({
|
||||||
@@ -51,6 +72,7 @@ export const CronJobBuilder: React.FC<CronJobBuilderProps> = ({
|
|||||||
onSchedulesChange,
|
onSchedulesChange,
|
||||||
ruleId
|
ruleId
|
||||||
}) => {
|
}) => {
|
||||||
|
const { toast } = useToast();
|
||||||
const [activeSchedules, setActiveSchedules] = useState<ScheduleItem[]>(
|
const [activeSchedules, setActiveSchedules] = useState<ScheduleItem[]>(
|
||||||
schedules.length > 0 ? schedules : [initialSchedule]
|
schedules.length > 0 ? schedules : [initialSchedule]
|
||||||
);
|
);
|
||||||
@@ -131,9 +153,112 @@ export const CronJobBuilder: React.FC<CronJobBuilderProps> = ({
|
|||||||
setActiveSchedules(newSchedules);
|
setActiveSchedules(newSchedules);
|
||||||
onSchedulesChange(newSchedules);
|
onSchedulesChange(newSchedules);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Toggle between basic and advanced mode
|
||||||
|
const toggleMode = (index: number) => {
|
||||||
|
const schedule = activeSchedules[index];
|
||||||
|
const newMode = schedule.mode === 'basic' ? 'advanced' : 'basic';
|
||||||
|
|
||||||
|
// If switching to advanced mode, initialize the advanced properties
|
||||||
|
if (newMode === 'advanced') {
|
||||||
|
updateSchedule(index, {
|
||||||
|
mode: newMode,
|
||||||
|
customSchedule: true,
|
||||||
|
months: [],
|
||||||
|
daysOfWeek: schedule.dayOfWeek ? [schedule.dayOfWeek] : [],
|
||||||
|
daysOfMonth: schedule.dayOfMonth ? [schedule.dayOfMonth] : [],
|
||||||
|
hours: [schedule.hour],
|
||||||
|
minutes: [schedule.minute]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Switching back to basic mode
|
||||||
|
updateSchedule(index, {
|
||||||
|
mode: newMode,
|
||||||
|
customSchedule: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copy cron expression to clipboard
|
||||||
|
const copyCronExpression = (expression: string) => {
|
||||||
|
navigator.clipboard.writeText(expression)
|
||||||
|
.then(() => {
|
||||||
|
toast({
|
||||||
|
title: "Copied to clipboard",
|
||||||
|
description: "Cron expression copied successfully",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast({
|
||||||
|
title: "Failed to copy",
|
||||||
|
description: "Could not copy to clipboard: " + err,
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle updating priority of schedule
|
||||||
|
const updatePriority = (index: number, priority: number) => {
|
||||||
|
updateSchedule(index, { priority });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate cron expression from advanced settings
|
||||||
|
const generateAdvancedCronExpression = (schedule: ScheduleItem): string => {
|
||||||
|
const minutePart = schedule.minutes && schedule.minutes.length > 0
|
||||||
|
? schedule.minutes.join(',')
|
||||||
|
: '*';
|
||||||
|
|
||||||
|
const hourPart = schedule.hours && schedule.hours.length > 0
|
||||||
|
? schedule.hours.join(',')
|
||||||
|
: '*';
|
||||||
|
|
||||||
|
const dayOfMonthPart = schedule.daysOfMonth && schedule.daysOfMonth.length > 0
|
||||||
|
? schedule.daysOfMonth.join(',')
|
||||||
|
: '*';
|
||||||
|
|
||||||
|
const monthPart = schedule.months && schedule.months.length > 0
|
||||||
|
? schedule.months.join(',')
|
||||||
|
: '*';
|
||||||
|
|
||||||
|
const dayOfWeekPart = schedule.daysOfWeek && schedule.daysOfWeek.length > 0
|
||||||
|
? schedule.daysOfWeek.map(day => weekdays.indexOf(day)).join(',')
|
||||||
|
: '*';
|
||||||
|
|
||||||
|
return `${minutePart} ${hourPart} ${dayOfMonthPart} ${monthPart} ${dayOfWeekPart}`;
|
||||||
|
};
|
||||||
|
|
||||||
// Function to generate a human-readable description of the schedule
|
// Function to generate a human-readable description of the schedule
|
||||||
const getScheduleDescription = (schedule: ScheduleItem): string => {
|
const getScheduleDescription = (schedule: ScheduleItem): string => {
|
||||||
|
if (schedule.mode === 'advanced' && schedule.customSchedule) {
|
||||||
|
const parts = [];
|
||||||
|
|
||||||
|
if (schedule.minutes && schedule.minutes.length > 0) {
|
||||||
|
parts.push(`at minute(s): ${schedule.minutes.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schedule.hours && schedule.hours.length > 0) {
|
||||||
|
parts.push(`hour(s): ${schedule.hours.map(h => formatTime(h, 0).replace(':00', '')).join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schedule.daysOfWeek && schedule.daysOfWeek.length > 0) {
|
||||||
|
parts.push(`on ${schedule.daysOfWeek.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schedule.daysOfMonth && schedule.daysOfMonth.length > 0) {
|
||||||
|
parts.push(`on day(s) ${schedule.daysOfMonth.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schedule.months && schedule.months.length > 0) {
|
||||||
|
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
|
||||||
|
'July', 'August', 'September', 'October', 'November', 'December'];
|
||||||
|
parts.push(`in ${schedule.months.map(m => monthNames[m-1]).join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.length > 0
|
||||||
|
? `Runs ${parts.join(', ')}`
|
||||||
|
: 'Custom advanced schedule';
|
||||||
|
}
|
||||||
|
|
||||||
switch (schedule.frequency) {
|
switch (schedule.frequency) {
|
||||||
case 'minutely':
|
case 'minutely':
|
||||||
return 'Runs every minute';
|
return 'Runs every minute';
|
||||||
@@ -145,6 +270,8 @@ export const CronJobBuilder: React.FC<CronJobBuilderProps> = ({
|
|||||||
return `Runs weekly on ${schedule.dayOfWeek || 'Monday'} at ${formatTime(schedule.hour, schedule.minute)}`;
|
return `Runs weekly on ${schedule.dayOfWeek || 'Monday'} at ${formatTime(schedule.hour, schedule.minute)}`;
|
||||||
case 'monthly':
|
case 'monthly':
|
||||||
return `Runs monthly on day ${schedule.dayOfMonth || 1} at ${formatTime(schedule.hour, schedule.minute)}`;
|
return `Runs monthly on day ${schedule.dayOfMonth || 1} at ${formatTime(schedule.hour, schedule.minute)}`;
|
||||||
|
case 'custom':
|
||||||
|
return 'Custom schedule';
|
||||||
default:
|
default:
|
||||||
return 'Custom schedule';
|
return 'Custom schedule';
|
||||||
}
|
}
|
||||||
@@ -326,9 +453,19 @@ export const CronJobBuilder: React.FC<CronJobBuilderProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="block mb-2">Cron Expression</Label>
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<Label>Cron Expression</Label>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => copyCronExpression(schedule.cronExpression)}
|
||||||
|
>
|
||||||
|
<Copy className="h-4 w-4 mr-1" />
|
||||||
|
Copy
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<div className="flex items-center p-2 rounded-md bg-muted/50">
|
<div className="flex items-center p-2 rounded-md bg-muted/50">
|
||||||
<code className="text-sm font-mono">{schedule.cronExpression}</code>
|
<code className="text-sm font-mono flex-1">{schedule.cronExpression}</code>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -338,28 +475,311 @@ export const CronJobBuilder: React.FC<CronJobBuilderProps> = ({
|
|||||||
<p className="text-sm">{getScheduleDescription(schedule)}</p>
|
<p className="text-sm">{getScheduleDescription(schedule)}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between pt-4">
|
<div className="pt-3 flex flex-col gap-4">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center justify-between">
|
||||||
<Switch
|
<div className="flex items-center space-x-2">
|
||||||
id={`schedule-enabled-${index}`}
|
<Switch
|
||||||
checked={schedule.enabled}
|
id={`schedule-enabled-${index}`}
|
||||||
onCheckedChange={() => toggleScheduleStatus(index)}
|
checked={schedule.enabled}
|
||||||
/>
|
onCheckedChange={() => toggleScheduleStatus(index)}
|
||||||
<Label htmlFor={`schedule-enabled-${index}`}>
|
/>
|
||||||
{schedule.enabled ? 'Enabled' : 'Disabled'}
|
<Label htmlFor={`schedule-enabled-${index}`}>
|
||||||
</Label>
|
{schedule.enabled ? 'Enabled' : 'Disabled'}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => toggleMode(index)}
|
||||||
|
>
|
||||||
|
{schedule.mode === 'basic' ? (
|
||||||
|
<>
|
||||||
|
<Edit className="h-4 w-4 mr-1" />
|
||||||
|
Advanced
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<RotateCcw className="h-4 w-4 mr-1" />
|
||||||
|
Basic
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
{schedule.mode === 'basic'
|
||||||
|
? 'Switch to advanced mode for more options'
|
||||||
|
: 'Switch back to basic mode'}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
|
||||||
|
{activeSchedules.length > 1 && (
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => removeSchedule(index)}
|
||||||
|
>
|
||||||
|
<Trash className="h-4 w-4 mr-1" />
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{activeSchedules.length > 1 && (
|
{schedule.mode === 'advanced' && (
|
||||||
<Button
|
<div className="pt-3">
|
||||||
variant="destructive"
|
<Accordion type="multiple">
|
||||||
size="sm"
|
<AccordionItem value="priority-settings">
|
||||||
onClick={() => removeSchedule(index)}
|
<AccordionTrigger>
|
||||||
>
|
<div className="flex items-center">
|
||||||
<Trash className="h-4 w-4 mr-1" />
|
<AlertCircle className="h-4 w-4 mr-2" />
|
||||||
Remove
|
Priority Settings
|
||||||
</Button>
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="space-y-4 pt-2">
|
||||||
|
<div>
|
||||||
|
<Label>
|
||||||
|
Schedule Priority: {schedule.priority ?? 0}
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<HelpCircle className="h-4 w-4 ml-1 inline-block" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p className="max-w-xs">
|
||||||
|
Higher priority schedules will execute first if multiple schedules
|
||||||
|
are triggered at the same time.
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</Label>
|
||||||
|
<Slider
|
||||||
|
className="mt-2"
|
||||||
|
value={[schedule.priority ?? 0]}
|
||||||
|
min={0}
|
||||||
|
max={10}
|
||||||
|
step={1}
|
||||||
|
onValueChange={(value) => updatePriority(index, value[0])}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem value="advanced-minutes">
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Clock className="h-4 w-4 mr-2" />
|
||||||
|
Minutes
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="space-y-4 pt-2">
|
||||||
|
<div className="grid grid-cols-6 gap-2">
|
||||||
|
{Array.from({ length: 60 }, (_, i) => i).map((minute) => (
|
||||||
|
<Button
|
||||||
|
key={minute}
|
||||||
|
variant={schedule.minutes?.includes(minute) ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
className="h-9 w-full text-center"
|
||||||
|
onClick={() => {
|
||||||
|
const currentMinutes = schedule.minutes || [];
|
||||||
|
const newMinutes = currentMinutes.includes(minute)
|
||||||
|
? currentMinutes.filter(m => m !== minute)
|
||||||
|
: [...currentMinutes, minute].sort((a, b) => a - b);
|
||||||
|
|
||||||
|
updateSchedule(index, {
|
||||||
|
minutes: newMinutes,
|
||||||
|
customSchedule: true,
|
||||||
|
cronExpression: generateAdvancedCronExpression({
|
||||||
|
...schedule,
|
||||||
|
minutes: newMinutes
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{minute}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem value="advanced-hours">
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Clock className="h-4 w-4 mr-2" />
|
||||||
|
Hours
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="space-y-4 pt-2">
|
||||||
|
<div className="grid grid-cols-6 gap-2">
|
||||||
|
{Array.from({ length: 24 }, (_, i) => i).map((hour) => (
|
||||||
|
<Button
|
||||||
|
key={hour}
|
||||||
|
variant={schedule.hours?.includes(hour) ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
className="h-9 w-full text-center"
|
||||||
|
onClick={() => {
|
||||||
|
const currentHours = schedule.hours || [];
|
||||||
|
const newHours = currentHours.includes(hour)
|
||||||
|
? currentHours.filter(h => h !== hour)
|
||||||
|
: [...currentHours, hour].sort((a, b) => a - b);
|
||||||
|
|
||||||
|
updateSchedule(index, {
|
||||||
|
hours: newHours,
|
||||||
|
customSchedule: true,
|
||||||
|
cronExpression: generateAdvancedCronExpression({
|
||||||
|
...schedule,
|
||||||
|
hours: newHours
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{hour}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem value="advanced-days">
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Calendar className="h-4 w-4 mr-2" />
|
||||||
|
Days of Week
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="space-y-4 pt-2">
|
||||||
|
<div className="grid grid-cols-3 sm:grid-cols-4 gap-2">
|
||||||
|
{weekdays.map((day) => (
|
||||||
|
<Button
|
||||||
|
key={day}
|
||||||
|
variant={schedule.daysOfWeek?.includes(day) ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
className="h-9 text-center"
|
||||||
|
onClick={() => {
|
||||||
|
const currentDays = schedule.daysOfWeek || [];
|
||||||
|
const newDays = currentDays.includes(day)
|
||||||
|
? currentDays.filter(d => d !== day)
|
||||||
|
: [...currentDays, day];
|
||||||
|
|
||||||
|
updateSchedule(index, {
|
||||||
|
daysOfWeek: newDays,
|
||||||
|
customSchedule: true,
|
||||||
|
cronExpression: generateAdvancedCronExpression({
|
||||||
|
...schedule,
|
||||||
|
daysOfWeek: newDays
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{day.charAt(0).toUpperCase() + day.slice(1)}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem value="advanced-days-month">
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Calendar className="h-4 w-4 mr-2" />
|
||||||
|
Days of Month
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="space-y-4 pt-2">
|
||||||
|
<div className="grid grid-cols-7 gap-2">
|
||||||
|
{Array.from({ length: 31 }, (_, i) => i + 1).map((day) => (
|
||||||
|
<Button
|
||||||
|
key={day}
|
||||||
|
variant={schedule.daysOfMonth?.includes(day) ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
className="h-9 w-full text-center"
|
||||||
|
onClick={() => {
|
||||||
|
const currentDays = schedule.daysOfMonth || [];
|
||||||
|
const newDays = currentDays.includes(day)
|
||||||
|
? currentDays.filter(d => d !== day)
|
||||||
|
: [...currentDays, day].sort((a, b) => a - b);
|
||||||
|
|
||||||
|
updateSchedule(index, {
|
||||||
|
daysOfMonth: newDays,
|
||||||
|
customSchedule: true,
|
||||||
|
cronExpression: generateAdvancedCronExpression({
|
||||||
|
...schedule,
|
||||||
|
daysOfMonth: newDays
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{day}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem value="advanced-months">
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Calendar className="h-4 w-4 mr-2" />
|
||||||
|
Months
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="space-y-4 pt-2">
|
||||||
|
<div className="grid grid-cols-3 sm:grid-cols-4 gap-2">
|
||||||
|
{[
|
||||||
|
'January', 'February', 'March', 'April',
|
||||||
|
'May', 'June', 'July', 'August',
|
||||||
|
'September', 'October', 'November', 'December'
|
||||||
|
].map((month, idx) => (
|
||||||
|
<Button
|
||||||
|
key={month}
|
||||||
|
variant={schedule.months?.includes(idx + 1) ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
className="h-9 text-center"
|
||||||
|
onClick={() => {
|
||||||
|
const currentMonths = schedule.months || [];
|
||||||
|
const monthValue = idx + 1;
|
||||||
|
const newMonths = currentMonths.includes(monthValue)
|
||||||
|
? currentMonths.filter(m => m !== monthValue)
|
||||||
|
: [...currentMonths, monthValue].sort((a, b) => a - b);
|
||||||
|
|
||||||
|
updateSchedule(index, {
|
||||||
|
months: newMonths,
|
||||||
|
customSchedule: true,
|
||||||
|
cronExpression: generateAdvancedCronExpression({
|
||||||
|
...schedule,
|
||||||
|
months: newMonths
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{month.substring(0, 3)}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user