My-Weekly-ToDo-List/src/components/TaskForm.tsx
mARTin 83d0d0b920 fix: implement custom CSS sync spinner and update all components
- Replaced broken animate-spin Tailwind class with custom weekly-spinner CSS

- Standardized loading indicators across Auth, Tasks, and Calendar settings

- Fixed JSX syntax errors in several components
2026-02-23 22:04:16 +01:00

214 lines
6.5 KiB
TypeScript

'use client';
import React, { useState } from 'react';
interface TaskFormData {
title: string;
description?: string;
startTime?: string;
endTime?: string;
dayOfWeek?: number;
}
interface TaskFormProps {
onSubmit: (task: TaskFormData) => void;
isLoading?: boolean;
}
const TaskForm: React.FC<TaskFormProps> = ({ onSubmit, isLoading = false }) => {
const [formData, setFormData] = useState<TaskFormData>({
title: '',
description: '',
startTime: '',
endTime: '',
dayOfWeek: 1 // Default to Monday
});
const [errors, setErrors] = useState<Record<string, string>>({});
const validateForm = (): boolean => {
const newErrors: Record<string, string> = {};
if (!formData.title.trim()) {
newErrors.title = 'Title is required';
}
// Validate time format if provided
if (formData.startTime && !isValidTime(formData.startTime)) {
newErrors.startTime = 'Invalid time format';
}
if (formData.endTime && !isValidTime(formData.endTime)) {
newErrors.endTime = 'Invalid time format';
}
// Check if start time is before end time if both are provided
if (formData.startTime && formData.endTime) {
const start = parseTime(formData.startTime);
const end = parseTime(formData.endTime);
if (start >= end) {
newErrors.endTime = 'End time must be after start time';
}
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const isValidTime = (time: string): boolean => {
const timeRegex = /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/;
return timeRegex.test(time);
};
const parseTime = (time: string): number => {
const [hours, minutes] = time.split(':').map(Number);
return hours * 60 + minutes;
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value } = e.target;
setFormData({
...formData,
[name]: value
});
// Clear error when user starts typing
if (errors[name]) {
setErrors(prev => {
const newErrors = { ...prev };
delete newErrors[name];
return newErrors;
});
}
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (validateForm()) {
onSubmit(formData);
// Reset form
setFormData({
title: '',
description: '',
startTime: '',
endTime: '',
dayOfWeek: 1
});
setErrors({});
}
};
return (
<form onSubmit={handleSubmit} className="task-form bg-white rounded-lg shadow-md p-6 mb-6">
<h3 className="text-lg font-semibold text-gray-800 mb-4">Add New Task</h3>
<div className="form-group mb-4">
<label htmlFor="title" className="block text-sm font-medium text-gray-700 mb-1">
Task Title *
</label>
<input
type="text"
id="title"
name="title"
value={formData.title}
onChange={handleChange}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${errors.title ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="What needs to be done?"
required
/>
{errors.title && <p className="mt-1 text-sm text-red-600">{errors.title}</p>}
</div>
<div className="form-group mb-4">
<label htmlFor="description" className="block text-sm font-medium text-gray-700 mb-1">
Description
</label>
<textarea
id="description"
name="description"
value={formData.description}
onChange={handleChange}
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Add details..."
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div className="form-group">
<label htmlFor="startTime" className="block text-sm font-medium text-gray-700 mb-1">
Start Time
</label>
<input
type="time"
id="startTime"
name="startTime"
value={formData.startTime}
onChange={handleChange}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${errors.startTime ? 'border-red-500' : 'border-gray-300'
}`}
/>
{errors.startTime && <p className="mt-1 text-sm text-red-600">{errors.startTime}</p>}
</div>
<div className="form-group">
<label htmlFor="endTime" className="block text-sm font-medium text-gray-700 mb-1">
End Time
</label>
<input
type="time"
id="endTime"
name="endTime"
value={formData.endTime}
onChange={handleChange}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${errors.endTime ? 'border-red-500' : 'border-gray-300'
}`}
/>
{errors.endTime && <p className="mt-1 text-sm text-red-600">{errors.endTime}</p>}
</div>
</div>
<div className="form-group mb-4">
<label htmlFor="dayOfWeek" className="block text-sm font-medium text-gray-700 mb-1">
Day of Week
</label>
<select
id="dayOfWeek"
name="dayOfWeek"
value={formData.dayOfWeek}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value={0}>Sunday</option>
<option value={1}>Monday</option>
<option value={2}>Tuesday</option>
<option value={3}>Wednesday</option>
<option value={4}>Thursday</option>
<option value={5}>Friday</option>
<option value={6}>Saturday</option>
</select>
</div>
<button
type="submit"
disabled={isLoading}
className={`w-full py-2 px-4 rounded-md text-white font-medium transition-colors ${isLoading
? 'bg-blue-400 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700'
}`}
>
{isLoading ? (
<span className="flex items-center justify-center">
<div className="weekly-spinner weekly-spinner-white -ml-1 mr-2"></div>
Adding Task...
</span>
) : (
'Add Task'
)}
</button>
</form >
);
};
export default TaskForm;