Files
towercontrol/src/components/daily/EditCheckboxModal.tsx
Julien Froidefond 34f1a62435 feat: replace Input with DateTimeInput component in forms and modals
- Updated CreateTaskForm, TaskBasicFields, and EditCheckboxModal to use DateTimeInput for date selection, enhancing consistency and user experience.
- Improved UI by integrating lucide-react Calendar icon in DateTimeInput for better visual feedback.
- Marked EditModal task color issue as complete in TODO.md.
2025-10-04 10:47:27 +02:00

293 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
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 { CheckSquare2, Calendar } from 'lucide-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 { TagDisplay } from '@/components/ui/TagDisplay';
import { Card } from '@/components/ui/Card';
import { SearchInput } from '@/components/ui/SearchInput';
import { StatusBadge } from '@/components/ui/StatusBadge';
import { ToggleButton } from '@/components/ui/ToggleButton';
import { DateTimeInput } from '@/components/ui/DateTimeInput';
import { tasksClient } from '@/clients/tasks-client';
interface EditCheckboxModalProps {
checkbox: DailyCheckbox;
isOpen: boolean;
onClose: () => void;
onSave: (text: string, type: DailyCheckboxType, taskId?: string, date?: Date) => 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 [date, setDate] = useState<Date>(checkbox.date);
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 et exclure les tâches avec des tags "objectif principal"
const filteredTasks = allTasks.filter(task => {
// Exclure les tâches avec des tags marqués comme "objectif principal" (isPinned = true)
if (task.tagDetails && task.tagDetails.some(tag => tag.isPinned)) {
return false;
}
// Filtrer selon la recherche
return 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, date);
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);
setDate(checkbox.date);
};
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">
<ToggleButton
onClick={() => setType('meeting')}
variant="primary"
size="sm"
isActive={type === 'meeting'}
icon={<Calendar size={14} />}
>
<span className="text-sm">Réunion</span>
</ToggleButton>
<ToggleButton
onClick={() => setType('task')}
variant="primary"
size="sm"
isActive={type === 'task'}
icon={<CheckSquare2 size={14} />}
>
<span className="text-sm">Tâche</span>
</ToggleButton>
</div>
</div>
{/* Date/Heure */}
<div>
<label className="block text-sm font-medium text-[var(--foreground)] mb-2">
Date/Heure
</label>
<DateTimeInput
value={date}
onChange={(newDate) => newDate && setDate(newDate)}
disabled={saving}
/>
</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
<Card className="p-3" background="muted">
<div className="flex items-center justify-between">
<div className="flex-1 min-w-0">
<div className="font-medium text-sm truncate">{selectedTask.title}</div>
{selectedTask.description && (
<div className="text-xs text-[var(--muted-foreground)] truncate">
{selectedTask.description}
</div>
)}
<div className="flex items-center gap-2 mt-1">
<StatusBadge status={selectedTask.status} />
{selectedTask.tags && selectedTask.tags.length > 0 && (
<TagDisplay
tags={selectedTask.tags}
size="sm"
availableTags={selectedTask.tagDetails}
maxTags={3}
/>
)}
</div>
</div>
<Button
type="button"
onClick={() => setTaskId(undefined)}
variant="ghost"
size="sm"
className="text-[var(--destructive)] hover:bg-[var(--destructive)]/10"
disabled={saving}
>
×
</Button>
</div>
</Card>
) : (
// Interface de sélection simplifiée
<div className="space-y-2">
<SearchInput
value={taskSearch}
onChange={setTaskSearch}
placeholder="Rechercher une tâche..."
disabled={saving || tasksLoading}
className="w-full"
/>
{taskSearch.trim() && (
<Card className="max-h-40 overflow-y-auto" shadow="sm">
{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 truncate">{task.title}</div>
{task.description && (
<div className="text-xs text-[var(--muted-foreground)] truncate mt-1 max-w-full overflow-hidden">
{task.description}
</div>
)}
<div className="flex items-center gap-2 mt-1">
<StatusBadge status={task.status} />
{task.tags && task.tags.length > 0 && (
<TagDisplay
tags={task.tags}
size="sm"
availableTags={task.tagDetails}
maxTags={3}
/>
)}
</div>
</button>
))
)}
</Card>
)}
</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>
);
}