My-Weekly-ToDo-List/src/components/RecurrenceModal.tsx

215 lines
11 KiB
TypeScript

import React, { useState } from 'react';
interface RecurrenceModalProps {
task: any;
onClose: () => void;
onSave: (taskId: string, recurrence: any) => Promise<void>;
language?: string;
}
const WEEKDAYS_EN = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const WEEKDAYS_DE = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
export default function RecurrenceModal({ task, onClose, onSave, language = 'en' }: RecurrenceModalProps) {
const [isRecurring, setIsRecurring] = useState(task.isRecurring || false);
const [interval, setInterval] = useState(task.recurrenceInterval || 1);
const [unit, setUnit] = useState(task.recurrenceUnit || 'weeks');
const [time, setTime] = useState(task.recurrenceTime || task.startTime || '09:00');
const [endDate, setEndDate] = useState(task.recurrenceEndDate ? new Date(task.recurrenceEndDate).toISOString().split('T')[0] : '');
const [isSaving, setIsSaving] = useState(false);
// Day-of-week for weekly recurrence (0=Sun, 1=Mon, etc.)
const taskDow = task.scheduledDate ? new Date(task.scheduledDate).getDay() : (task.dayOfWeek ?? new Date().getDay());
const [selectedDays, setSelectedDays] = useState<number[]>(task.recurrenceDays?.length ? task.recurrenceDays : [taskDow]);
const weekdays = language === 'de' ? WEEKDAYS_DE : WEEKDAYS_EN;
const isDE = language === 'de';
// Helper to format date as DD.MM.YYYY
const formatDate = (dateStr: string) => {
if (!dateStr) return '';
const [y, m, d] = dateStr.split('-');
return `${d}.${m}.${y}`;
};
// Helper to parse DD.MM.YYYY back to YYYY-MM-DD
const parseDateInput = (input: string) => {
const match = input.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/);
if (match) {
const d = match[1].padStart(2, '0');
const m = match[2].padStart(2, '0');
const y = match[3];
return `${y}-${m}-${d}`;
}
return input; // fallback to raw
};
const toggleDay = (day: number) => {
setSelectedDays(prev => {
if (prev.includes(day)) {
return prev.length > 1 ? prev.filter(d => d !== day) : prev; // keep at least one
}
return [...prev, day].sort();
});
};
const handleSave = async () => {
setIsSaving(true);
try {
const parsedEndDate = parseDateInput(endDate);
await onSave(task.id, {
isRecurring,
recurrenceInterval: isRecurring ? interval : null,
recurrenceUnit: isRecurring ? unit : null,
recurrenceTime: isRecurring ? time : null,
recurrenceEndDate: isRecurring && parsedEndDate ? new Date(parsedEndDate) : null,
recurrenceDays: isRecurring && unit === 'weeks' ? selectedDays : null,
});
onClose();
} catch (error) {
console.error('Failed to save recurrence', error);
setIsSaving(false);
}
};
return (
<div className="weekly-modal-overlay" onClick={onClose}>
<div className="weekly-modal-content" onClick={e => e.stopPropagation()} style={{ maxWidth: '400px' }}>
<h3 style={{ marginBottom: '1.5rem' }}>{isDE ? 'Wiederkehrende Aufgabe' : 'Recurring Task'}</h3>
<div style={{ marginBottom: '1.5rem' }}>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '1rem' }}>
<input
type="checkbox"
id="isRecurring"
checked={isRecurring}
onChange={e => setIsRecurring(e.target.checked)}
style={{ width: '18px', height: '18px', marginRight: '10px' }}
/>
<label htmlFor="isRecurring" style={{ fontSize: '1rem', fontWeight: 500 }}>{isDE ? 'Wiederholung aktivieren' : 'Enable Recurrence'}</label>
</div>
{isRecurring && (
<div style={{ paddingLeft: '28px', display: 'flex', flexDirection: 'column', gap: '12px' }}>
<div>
<label style={{ display: 'block', marginBottom: '4px', fontSize: '0.9rem', color: '#666' }}>{isDE ? 'Wiederholen alle' : 'Repeat every'}</label>
<div style={{ display: 'flex', gap: '8px' }}>
<input
type="number"
min="1"
value={interval}
onChange={e => setInterval(parseInt(e.target.value) || 1)}
style={{ width: '60px', padding: '6px', borderRadius: '4px', border: '1px solid #ddd' }}
/>
<select
value={unit}
onChange={e => setUnit(e.target.value)}
style={{ flex: 1, padding: '6px', borderRadius: '4px', border: '1px solid #ddd' }}
>
<option value="days">{isDE ? 'Tage' : 'Days'}</option>
<option value="weeks">{isDE ? 'Wochen' : 'Weeks'}</option>
<option value="months">{isDE ? 'Monate' : 'Months'}</option>
<option value="years">{isDE ? 'Jahre' : 'Years'}</option>
</select>
</div>
</div>
{/* Weekday selector for weekly recurrence */}
{unit === 'weeks' && (
<div>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '0.9rem', color: '#666' }}>{isDE ? 'An diesen Tagen' : 'On these days'}</label>
<div style={{ display: 'flex', gap: '4px' }}>
{weekdays.map((dayName, i) => (
<button
key={i}
type="button"
onClick={() => toggleDay(i)}
style={{
flex: 1,
padding: '6px 2px',
borderRadius: '6px',
border: 'none',
cursor: 'pointer',
fontSize: '0.8rem',
fontWeight: selectedDays.includes(i) ? 700 : 400,
background: selectedDays.includes(i) ? '#3b82f6' : '#f3f4f6',
color: selectedDays.includes(i) ? '#fff' : '#666',
transition: 'all 0.15s',
}}
>
{dayName}
</button>
))}
</div>
</div>
)}
<div>
<label style={{ display: 'block', marginBottom: '4px', fontSize: '0.9rem', color: '#666' }}>{isDE ? 'Uhrzeit' : 'Time'}</label>
<input
type="time"
value={time}
onChange={e => setTime(e.target.value)}
style={{ width: '100%', padding: '6px', borderRadius: '4px', border: '1px solid #ddd' }}
/>
</div>
<div>
<label style={{ display: 'block', marginBottom: '4px', fontSize: '0.9rem', color: '#666' }}>{isDE ? 'Enddatum (Optional, TT.MM.JJJJ)' : 'End Date (Optional, DD.MM.YYYY)'}</label>
<input
type="text"
placeholder="DD.MM.YYYY"
value={endDate.includes('-') ? formatDate(endDate) : endDate}
onChange={(e) => {
const input = e.target.value;
// If user is deleting (new length < old length), just let them delete
if (input.length < endDate.length) {
setEndDate(input);
return;
}
let val = input.replace(/\D/g, ''); // Keep only digits
if (val.length > 8) val = val.slice(0, 8);
let formatted = val;
if (val.length > 2 && val.length <= 4) {
formatted = val.slice(0, 2) + '.' + val.slice(2);
} else if (val.length > 4) {
formatted = val.slice(0, 2) + '.' + val.slice(2, 4) + '.' + val.slice(4);
}
// If they typed a dot manually at the right position, don't interfere too much
if (input.endsWith('.') && (input.length === 3 || input.length === 6)) {
setEndDate(input);
} else {
setEndDate(formatted);
}
}}
style={{ width: '100%', padding: '6px', borderRadius: '4px', border: '1px solid #ddd' }}
/>
</div>
</div>
)}
</div>
<div className="weekly-modal-actions">
<button
className="weekly-btn weekly-btn-primary"
onClick={handleSave}
disabled={isSaving}
>
{isSaving ? (isDE ? 'Speichere...' : 'Saving...') : (isDE ? 'Speichern' : 'Save')}
</button>
<button
className="weekly-btn weekly-btn-secondary"
onClick={onClose}
disabled={isSaving}
>
{isDE ? 'Abbrechen' : 'Cancel'}
</button>
</div>
</div>
</div>
);
}