Files
smoothschedule/frontend/src/constants/schedulePresets.ts
poduck 8c52d6a275 refactor: Extract reusable UI components and add TDD documentation
- Add comprehensive TDD documentation to CLAUDE.md with coverage requirements and examples
- Extract reusable UI components to frontend/src/components/ui/ (Modal, FormInput, Button, Alert, etc.)
- Add shared constants (schedulePresets) and utility hooks (useCrudMutation, useFormValidation)
- Update frontend/CLAUDE.md with component documentation and usage examples
- Refactor CreateTaskModal to use shared components and constants
- Fix test assertions to be more robust and accurate across all test files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-10 15:27:27 -05:00

212 lines
5.4 KiB
TypeScript

/**
* Schedule presets for scheduled tasks and event automations.
* Shared between CreateTaskModal and EditTaskModal.
*/
export interface SchedulePreset {
id: string;
label: string;
description: string;
type: 'INTERVAL' | 'CRON';
interval_minutes?: number;
cron_expression?: string;
}
export const SCHEDULE_PRESETS: SchedulePreset[] = [
// Interval-based
{
id: 'every_15min',
label: 'Every 15 minutes',
description: 'Runs 4 times per hour',
type: 'INTERVAL',
interval_minutes: 15,
},
{
id: 'every_30min',
label: 'Every 30 minutes',
description: 'Runs twice per hour',
type: 'INTERVAL',
interval_minutes: 30,
},
{
id: 'every_hour',
label: 'Every hour',
description: 'Runs 24 times per day',
type: 'INTERVAL',
interval_minutes: 60,
},
{
id: 'every_2hours',
label: 'Every 2 hours',
description: 'Runs 12 times per day',
type: 'INTERVAL',
interval_minutes: 120,
},
{
id: 'every_4hours',
label: 'Every 4 hours',
description: 'Runs 6 times per day',
type: 'INTERVAL',
interval_minutes: 240,
},
{
id: 'every_6hours',
label: 'Every 6 hours',
description: 'Runs 4 times per day',
type: 'INTERVAL',
interval_minutes: 360,
},
{
id: 'every_12hours',
label: 'Twice daily',
description: 'Runs at midnight and noon',
type: 'INTERVAL',
interval_minutes: 720,
},
// Cron-based (specific times)
{
id: 'daily_midnight',
label: 'Daily at midnight',
description: 'Runs once per day at 12:00 AM',
type: 'CRON',
cron_expression: '0 0 * * *',
},
{
id: 'daily_9am',
label: 'Daily at 9 AM',
description: 'Runs once per day at 9:00 AM',
type: 'CRON',
cron_expression: '0 9 * * *',
},
{
id: 'daily_6pm',
label: 'Daily at 6 PM',
description: 'Runs once per day at 6:00 PM',
type: 'CRON',
cron_expression: '0 18 * * *',
},
{
id: 'weekdays_9am',
label: 'Weekdays at 9 AM',
description: 'Mon-Fri at 9:00 AM',
type: 'CRON',
cron_expression: '0 9 * * 1-5',
},
{
id: 'weekdays_6pm',
label: 'Weekdays at 6 PM',
description: 'Mon-Fri at 6:00 PM',
type: 'CRON',
cron_expression: '0 18 * * 1-5',
},
{
id: 'weekly_sunday',
label: 'Weekly on Sunday',
description: 'Every Sunday at midnight',
type: 'CRON',
cron_expression: '0 0 * * 0',
},
{
id: 'weekly_monday',
label: 'Weekly on Monday',
description: 'Every Monday at 9:00 AM',
type: 'CRON',
cron_expression: '0 9 * * 1',
},
{
id: 'monthly_1st',
label: 'Monthly on the 1st',
description: 'First day of each month',
type: 'CRON',
cron_expression: '0 0 1 * *',
},
];
/** Event trigger options for event automations */
export interface TriggerOption {
value: string;
label: string;
}
export const TRIGGER_OPTIONS: TriggerOption[] = [
{ value: 'before_start', label: 'Before Start' },
{ value: 'at_start', label: 'At Start' },
{ value: 'after_start', label: 'After Start' },
{ value: 'after_end', label: 'After End' },
{ value: 'on_complete', label: 'When Completed' },
{ value: 'on_cancel', label: 'When Canceled' },
];
/** Offset presets for event automations */
export interface OffsetPreset {
value: number;
label: string;
}
export const OFFSET_PRESETS: OffsetPreset[] = [
{ value: 0, label: 'Immediately' },
{ value: 5, label: '5 min' },
{ value: 10, label: '10 min' },
{ value: 15, label: '15 min' },
{ value: 30, label: '30 min' },
{ value: 60, label: '1 hour' },
];
/**
* Get a schedule preset by ID
*/
export const getSchedulePreset = (id: string): SchedulePreset | undefined => {
return SCHEDULE_PRESETS.find((preset) => preset.id === id);
};
/**
* Get schedule description for display
*/
export const getScheduleDescription = (
scheduleMode: 'preset' | 'onetime' | 'advanced',
selectedPreset: string,
runAtDate?: string,
runAtTime?: string,
customCron?: string
): string => {
if (scheduleMode === 'onetime') {
if (runAtDate && runAtTime) {
return `Once on ${new Date(`${runAtDate}T${runAtTime}`).toLocaleString()}`;
}
return 'Select date and time';
}
if (scheduleMode === 'advanced') {
return `Custom: ${customCron || '0 0 * * *'}`;
}
const preset = getSchedulePreset(selectedPreset);
return preset?.description || 'Select a schedule';
};
/**
* Get event timing description for display
*/
export const getEventTimingDescription = (
selectedTrigger: string,
selectedOffset: number
): string => {
const trigger = TRIGGER_OPTIONS.find((t) => t.value === selectedTrigger);
if (!trigger) return 'Select timing';
if (selectedTrigger === 'on_complete') return 'When event is completed';
if (selectedTrigger === 'on_cancel') return 'When event is canceled';
if (selectedOffset === 0) {
if (['before_start', 'at_start', 'after_start'].includes(selectedTrigger)) {
return 'At event start';
}
if (selectedTrigger === 'after_end') return 'At event end';
}
const offsetLabel = OFFSET_PRESETS.find((o) => o.value === selectedOffset)?.label || `${selectedOffset} min`;
if (selectedTrigger === 'before_start') return `${offsetLabel} before event starts`;
if (['at_start', 'after_start'].includes(selectedTrigger)) return `${offsetLabel} after event starts`;
if (selectedTrigger === 'after_end') return `${offsetLabel} after event ends`;
return trigger.label;
};