Files
towercontrol/components/daily/EditCheckboxModal.tsx
Julien Froidefond c2f949325a feat: enhance DailyCheckboxItem and EditCheckboxModal for task management
- Updated DailyCheckboxItem to display task title instead of ID, improving user clarity.
- Refactored EditCheckboxModal to load tasks dynamically, allowing for task selection with search functionality.
- Removed TaskSelector component to streamline task selection process within the modal.
- Added loading and filtering logic for tasks, enhancing user experience during task selection.
2025-09-15 22:39:58 +02:00

269 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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-100 dark:bg-green-900/40 text-green-900 dark:text-green-100 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-100 dark:bg-blue-900/40 text-blue-900 dark:text-blue-100 font-medium'
: 'border-l-blue-300 hover:border-l-blue-400 opacity-70 hover:opacity-90'
}`}
>
🗓 Réunion
</Button>
</div>
</div>
{/* Liaison tâche (seulement pour les tâches) */}
{type === 'task' && (
<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>
);
}