// Copyright (c) 2026 PowerOn AG // All rights reserved. /** * Accordion schedule planner → updates ScheduleSpec; parent derives cron via buildCronFromSpec. */ import React, { useCallback, useMemo, useState } from 'react'; import { MINUTE_SELECT_OPTIONS, WEEKDAYS_MO_SO, type ScheduleSpec, } from '../../utils/scheduleCron'; import { useLanguage } from '../../providers/language/LanguageContext'; import { AccordionList } from '../UiComponents'; import styles from './SchedulePlanner.module.css'; export type PlannerModeId = 'minutes' | 'hours' | 'days' | 'weeks' | 'months' | 'custom'; const PLANNER_MODE_IDS: PlannerModeId[] = [ 'minutes', 'hours', 'days', 'weeks', 'months', 'custom', ]; function plannerModeFromSpec(spec: ScheduleSpec): PlannerModeId { const m = spec.mode; if (m === 'interval') { if (spec.intervalUnit === 'hours') return 'hours'; if (spec.intervalUnit === 'days') return 'days'; if (spec.intervalUnit === 'minutes') return 'minutes'; return 'custom'; } if (m === 'minutes') return 'minutes'; if (m === 'hours') return 'hours'; if (m === 'days' || m === 'daily') return 'days'; if (m === 'weeks' || m === 'weekly' || m === 'weekdays') return 'weeks'; if (m === 'months' || (m === 'calendar' && spec.calendarPeriod === 'monthly')) return 'months'; return 'custom'; } function withPlannerMode(spec: ScheduleSpec, id: PlannerModeId): ScheduleSpec { const base = { ...spec }; switch (id) { case 'minutes': return { ...base, mode: 'minutes', intervalValue: Math.max(1, base.intervalValue || 15), }; case 'hours': return { ...base, mode: 'hours', intervalValue: Math.max(1, base.intervalValue || 1), }; case 'days': return { ...base, mode: 'days', intervalValue: Math.max(1, base.intervalValue || 1), }; case 'weeks': return { ...base, mode: 'weeks', weeksInterval: Math.max(1, base.weeksInterval || 1), weekdays: base.weekdays.length > 0 ? [...base.weekdays] : [1, 2, 3, 4, 5], }; case 'months': return { ...base, mode: 'months', intervalValue: Math.max(1, base.intervalValue || 1), monthDay: Math.min(28, Math.max(1, base.monthDay || 1)), }; case 'custom': return { ...base, mode: 'custom', customCron: (base.customCron || '0 9 * * *').trim(), }; default: return base; } } export interface SchedulePlannerProps { value: ScheduleSpec; onChange: (next: ScheduleSpec) => void; className?: string; disabled?: boolean; } export const SchedulePlanner: React.FC = ({ value, onChange, className = '', disabled = false, }) => { const { t } = useLanguage(); const activeMode = useMemo(() => plannerModeFromSpec(value), [value]); const [openId, setOpenId] = useState(null); const modeMeta = useMemo( () => ({ minutes: { label: t('Alle X Minuten') }, hours: { label: t('Alle X Stunden') }, days: { label: t('Täglich / alle X Tage') }, weeks: { label: t('Wöchentlich') }, months: { label: t('Monatlich') }, custom: { label: t('Custom (Cron)') }, }) as const, [t] ); const push = useCallback( (next: ScheduleSpec) => { if (!disabled) onChange(next); }, [disabled, onChange] ); const handleOpenChange = (next: PlannerModeId | null) => { setOpenId(next); if (next != null) { push(withPlannerMode(value, next)); } }; const hourOptions = useMemo( () => Array.from({ length: 24 }, (_, i) => i), [] ); const renderBody = (id: PlannerModeId) => { const v = plannerModeFromSpec(value) === id ? value : withPlannerMode(value, id); switch (id) { case 'minutes': return (
{t('Alle')} push({ ...v, mode: 'minutes', intervalValue: Math.min(59, Math.max(1, Number(e.target.value) || 1)), }) } /> {t('Minuten')}
); case 'hours': return (
{t('Alle')} push({ ...v, mode: 'hours', intervalValue: Math.min(23, Math.max(1, Number(e.target.value) || 1)), }) } /> {t('Stunden')}
{t('Bei Minute')}
); case 'days': return (
{t('Alle')} push({ ...v, mode: 'days', intervalValue: Math.min(31, Math.max(1, Number(e.target.value) || 1)), }) } /> {t('Tage')}
{t('Uhrzeit')}
:

{t( 'Hinweis: „Alle N Tage“ entspricht in Cron einem Schritt im Tag-des-Monats-Feld, nicht zwingend jedem Kalendertag.' )}

); case 'weeks': return (
{t('Alle')} push({ ...v, mode: 'weeks', weeksInterval: Math.min(52, Math.max(1, Number(e.target.value) || 1)), }) } /> {t('Wochen')}
{(v.weeksInterval ?? 1) > 1 && (

{t( 'Mehr als jede Woche: der erzeugte Cron entspricht vorläufig wöchentlich (ein Wochen-Intervall > 1 ist im Standard-Cron nicht exakt abbildbar).' )}

)}
{t('Wochentage')}
{WEEKDAYS_MO_SO.map(({ cronDow }) => ( ))}
{t('Uhrzeit')}
:
); case 'months': return (
{t('Alle')} push({ ...v, mode: 'months', intervalValue: Math.min(12, Math.max(1, Number(e.target.value) || 1)), }) } /> {t('Monate')}
{t('Tag des Monats')} push({ ...v, mode: 'months', monthDay: Math.min(28, Math.max(1, Number(e.target.value) || 1)), }) } />
{t('Uhrzeit')}
:
); case 'custom': return (
{t('Ausdruck')}
push({ ...v, mode: 'custom', customCron: e.target.value, }) } spellCheck={false} />
{t('Cron')}: {t('Minute')} · {t('Stunde')} · {t('Tag')} · {t('Monat')} ·{' '} {t('Wochentag')} {' · '}[{t('Sekunde')} {t('optional')}]
{t('z.B.')}{' '} 0 9 * * 3,5
); default: return null; } }; return (

{t( 'Legen Sie fest, wann dieser Workflow automatisch laufen soll. Der Cron-Ausdruck wird für die API erzeugt.' )}

items={PLANNER_MODE_IDS.map((mid) => ({ id: mid, title: modeMeta[mid].label, children: renderBody(mid), }))} showSelectionIndicator selectedId={activeMode} openId={openId} onOpenChange={handleOpenChange} disabled={disabled} />
); };