♻️(front) refactor RecurrenceEditor component
Improve RecurrenceEditor with better RRULE parsing, support for all recurrence patterns (daily, weekly, monthly, yearly) and cleaner UI with Cunningham components. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,10 @@
|
|||||||
.recurrence-editor {
|
.recurrence-editor {
|
||||||
|
&__label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
&__weekday-button {
|
&__weekday-button {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
@@ -23,6 +29,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__warning {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
background-color: #fff3cd;
|
||||||
|
border: 1px solid #ffc107;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #856404;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility classes for layout
|
// Utility classes for layout
|
||||||
@@ -38,6 +58,10 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--flex-wrap {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
&--gap-1rem {
|
&--gap-1rem {
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,110 @@
|
|||||||
import { Select, Input } from '@openfun/cunningham-react';
|
import { Select, Input } from '@gouvfr-lasuite/cunningham-react';
|
||||||
import { useState } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import type { IcsRecurrenceRule } from 'ts-ics';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import type { IcsRecurrenceRule, IcsWeekDay } from 'ts-ics';
|
||||||
|
|
||||||
const WEEKDAYS = [
|
type RecurrenceFrequency = IcsRecurrenceRule['frequency'];
|
||||||
{ value: 'MO', label: 'L' },
|
|
||||||
{ value: 'TU', label: 'M' },
|
|
||||||
{ value: 'WE', label: 'M' },
|
|
||||||
{ value: 'TH', label: 'J' },
|
|
||||||
{ value: 'FR', label: 'V' },
|
|
||||||
{ value: 'SA', label: 'S' },
|
|
||||||
{ value: 'SU', label: 'D' },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
const RECURRENCE_OPTIONS = [
|
const WEEKDAY_KEYS: IcsWeekDay[] = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'];
|
||||||
{ value: 'NONE', label: 'Non' },
|
|
||||||
{ value: 'DAILY', label: 'Tous les jours' },
|
const MONTHS = [
|
||||||
{ value: 'WEEKLY', label: 'Toutes les semaines' },
|
{ value: 1, key: 'january' },
|
||||||
{ value: 'MONTHLY', label: 'Tous les mois' },
|
{ value: 2, key: 'february' },
|
||||||
{ value: 'YEARLY', label: 'Tous les ans' },
|
{ value: 3, key: 'march' },
|
||||||
{ value: 'CUSTOM', label: 'Personnalisé...' },
|
{ value: 4, key: 'april' },
|
||||||
] as const;
|
{ value: 5, key: 'may' },
|
||||||
|
{ value: 6, key: 'june' },
|
||||||
|
{ value: 7, key: 'july' },
|
||||||
|
{ value: 8, key: 'august' },
|
||||||
|
{ value: 9, key: 'september' },
|
||||||
|
{ value: 10, key: 'october' },
|
||||||
|
{ value: 11, key: 'november' },
|
||||||
|
{ value: 12, key: 'december' },
|
||||||
|
];
|
||||||
|
|
||||||
interface RecurrenceEditorProps {
|
interface RecurrenceEditorProps {
|
||||||
value?: IcsRecurrenceRule;
|
value?: IcsRecurrenceRule;
|
||||||
onChange: (rule: IcsRecurrenceRule | undefined) => void;
|
onChange: (rule: IcsRecurrenceRule | undefined) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate day of month based on month (handles leap years and month lengths)
|
||||||
|
*/
|
||||||
|
function isValidDayForMonth(day: number, month?: number): boolean {
|
||||||
|
if (day < 1 || day > 31) return false;
|
||||||
|
|
||||||
|
if (!month) return true; // No month specified, allow any valid day
|
||||||
|
|
||||||
|
// Days per month (non-leap year)
|
||||||
|
const daysInMonth: Record<number, number> = {
|
||||||
|
1: 31, 2: 29, 3: 31, 4: 30, 5: 31, 6: 30,
|
||||||
|
7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31
|
||||||
|
};
|
||||||
|
|
||||||
|
return day <= (daysInMonth[month] || 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get warning message for invalid dates
|
||||||
|
*/
|
||||||
|
function getDateWarning(t: (key: string) => string, day: number, month?: number): string | null {
|
||||||
|
if (!month) return null;
|
||||||
|
|
||||||
|
if (month === 2 && day > 29) {
|
||||||
|
return t('calendar.recurrence.warnings.februaryMax');
|
||||||
|
}
|
||||||
|
if (month === 2 && day === 29) {
|
||||||
|
return t('calendar.recurrence.warnings.leapYear');
|
||||||
|
}
|
||||||
|
if ([4, 6, 9, 11].includes(month) && day > 30) {
|
||||||
|
return t('calendar.recurrence.warnings.monthMax30');
|
||||||
|
}
|
||||||
|
if (day > 31) {
|
||||||
|
return t('calendar.recurrence.warnings.dayMax31');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export function RecurrenceEditor({ value, onChange }: RecurrenceEditorProps) {
|
export function RecurrenceEditor({ value, onChange }: RecurrenceEditorProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [isCustom, setIsCustom] = useState(() => {
|
const [isCustom, setIsCustom] = useState(() => {
|
||||||
if (!value) return false;
|
if (!value) return false;
|
||||||
return value.interval !== 1 || value.byDay?.length || value.count || value.until;
|
return value.interval !== 1 || value.byDay?.length || value.byMonthday?.length || value.byMonth?.length || value.count || value.until;
|
||||||
});
|
});
|
||||||
|
|
||||||
const getSimpleValue = () => {
|
// Generate options from translations
|
||||||
|
const recurrenceOptions = useMemo(() => [
|
||||||
|
{ value: 'NONE', label: t('calendar.recurrence.none') },
|
||||||
|
{ value: 'DAILY', label: t('calendar.recurrence.daily') },
|
||||||
|
{ value: 'WEEKLY', label: t('calendar.recurrence.weekly') },
|
||||||
|
{ value: 'MONTHLY', label: t('calendar.recurrence.monthly') },
|
||||||
|
{ value: 'YEARLY', label: t('calendar.recurrence.yearly') },
|
||||||
|
{ value: 'CUSTOM', label: t('calendar.recurrence.custom') },
|
||||||
|
], [t]);
|
||||||
|
|
||||||
|
const frequencyOptions = useMemo(() => [
|
||||||
|
{ value: 'DAILY', label: t('calendar.recurrence.days') },
|
||||||
|
{ value: 'WEEKLY', label: t('calendar.recurrence.weeks') },
|
||||||
|
{ value: 'MONTHLY', label: t('calendar.recurrence.months') },
|
||||||
|
{ value: 'YEARLY', label: t('calendar.recurrence.years') },
|
||||||
|
], [t]);
|
||||||
|
|
||||||
|
const weekdays = useMemo(() => WEEKDAY_KEYS.map(key => ({
|
||||||
|
value: key,
|
||||||
|
label: t(`calendar.recurrence.weekdays.${key.toLowerCase()}`),
|
||||||
|
})), [t]);
|
||||||
|
|
||||||
|
const monthOptions = useMemo(() => MONTHS.map(month => ({
|
||||||
|
value: String(month.value),
|
||||||
|
label: t(`calendar.recurrence.months.${month.key}`),
|
||||||
|
})), [t]);
|
||||||
|
|
||||||
|
const getSimpleValue = (): string => {
|
||||||
if (!value) return 'NONE';
|
if (!value) return 'NONE';
|
||||||
if (isCustom) return 'CUSTOM';
|
if (isCustom) return 'CUSTOM';
|
||||||
return value.freq;
|
return value.frequency;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSimpleChange = (newValue: string) => {
|
const handleSimpleChange = (newValue: string) => {
|
||||||
@@ -44,21 +113,23 @@ export function RecurrenceEditor({ value, onChange }: RecurrenceEditorProps) {
|
|||||||
onChange(undefined);
|
onChange(undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newValue === 'CUSTOM') {
|
if (newValue === 'CUSTOM') {
|
||||||
setIsCustom(true);
|
setIsCustom(true);
|
||||||
onChange({
|
onChange({
|
||||||
freq: 'WEEKLY',
|
frequency: 'WEEKLY',
|
||||||
interval: 1
|
interval: 1
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsCustom(false);
|
setIsCustom(false);
|
||||||
onChange({
|
onChange({
|
||||||
freq: newValue as IcsRecurrenceRule['freq'],
|
frequency: newValue as RecurrenceFrequency,
|
||||||
interval: 1,
|
interval: 1,
|
||||||
byDay: undefined,
|
byDay: undefined,
|
||||||
|
byMonthday: undefined,
|
||||||
|
byMonth: undefined,
|
||||||
count: undefined,
|
count: undefined,
|
||||||
until: undefined
|
until: undefined
|
||||||
});
|
});
|
||||||
@@ -66,63 +137,100 @@ export function RecurrenceEditor({ value, onChange }: RecurrenceEditorProps) {
|
|||||||
|
|
||||||
const handleChange = (updates: Partial<IcsRecurrenceRule>) => {
|
const handleChange = (updates: Partial<IcsRecurrenceRule>) => {
|
||||||
onChange({
|
onChange({
|
||||||
freq: 'WEEKLY',
|
frequency: 'WEEKLY',
|
||||||
interval: 1,
|
interval: 1,
|
||||||
...value,
|
...value,
|
||||||
...updates
|
...updates
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get selected day strings from byDay array
|
||||||
|
const getSelectedDays = (): IcsWeekDay[] => {
|
||||||
|
if (!value?.byDay) return [];
|
||||||
|
return value.byDay.map(d => typeof d === 'string' ? d as IcsWeekDay : d.day);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDaySelected = (day: IcsWeekDay): boolean => {
|
||||||
|
return getSelectedDays().includes(day);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleDay = (day: IcsWeekDay) => {
|
||||||
|
const currentDays = getSelectedDays();
|
||||||
|
const newDays = currentDays.includes(day)
|
||||||
|
? currentDays.filter(d => d !== day)
|
||||||
|
: [...currentDays, day];
|
||||||
|
handleChange({ byDay: newDays.map(d => ({ day: d })) });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get selected month day
|
||||||
|
const getMonthDay = (): number => {
|
||||||
|
return value?.byMonthday?.[0] ?? 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get selected month for yearly recurrence
|
||||||
|
const getMonth = (): string => {
|
||||||
|
return String(value?.byMonth?.[0] ?? 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMonthDayChange = (day: number) => {
|
||||||
|
handleChange({ byMonthday: [day] });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMonthChange = (month: number) => {
|
||||||
|
handleChange({ byMonth: [month] });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate date warning
|
||||||
|
const monthDay = getMonthDay();
|
||||||
|
const selectedMonth = value?.frequency === 'YEARLY' ? parseInt(getMonth()) : undefined;
|
||||||
|
const dateWarning = isCustom && (value?.frequency === 'MONTHLY' || value?.frequency === 'YEARLY')
|
||||||
|
? getDateWarning(t, monthDay, selectedMonth)
|
||||||
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="recurrence-editor-layout recurrence-editor-layout--gap-1rem">
|
<div className="recurrence-editor-layout recurrence-editor-layout--gap-1rem">
|
||||||
<Select
|
<Select
|
||||||
label="Répéter"
|
label={t('calendar.recurrence.label')}
|
||||||
value={getSimpleValue()}
|
value={getSimpleValue()}
|
||||||
options={RECURRENCE_OPTIONS}
|
options={recurrenceOptions}
|
||||||
onChange={(e) => handleSimpleChange(e.target.value)}
|
onChange={(e) => handleSimpleChange(String(e.target.value ?? ''))}
|
||||||
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isCustom && (
|
{isCustom && (
|
||||||
<div className="recurrence-editor-layout recurrence-editor-layout--gap-1rem recurrence-editor-layout--margin-left-2rem">
|
<div className="recurrence-editor-layout recurrence-editor-layout--gap-1rem recurrence-editor-layout--margin-left-2rem">
|
||||||
<div className="recurrence-editor-layout recurrence-editor-layout--row recurrence-editor-layout--align-center recurrence-editor-layout--gap-1rem">
|
<div className="recurrence-editor-layout recurrence-editor-layout--row recurrence-editor-layout--align-center recurrence-editor-layout--gap-1rem">
|
||||||
<span>Répéter tous les</span>
|
<span>{t('calendar.recurrence.everyLabel')}</span>
|
||||||
<Input
|
<Input
|
||||||
|
label=""
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
value={value?.interval || 1}
|
value={value?.interval || 1}
|
||||||
onChange={(e) => handleChange({ interval: parseInt(e.target.value) })}
|
onChange={(e) => handleChange({ interval: parseInt(e.target.value) || 1 })}
|
||||||
style={{ width: '80px' }}
|
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
value={value?.freq || 'WEEKLY'}
|
label=""
|
||||||
options={[
|
value={value?.frequency || 'WEEKLY'}
|
||||||
{ value: 'DAILY', label: 'jours' },
|
options={frequencyOptions}
|
||||||
{ value: 'WEEKLY', label: 'semaines' },
|
onChange={(e) => handleChange({ frequency: String(e.target.value ?? '') as RecurrenceFrequency })}
|
||||||
{ value: 'MONTHLY', label: 'mois' },
|
|
||||||
{ value: 'YEARLY', label: 'années' },
|
|
||||||
]}
|
|
||||||
onChange={(e) => handleChange({ freq: e.target.value as IcsRecurrenceRule['freq'] })}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{value?.freq === 'WEEKLY' && (
|
{/* WEEKLY: Day selection */}
|
||||||
|
{value?.frequency === 'WEEKLY' && (
|
||||||
<div className="recurrence-editor-layout">
|
<div className="recurrence-editor-layout">
|
||||||
<span>Répéter le</span>
|
<span className="recurrence-editor__label">{t('calendar.recurrence.repeatOn')}</span>
|
||||||
<div className="recurrence-editor-layout recurrence-editor-layout--row recurrence-editor-layout--gap-0-5rem recurrence-editor-layout--margin-top-0-5rem">
|
<div className="recurrence-editor-layout recurrence-editor-layout--row recurrence-editor-layout--gap-0-5rem recurrence-editor-layout--margin-top-0-5rem recurrence-editor-layout--flex-wrap">
|
||||||
{WEEKDAYS.map(day => {
|
{weekdays.map(day => {
|
||||||
const isSelected = value?.byDay?.includes(day.value) || false;
|
const isSelected = isDaySelected(day.value);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={day.value}
|
key={day.value}
|
||||||
|
type="button"
|
||||||
className={`recurrence-editor__weekday-button ${isSelected ? 'recurrence-editor__weekday-button--selected' : ''}`}
|
className={`recurrence-editor__weekday-button ${isSelected ? 'recurrence-editor__weekday-button--selected' : ''}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const byDay = value?.byDay || [];
|
toggleDay(day.value);
|
||||||
handleChange({
|
|
||||||
byDay: byDay.includes(day.value)
|
|
||||||
? byDay.filter(d => d !== day.value)
|
|
||||||
: [...byDay, day.value]
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{day.label}
|
{day.label}
|
||||||
@@ -133,36 +241,100 @@ export function RecurrenceEditor({ value, onChange }: RecurrenceEditorProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* MONTHLY: Day of month selection */}
|
||||||
|
{value?.frequency === 'MONTHLY' && (
|
||||||
|
<div className="recurrence-editor-layout">
|
||||||
|
<span className="recurrence-editor__label">{t('calendar.recurrence.repeatOnDay')}</span>
|
||||||
|
<div className="recurrence-editor-layout recurrence-editor-layout--row recurrence-editor-layout--align-center recurrence-editor-layout--gap-0-5rem recurrence-editor-layout--margin-top-0-5rem">
|
||||||
|
<span>{t('calendar.recurrence.dayOfMonth')}</span>
|
||||||
|
<Input
|
||||||
|
label=""
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={31}
|
||||||
|
value={getMonthDay()}
|
||||||
|
onChange={(e) => {
|
||||||
|
const day = parseInt(e.target.value) || 1;
|
||||||
|
if (day >= 1 && day <= 31) {
|
||||||
|
handleMonthDayChange(day);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{dateWarning && (
|
||||||
|
<div className="recurrence-editor__warning">
|
||||||
|
⚠️ {dateWarning}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* YEARLY: Month + Day selection */}
|
||||||
|
{value?.frequency === 'YEARLY' && (
|
||||||
|
<div className="recurrence-editor-layout">
|
||||||
|
<span className="recurrence-editor__label">{t('calendar.recurrence.repeatOnDate')}</span>
|
||||||
|
<div className="recurrence-editor-layout recurrence-editor-layout--row recurrence-editor-layout--align-center recurrence-editor-layout--gap-0-5rem recurrence-editor-layout--margin-top-0-5rem">
|
||||||
|
<Select
|
||||||
|
label=""
|
||||||
|
value={getMonth()}
|
||||||
|
options={monthOptions}
|
||||||
|
onChange={(e) => handleMonthChange(parseInt(String(e.target.value)) || 1)}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label=""
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={31}
|
||||||
|
value={getMonthDay()}
|
||||||
|
onChange={(e) => {
|
||||||
|
const day = parseInt(e.target.value) || 1;
|
||||||
|
if (day >= 1 && day <= 31) {
|
||||||
|
handleMonthDayChange(day);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{dateWarning && (
|
||||||
|
<div className="recurrence-editor__warning">
|
||||||
|
⚠️ {dateWarning}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* End conditions */}
|
||||||
<div className="recurrence-editor-layout">
|
<div className="recurrence-editor-layout">
|
||||||
<span>Se termine</span>
|
<span className="recurrence-editor__label">{t('calendar.recurrence.endsLabel')}</span>
|
||||||
<div className="recurrence-editor-layout recurrence-editor-layout--gap-0-5rem recurrence-editor-layout--margin-top-0-5rem">
|
<div className="recurrence-editor-layout recurrence-editor-layout--gap-0-5rem recurrence-editor-layout--margin-top-0-5rem">
|
||||||
<div className="recurrence-editor-layout recurrence-editor-layout--row recurrence-editor-layout--align-center recurrence-editor-layout--gap-0-5rem">
|
<div className="recurrence-editor-layout recurrence-editor-layout--row recurrence-editor-layout--align-center recurrence-editor-layout--gap-0-5rem">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
|
id="end-never"
|
||||||
name="end-type"
|
name="end-type"
|
||||||
checked={!value?.count && !value?.until}
|
checked={!value?.count && !value?.until}
|
||||||
onChange={() => handleChange({ count: undefined, until: undefined })}
|
onChange={() => handleChange({ count: undefined, until: undefined })}
|
||||||
/>
|
/>
|
||||||
<span>Jamais</span>
|
<label htmlFor="end-never">{t('calendar.recurrence.never')}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="recurrence-editor-layout recurrence-editor-layout--row recurrence-editor-layout--align-center recurrence-editor-layout--gap-0-5rem">
|
<div className="recurrence-editor-layout recurrence-editor-layout--row recurrence-editor-layout--align-center recurrence-editor-layout--gap-0-5rem">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
|
id="end-date"
|
||||||
name="end-type"
|
name="end-type"
|
||||||
checked={!!value?.until}
|
checked={!!value?.until}
|
||||||
onChange={() => handleChange({ until: new Date(), count: undefined })}
|
onChange={() => handleChange({ until: { type: 'DATE', date: new Date() }, count: undefined })}
|
||||||
/>
|
/>
|
||||||
<span>Le</span>
|
<label htmlFor="end-date">{t('calendar.recurrence.on')}</label>
|
||||||
{value?.until && (
|
{value?.until && (
|
||||||
<Input
|
<Input
|
||||||
|
label=""
|
||||||
type="date"
|
type="date"
|
||||||
value={value.until.date ? new Date(value.until.date).toISOString().split('T')[0] : ''}
|
value={value.until.date instanceof Date ? value.until.date.toISOString().split('T')[0] : ''}
|
||||||
onChange={(e) => handleChange({
|
onChange={(e) => handleChange({
|
||||||
until: {
|
until: {
|
||||||
type: 'DATE-TIME',
|
type: 'DATE',
|
||||||
date: new Date(e.target.value),
|
date: new Date(e.target.value),
|
||||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@@ -172,21 +344,22 @@ export function RecurrenceEditor({ value, onChange }: RecurrenceEditorProps) {
|
|||||||
<div className="recurrence-editor-layout recurrence-editor-layout--row recurrence-editor-layout--align-center recurrence-editor-layout--gap-0-5rem">
|
<div className="recurrence-editor-layout recurrence-editor-layout--row recurrence-editor-layout--align-center recurrence-editor-layout--gap-0-5rem">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
|
id="end-count"
|
||||||
name="end-type"
|
name="end-type"
|
||||||
checked={!!value?.count}
|
checked={!!value?.count}
|
||||||
onChange={() => handleChange({ count: 1, until: undefined })}
|
onChange={() => handleChange({ count: 10, until: undefined })}
|
||||||
/>
|
/>
|
||||||
<span>Après</span>
|
<label htmlFor="end-count">{t('calendar.recurrence.after')}</label>
|
||||||
{value?.count !== undefined && (
|
{value?.count !== undefined && (
|
||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
|
label=""
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
value={value.count}
|
value={value.count}
|
||||||
onChange={(e) => handleChange({ count: parseInt(e.target.value) })}
|
onChange={(e) => handleChange({ count: parseInt(e.target.value) || 1 })}
|
||||||
style={{ width: '80px' }}
|
/>
|
||||||
/>
|
<span>{t('calendar.recurrence.occurrences')}</span>
|
||||||
<span>occurrences</span>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -196,4 +369,4 @@ export function RecurrenceEditor({ value, onChange }: RecurrenceEditorProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user