Files
towercontrol/components/daily/TaskSelector.tsx
Julien Froidefond adfef551ab feat: enhance DailyCheckbox model and service for type management
- Added `DailyCheckboxType` to define checkbox types ('task' | 'meeting') in TypeScript.
- Updated `DailyCheckbox` model in Prisma schema to include `type` field with a default value of 'task'.
- Modified `DailyService` to handle checkbox type during creation and updates.
- Adjusted API route to accept checkbox type in requests.
- Refactored `DailyPageClient` to support type management in checkbox operations.
2025-09-15 22:16:34 +02:00

184 lines
5.6 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 { Task } from '@/lib/types';
import { tasksClient } from '@/clients/tasks-client';
import { Button } from '@/components/ui/Button';
interface TaskSelectorProps {
selectedTaskId?: string;
onTaskSelect: (taskId: string | undefined) => void;
disabled?: boolean;
}
export function TaskSelector({ selectedTaskId, onTaskSelect, disabled }: TaskSelectorProps) {
const [tasks, setTasks] = useState<Task[]>([]);
const [loading, setLoading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [search, setSearch] = useState('');
const selectedTask = tasks.find(task => task.id === selectedTaskId);
useEffect(() => {
if (isOpen && tasks.length === 0) {
loadTasks();
}
}, [isOpen]);
const loadTasks = async () => {
setLoading(true);
try {
const response = await tasksClient.getTasks({
status: ['todo', 'in_progress', 'backlog'],
limit: 100
});
setTasks(response.data);
} catch (error) {
console.error('Erreur lors du chargement des tâches:', error);
} finally {
setLoading(false);
}
};
const filteredTasks = tasks.filter(task =>
task.title.toLowerCase().includes(search.toLowerCase()) ||
task.description?.toLowerCase().includes(search.toLowerCase())
);
const handleTaskSelect = (taskId: string) => {
onTaskSelect(taskId);
setIsOpen(false);
setSearch('');
};
const handleClear = () => {
onTaskSelect(undefined);
setIsOpen(false);
setSearch('');
};
if (!isOpen) {
return (
<div className="flex gap-1">
<Button
type="button"
onClick={() => setIsOpen(true)}
disabled={disabled}
variant="ghost"
size="sm"
className="text-xs px-2 py-1 h-6"
title="Lier à une tâche"
>
{selectedTask ? `#${selectedTask.id.slice(-6)}` : '🔗'}
</Button>
{selectedTask && (
<Button
type="button"
onClick={handleClear}
disabled={disabled}
variant="ghost"
size="sm"
className="text-xs px-1 py-1 h-6 text-[var(--destructive)]"
title="Délier"
>
×
</Button>
)}
</div>
);
}
return (
<div className="relative">
<div className="absolute bottom-full mb-2 right-0 bg-[var(--card)] border border-[var(--border)] rounded-lg shadow-lg z-10 min-w-[300px] max-w-[400px]">
<div className="p-3">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium text-[var(--foreground)]">Lier à une tâche</h3>
<Button
type="button"
onClick={() => setIsOpen(false)}
variant="ghost"
size="sm"
className="text-xs px-1 py-1 h-6"
>
×
</Button>
</div>
<input
type="text"
placeholder="Rechercher une tâche..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full mb-2 px-2 py-1 text-xs border border-[var(--border)] rounded bg-[var(--background)] text-[var(--foreground)]"
autoFocus
/>
<div className="max-h-32 overflow-y-auto space-y-1">
{loading ? (
<div className="text-xs text-[var(--muted-foreground)] text-center py-2">
Chargement...
</div>
) : filteredTasks.length === 0 ? (
<div className="text-xs text-[var(--muted-foreground)] text-center py-2">
Aucune tâche trouvée
</div>
) : (
filteredTasks.map((task) => (
<button
key={task.id}
type="button"
onClick={() => handleTaskSelect(task.id)}
className="w-full text-left p-2 rounded text-xs hover:bg-[var(--muted)] transition-colors"
>
<div className="font-medium text-[var(--foreground)] truncate">
{task.title}
</div>
{task.description && (
<div className="text-[var(--muted-foreground)] truncate">
{task.description}
</div>
)}
<div className="flex items-center gap-2 mt-1">
<span className={`px-1 py-0.5 rounded text-xs ${
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>
<span className="text-[var(--muted-foreground)]">
#{task.id.slice(-6)}
</span>
</div>
</button>
))
)}
</div>
<div className="flex gap-2 mt-2 pt-2 border-t border-[var(--border)]">
<Button
type="button"
onClick={handleClear}
variant="ghost"
size="sm"
className="text-xs flex-1"
>
Aucune tâche
</Button>
<Button
type="button"
onClick={() => setIsOpen(false)}
variant="ghost"
size="sm"
className="text-xs flex-1"
>
Annuler
</Button>
</div>
</div>
</div>
</div>
);
}