- 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.
184 lines
5.6 KiB
TypeScript
184 lines
5.6 KiB
TypeScript
'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>
|
||
);
|
||
}
|