Add inline task creation in étapes view
- Each étape shows an "Ajouter une tâche" button at the bottom when expanded - Clicking opens an inline text input with Enter to submit, Escape to cancel - Created tasks are linked to the étape via parent_task_id - Purple "Ajouter" button with instant feedback - Uses existing useCreateTask hook with correct tablo_id and parent_task_id Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3f97478977
commit
bbf9d695ba
1 changed files with 101 additions and 34 deletions
|
|
@ -29,7 +29,7 @@ import { TabloDiscussionSection } from "../components/TabloDiscussionSection";
|
|||
import { TabloEventsSection } from "../components/TabloEventsSection";
|
||||
import { TabloFilesSection } from "../components/TabloFilesSection";
|
||||
import { TabloTasksSection } from "../components/TabloTasksSection";
|
||||
import { useAllTasks, useTabloEtapes } from "../hooks/tasks";
|
||||
import { useAllTasks, useCreateTask, useTabloEtapes } from "../hooks/tasks";
|
||||
import { useTabloFileNames } from "../hooks/tablo_data";
|
||||
import { useTablosList } from "../hooks/tablos";
|
||||
|
||||
|
|
@ -459,7 +459,7 @@ export const TabloDetailsPage = () => {
|
|||
)}
|
||||
|
||||
{activeSection === "etapes" && (
|
||||
<EtapesSection etapes={etapes} tabloTasks={tabloTasks} />
|
||||
<EtapesSection etapes={etapes} tabloTasks={tabloTasks} tabloId={tabloId ?? ""} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -471,13 +471,18 @@ export const TabloDetailsPage = () => {
|
|||
function EtapesSection({
|
||||
etapes,
|
||||
tabloTasks,
|
||||
tabloId,
|
||||
}: {
|
||||
etapes: Etape[];
|
||||
tabloTasks: KanbanTask[];
|
||||
tabloId: string;
|
||||
}) {
|
||||
const [expandedEtapes, setExpandedEtapes] = useState<Set<string>>(
|
||||
new Set(etapes.map((e) => e.id)),
|
||||
);
|
||||
const [addingTaskToEtape, setAddingTaskToEtape] = useState<string | null>(null);
|
||||
const [newTaskTitle, setNewTaskTitle] = useState("");
|
||||
const { mutate: createTask } = useCreateTask();
|
||||
|
||||
const toggleEtape = (id: string) => {
|
||||
setExpandedEtapes((prev) => {
|
||||
|
|
@ -488,6 +493,21 @@ function EtapesSection({
|
|||
});
|
||||
};
|
||||
|
||||
const handleAddTask = (etapeId: string) => {
|
||||
const title = newTaskTitle.trim();
|
||||
if (!title || !tabloId) return;
|
||||
createTask({
|
||||
tablo_id: tabloId,
|
||||
title,
|
||||
status: "todo",
|
||||
parent_task_id: etapeId,
|
||||
is_parent: false,
|
||||
position: tabloTasks.filter((t) => t.parent_task_id === etapeId).length,
|
||||
});
|
||||
setNewTaskTitle("");
|
||||
setAddingTaskToEtape(null);
|
||||
};
|
||||
|
||||
const statusConfig: Record<string, { label: string; color: string }> = {
|
||||
todo: { label: "À faire", color: "bg-blue-100 text-blue-700 dark:bg-blue-950/30 dark:text-blue-400" },
|
||||
in_progress: { label: "En cours", color: "bg-yellow-100 text-yellow-700 dark:bg-yellow-950/30 dark:text-yellow-400" },
|
||||
|
|
@ -575,40 +595,87 @@ function EtapesSection({
|
|||
)}
|
||||
</button>
|
||||
|
||||
{/* Child tasks */}
|
||||
{isExpanded && childTasks.length > 0 && (
|
||||
<div className="border-t border-gray-100 dark:border-gray-700 divide-y divide-gray-100 dark:divide-gray-700">
|
||||
{childTasks.map((task) => (
|
||||
<div
|
||||
key={task.id}
|
||||
className="flex items-center gap-3 px-5 py-3 pl-16 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
{task.status === "done" ? (
|
||||
<CircleCheckIcon className="w-4 h-4 text-green-500 shrink-0" />
|
||||
) : (
|
||||
<div className="w-4 h-4 rounded-full border-2 border-gray-300 dark:border-gray-600 shrink-0" />
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm flex-1 truncate",
|
||||
task.status === "done" ? "line-through text-gray-400" : "text-gray-900 dark:text-gray-100",
|
||||
)}
|
||||
>
|
||||
{task.title}
|
||||
</span>
|
||||
{task.status && (
|
||||
<span className={cn("px-2 py-0.5 rounded-full text-[10px] font-medium shrink-0", (statusConfig[task.status] ?? statusConfig.todo).color)}>
|
||||
{(statusConfig[task.status] ?? statusConfig.todo).label}
|
||||
</span>
|
||||
)}
|
||||
{/* Child tasks + add task */}
|
||||
{isExpanded && (
|
||||
<div className="border-t border-gray-100 dark:border-gray-700">
|
||||
{childTasks.length > 0 && (
|
||||
<div className="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
{childTasks.map((task) => (
|
||||
<div
|
||||
key={task.id}
|
||||
className="flex items-center gap-3 px-5 py-3 pl-16 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
{task.status === "done" ? (
|
||||
<CircleCheckIcon className="w-4 h-4 text-green-500 shrink-0" />
|
||||
) : (
|
||||
<div className="w-4 h-4 rounded-full border-2 border-gray-300 dark:border-gray-600 shrink-0" />
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm flex-1 truncate",
|
||||
task.status === "done" ? "line-through text-gray-400" : "text-gray-900 dark:text-gray-100",
|
||||
)}
|
||||
>
|
||||
{task.title}
|
||||
</span>
|
||||
{task.status && (
|
||||
<span className={cn("px-2 py-0.5 rounded-full text-[10px] font-medium shrink-0", (statusConfig[task.status] ?? statusConfig.todo).color)}>
|
||||
{(statusConfig[task.status] ?? statusConfig.todo).label}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{isExpanded && childTasks.length === 0 && (
|
||||
<div className="border-t border-gray-100 dark:border-gray-700 px-5 py-4 pl-16 text-sm text-muted-foreground">
|
||||
Aucune tâche dans cette étape
|
||||
{childTasks.length === 0 && addingTaskToEtape !== etape.id && (
|
||||
<div className="px-5 py-4 pl-16 text-sm text-muted-foreground">
|
||||
Aucune tâche dans cette étape
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Inline add task */}
|
||||
{addingTaskToEtape === etape.id ? (
|
||||
<div className="flex items-center gap-2 px-5 py-3 pl-16 border-t border-gray-100 dark:border-gray-700">
|
||||
<div className="w-4 h-4 rounded-full border-2 border-gray-300 dark:border-gray-600 shrink-0" />
|
||||
<input
|
||||
autoFocus
|
||||
type="text"
|
||||
value={newTaskTitle}
|
||||
onChange={(e) => setNewTaskTitle(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") handleAddTask(etape.id);
|
||||
if (e.key === "Escape") { setAddingTaskToEtape(null); setNewTaskTitle(""); }
|
||||
}}
|
||||
placeholder="Nom de la tâche..."
|
||||
className="flex-1 text-sm bg-transparent border-none outline-none text-gray-900 dark:text-gray-100 placeholder-gray-400"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleAddTask(etape.id)}
|
||||
disabled={!newTaskTitle.trim()}
|
||||
className="text-xs font-medium px-3 py-1 rounded-md bg-[#804EEC] text-white hover:bg-[#6f3fd4] disabled:opacity-40 transition-colors"
|
||||
>
|
||||
Ajouter
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setAddingTaskToEtape(null); setNewTaskTitle(""); }}
|
||||
className="text-xs text-muted-foreground hover:text-foreground px-2 py-1"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => { e.stopPropagation(); setAddingTaskToEtape(etape.id); setNewTaskTitle(""); }}
|
||||
className="flex items-center gap-2 px-5 py-2.5 pl-16 text-sm text-muted-foreground hover:text-[#804EEC] hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors w-full text-left border-t border-gray-100 dark:border-gray-700"
|
||||
>
|
||||
<PlusIcon className="w-4 h-4" />
|
||||
Ajouter une tâche
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue