- Changed default selected type in DailyAddForm from 'task' to 'meeting'. - Updated class names in DailyAddForm and EditCheckboxModal for improved visual consistency and user experience, ensuring better color contrast and hover effects.
267 lines
9.2 KiB
TypeScript
267 lines
9.2 KiB
TypeScript
'use client';
|
||
|
||
import { useState, useEffect } from 'react';
|
||
import { DailyCheckbox, DailyCheckboxType, Task } from '@/lib/types';
|
||
import { Button } from '@/components/ui/Button';
|
||
import { Input } from '@/components/ui/Input';
|
||
import { Modal } from '@/components/ui/Modal';
|
||
import { tasksClient } from '@/clients/tasks-client';
|
||
|
||
interface EditCheckboxModalProps {
|
||
checkbox: DailyCheckbox;
|
||
isOpen: boolean;
|
||
onClose: () => void;
|
||
onSave: (text: string, type: DailyCheckboxType, taskId?: string) => Promise<void>;
|
||
saving?: boolean;
|
||
}
|
||
|
||
export function EditCheckboxModal({
|
||
checkbox,
|
||
isOpen,
|
||
onClose,
|
||
onSave,
|
||
saving = false
|
||
}: EditCheckboxModalProps) {
|
||
const [text, setText] = useState(checkbox.text);
|
||
const [type, setType] = useState<DailyCheckboxType>(checkbox.type);
|
||
const [taskId, setTaskId] = useState<string | undefined>(checkbox.taskId);
|
||
const [selectedTask, setSelectedTask] = useState<Task | undefined>(undefined);
|
||
const [allTasks, setAllTasks] = useState<Task[]>([]);
|
||
const [tasksLoading, setTasksLoading] = useState(false);
|
||
const [taskSearch, setTaskSearch] = useState('');
|
||
|
||
// Charger toutes les tâches au début
|
||
useEffect(() => {
|
||
if (isOpen) {
|
||
setTasksLoading(true);
|
||
tasksClient.getTasks()
|
||
.then(response => {
|
||
setAllTasks(response.data);
|
||
// Trouver la tâche sélectionnée si elle existe
|
||
if (taskId) {
|
||
const task = response.data.find((t: Task) => t.id === taskId);
|
||
setSelectedTask(task);
|
||
}
|
||
})
|
||
.catch(console.error)
|
||
.finally(() => setTasksLoading(false));
|
||
}
|
||
}, [isOpen, taskId]);
|
||
|
||
// Mettre à jour la tâche sélectionnée quand taskId change
|
||
useEffect(() => {
|
||
if (taskId && allTasks.length > 0) {
|
||
const task = allTasks.find((t: Task) => t.id === taskId);
|
||
setSelectedTask(task);
|
||
} else {
|
||
setSelectedTask(undefined);
|
||
}
|
||
}, [taskId, allTasks]);
|
||
|
||
// Filtrer les tâches selon la recherche
|
||
const filteredTasks = allTasks.filter(task =>
|
||
task.title.toLowerCase().includes(taskSearch.toLowerCase()) ||
|
||
(task.description && task.description.toLowerCase().includes(taskSearch.toLowerCase()))
|
||
);
|
||
|
||
const handleTaskSelect = (task: Task) => {
|
||
setTaskId(task.id);
|
||
setTaskSearch(''); // Fermer la recherche après sélection
|
||
};
|
||
|
||
const handleSave = async () => {
|
||
if (!text.trim()) return;
|
||
|
||
try {
|
||
await onSave(text.trim(), type, taskId);
|
||
onClose();
|
||
} catch (error) {
|
||
console.error('Erreur lors de la sauvegarde:', error);
|
||
}
|
||
};
|
||
|
||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||
if (e.key === 'Enter' && !e.shiftKey) {
|
||
e.preventDefault();
|
||
handleSave();
|
||
}
|
||
};
|
||
|
||
const resetForm = () => {
|
||
setText(checkbox.text);
|
||
setType(checkbox.type);
|
||
setTaskId(checkbox.taskId);
|
||
};
|
||
|
||
const handleClose = () => {
|
||
resetForm();
|
||
onClose();
|
||
};
|
||
|
||
return (
|
||
<Modal isOpen={isOpen} onClose={handleClose} title="Modifier la tâche">
|
||
<div className="space-y-4">
|
||
{/* Texte */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-[var(--foreground)] mb-2">
|
||
Description
|
||
</label>
|
||
<Input
|
||
value={text}
|
||
onChange={(e) => setText(e.target.value)}
|
||
onKeyDown={handleKeyPress}
|
||
placeholder="Description de la tâche..."
|
||
className="w-full"
|
||
autoFocus
|
||
/>
|
||
</div>
|
||
|
||
{/* Type */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-[var(--foreground)] mb-2">
|
||
Type
|
||
</label>
|
||
<div className="flex gap-2">
|
||
<Button
|
||
type="button"
|
||
onClick={() => setType('task')}
|
||
variant="ghost"
|
||
size="sm"
|
||
className={`flex items-center gap-2 border-l-4 ${
|
||
type === 'task'
|
||
? 'border-l-green-500 bg-green-500/30 text-white font-medium'
|
||
: 'border-l-green-300 hover:border-l-green-400 opacity-70 hover:opacity-90'
|
||
}`}
|
||
>
|
||
✅ Tâche
|
||
</Button>
|
||
<Button
|
||
type="button"
|
||
onClick={() => setType('meeting')}
|
||
variant="ghost"
|
||
size="sm"
|
||
className={`flex items-center gap-2 border-l-4 ${
|
||
type === 'meeting'
|
||
? 'border-l-blue-500 bg-blue-500/30 text-white font-medium'
|
||
: 'border-l-blue-300 hover:border-l-blue-400 opacity-70 hover:opacity-90'
|
||
}`}
|
||
>
|
||
🗓️ Réunion
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Liaison tâche (pour tous les types) */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-[var(--foreground)] mb-2">
|
||
Lier à une tâche (optionnel)
|
||
</label>
|
||
|
||
{selectedTask ? (
|
||
// Tâche déjà sélectionnée
|
||
<div className="border border-[var(--border)] rounded-lg p-3 bg-[var(--muted)]/30">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex-1">
|
||
<div className="font-medium text-sm">{selectedTask.title}</div>
|
||
{selectedTask.description && (
|
||
<div className="text-xs text-[var(--muted-foreground)] truncate">
|
||
{selectedTask.description}
|
||
</div>
|
||
)}
|
||
<span className={`inline-block px-1 py-0.5 rounded text-xs mt-1 ${
|
||
selectedTask.status === 'todo' ? 'bg-blue-100 text-blue-800' :
|
||
selectedTask.status === 'in_progress' ? 'bg-yellow-100 text-yellow-800' :
|
||
'bg-gray-100 text-gray-800'
|
||
}`}>
|
||
{selectedTask.status}
|
||
</span>
|
||
</div>
|
||
<Button
|
||
type="button"
|
||
onClick={() => setTaskId(undefined)}
|
||
variant="ghost"
|
||
size="sm"
|
||
className="text-[var(--destructive)] hover:bg-[var(--destructive)]/10"
|
||
disabled={saving}
|
||
>
|
||
×
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
// Interface de sélection simplifiée
|
||
<div className="space-y-2">
|
||
<Input
|
||
type="text"
|
||
placeholder="Rechercher une tâche..."
|
||
value={taskSearch}
|
||
onChange={(e) => setTaskSearch(e.target.value)}
|
||
disabled={saving || tasksLoading}
|
||
className="w-full"
|
||
/>
|
||
|
||
{taskSearch.trim() && (
|
||
<div className="border border-[var(--border)] rounded-lg max-h-40 overflow-y-auto">
|
||
{tasksLoading ? (
|
||
<div className="p-3 text-center text-sm text-[var(--muted-foreground)]">
|
||
Chargement...
|
||
</div>
|
||
) : filteredTasks.length === 0 ? (
|
||
<div className="p-3 text-center text-sm text-[var(--muted-foreground)]">
|
||
Aucune tâche trouvée
|
||
</div>
|
||
) : (
|
||
filteredTasks.slice(0, 5).map((task) => (
|
||
<button
|
||
key={task.id}
|
||
type="button"
|
||
onClick={() => handleTaskSelect(task)}
|
||
className="w-full text-left p-3 hover:bg-[var(--muted)]/50 transition-colors border-b border-[var(--border)]/30 last:border-b-0"
|
||
disabled={saving}
|
||
>
|
||
<div className="font-medium text-sm">{task.title}</div>
|
||
{task.description && (
|
||
<div className="text-xs text-[var(--muted-foreground)] truncate mt-1">
|
||
{task.description}
|
||
</div>
|
||
)}
|
||
<span className={`inline-block px-1 py-0.5 rounded text-xs mt-1 ${
|
||
task.status === 'todo' ? 'bg-blue-100 text-blue-800' :
|
||
task.status === 'in_progress' ? 'bg-yellow-100 text-yellow-800' :
|
||
'bg-gray-100 text-gray-800'
|
||
}`}>
|
||
{task.status}
|
||
</span>
|
||
</button>
|
||
))
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Actions */}
|
||
<div className="flex gap-2 justify-end pt-4">
|
||
<Button
|
||
type="button"
|
||
onClick={handleClose}
|
||
variant="ghost"
|
||
disabled={saving}
|
||
>
|
||
Annuler
|
||
</Button>
|
||
<Button
|
||
type="button"
|
||
onClick={handleSave}
|
||
variant="primary"
|
||
disabled={!text.trim() || saving}
|
||
>
|
||
{saving ? 'Sauvegarde...' : 'Sauvegarder'}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</Modal>
|
||
);
|
||
}
|