mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-11 17:27:31 +02:00
Expand task workflow with new status options and kanban view
Update task schemas and UI to include "blocked," "ready_to_deploy," and "deployed" statuses, and adjust the kanban view to accommodate 10 columns. Replit-Commit-Author: Agent Replit-Commit-Session-Id: d43bd811-9372-45a7-8ac9-4a954c0538e1 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/d43bd811-9372-45a7-8ac9-4a954c0538e1/aphe8bv
This commit is contained in:
1 parent
fc4a7e1d30
commit
4587e9517e
3 files changed
+57
-105
No files matched your search
@@ -31,7 +31,7 @@ const taskFormSchema = z.object({
|
||||
title: z.string().min(1, "Le titre est requis"),
|
||||
description: z.string().optional(),
|
||||
priority: z.enum(["low", "medium", "high"]).default("medium"),
|
||||
status: z.enum(["backlog", "todo", "in_progress", "review", "testing", "done", "archived"]).default("todo"),
|
||||
status: z.enum(["backlog", "todo", "in_progress", "review", "testing", "blocked", "ready_to_deploy", "deployed", "done", "archived"]).default("todo"),
|
||||
assignedTo: z.string().min(1, "L'assignation est requise"),
|
||||
dueDate: z.date().optional(),
|
||||
});
|
||||
@@ -118,7 +118,7 @@ export default function TaskForm({ task, onClose }: TaskFormProps) {
|
||||
title: task?.title || "",
|
||||
description: task?.description || "",
|
||||
priority: (task?.priority as "low" | "medium" | "high") || "medium",
|
||||
status: (task?.status as "backlog" | "todo" | "in_progress" | "review" | "testing" | "done" | "archived") || "todo",
|
||||
status: (task?.status as "backlog" | "todo" | "in_progress" | "review" | "testing" | "blocked" | "ready_to_deploy" | "deployed" | "done" | "archived") || "todo",
|
||||
assignedTo: task?.assignedTo || "",
|
||||
dueDate: task?.dueDate ? new Date(task.dueDate) : undefined,
|
||||
},
|
||||
@@ -351,6 +351,9 @@ export default function TaskForm({ task, onClose }: TaskFormProps) {
|
||||
<SelectItem value="in_progress">En cours</SelectItem>
|
||||
<SelectItem value="review">Révision</SelectItem>
|
||||
<SelectItem value="testing">Test</SelectItem>
|
||||
<SelectItem value="blocked">Bloqué</SelectItem>
|
||||
<SelectItem value="ready_to_deploy">Prêt Déploiement</SelectItem>
|
||||
<SelectItem value="deployed">Déployé</SelectItem>
|
||||
<SelectItem value="done">Terminé</SelectItem>
|
||||
<SelectItem value="archived">Archivé</SelectItem>
|
||||
</SelectContent>
|
||||
|
||||
+51
-102
@@ -499,6 +499,9 @@ export default function Tasks() {
|
||||
<SelectItem value="in_progress">En cours</SelectItem>
|
||||
<SelectItem value="review">Révision</SelectItem>
|
||||
<SelectItem value="testing">Test</SelectItem>
|
||||
<SelectItem value="blocked">Bloqué</SelectItem>
|
||||
<SelectItem value="ready_to_deploy">Prêt Déploiement</SelectItem>
|
||||
<SelectItem value="deployed">Déployé</SelectItem>
|
||||
<SelectItem value="done">Terminé</SelectItem>
|
||||
<SelectItem value="archived">Archivé</SelectItem>
|
||||
</SelectContent>
|
||||
@@ -550,9 +553,9 @@ export default function Tasks() {
|
||||
{/* Contenu selon la vue sélectionnée */}
|
||||
<Tabs value={viewMode} onValueChange={setViewMode} className="h-full">
|
||||
<TabsContent value="kanban" className="mt-0">
|
||||
{/* Vue Kanban avec 7 colonnes */}
|
||||
{/* Vue Kanban avec 10 colonnes - Workflow complet */}
|
||||
<div className="p-6">
|
||||
<div className="grid grid-cols-7 gap-3 h-full text-sm">
|
||||
<div className="grid grid-cols-10 gap-1 h-full text-xs overflow-x-auto">
|
||||
{/* Colonne Backlog */}
|
||||
<div className="bg-gray-50 rounded-lg p-3">
|
||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2 text-xs">
|
||||
@@ -606,7 +609,7 @@ export default function Tasks() {
|
||||
</div>
|
||||
|
||||
{/* Colonne Test */}
|
||||
<div className="bg-orange-50 rounded-lg p-3">
|
||||
<div className="bg-orange-50 rounded-lg p-2">
|
||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2 text-xs">
|
||||
<AlertTriangle className="w-4 h-4 text-orange-500" />
|
||||
Test ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'testing').length})
|
||||
@@ -618,8 +621,47 @@ export default function Tasks() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Colonne Bloqué */}
|
||||
<div className="bg-red-50 rounded-lg p-2">
|
||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2 text-xs">
|
||||
<Circle className="w-4 h-4 text-red-500" />
|
||||
Bloqué ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'blocked').length})
|
||||
</h3>
|
||||
<div className="space-y-2 max-h-[500px] overflow-y-auto">
|
||||
{filteredTasks.filter((task: TaskWithRelations) => task.status === 'blocked').map((task: TaskWithRelations) => {
|
||||
return renderKanbanCard(task, canEditTasks);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Colonne Prêt Déploiement */}
|
||||
<div className="bg-indigo-50 rounded-lg p-2">
|
||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2 text-xs">
|
||||
<CheckCircle className="w-4 h-4 text-indigo-500" />
|
||||
Prêt Déploiement ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'ready_to_deploy').length})
|
||||
</h3>
|
||||
<div className="space-y-2 max-h-[500px] overflow-y-auto">
|
||||
{filteredTasks.filter((task: TaskWithRelations) => task.status === 'ready_to_deploy').map((task: TaskWithRelations) => {
|
||||
return renderKanbanCard(task, canEditTasks);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Colonne Déployé */}
|
||||
<div className="bg-cyan-50 rounded-lg p-2">
|
||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2 text-xs">
|
||||
<CheckCircle className="w-4 h-4 text-cyan-500" />
|
||||
Déployé ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'deployed').length})
|
||||
</h3>
|
||||
<div className="space-y-2 max-h-[500px] overflow-y-auto">
|
||||
{filteredTasks.filter((task: TaskWithRelations) => task.status === 'deployed').map((task: TaskWithRelations) => {
|
||||
return renderKanbanCard(task, canEditTasks);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Colonne Terminé */}
|
||||
<div className="bg-green-50 rounded-lg p-3">
|
||||
<div className="bg-green-50 rounded-lg p-2">
|
||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2 text-xs">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
Terminé ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'done').length})
|
||||
@@ -632,7 +674,7 @@ export default function Tasks() {
|
||||
</div>
|
||||
|
||||
{/* Colonne Archivé */}
|
||||
<div className="bg-gray-100 rounded-lg p-3">
|
||||
<div className="bg-gray-100 rounded-lg p-2">
|
||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2 text-xs">
|
||||
<Circle className="w-4 h-4 text-gray-400" />
|
||||
Archivé ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'archived').length})
|
||||
@@ -693,7 +735,10 @@ export default function Tasks() {
|
||||
task.status === 'todo' ? 'À faire' :
|
||||
task.status === 'in_progress' ? 'En cours' :
|
||||
task.status === 'review' ? 'Révision' :
|
||||
task.status === 'testing' ? 'Test' : task.status}
|
||||
task.status === 'testing' ? 'Test' :
|
||||
task.status === 'blocked' ? 'Bloqué' :
|
||||
task.status === 'ready_to_deploy' ? 'Prêt Déploiement' :
|
||||
task.status === 'deployed' ? 'Déployé' : task.status}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
@@ -868,103 +913,7 @@ export default function Tasks() {
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="kanban" className="mt-0">
|
||||
{/* Vue Kanban */}
|
||||
<div className="p-6">
|
||||
<div className="grid grid-cols-7 gap-3 h-full text-sm">
|
||||
{/* Colonne Backlog */}
|
||||
<div className="bg-gray-50 rounded-lg p-3">
|
||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2 text-xs">
|
||||
<Circle className="w-4 h-4 text-gray-400" />
|
||||
Backlog ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'backlog').length})
|
||||
</h3>
|
||||
<div className="space-y-2 max-h-[500px] overflow-y-auto">
|
||||
{filteredTasks.filter((task: TaskWithRelations) => task.status === 'backlog').map((task: TaskWithRelations) => {
|
||||
return renderKanbanCard(task, canEditTasks);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Colonne À faire */}
|
||||
<div className="bg-blue-50 rounded-lg p-3">
|
||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2 text-xs">
|
||||
<Circle className="w-4 h-4 text-blue-500" />
|
||||
À faire ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'todo').length})
|
||||
</h3>
|
||||
<div className="space-y-2 max-h-[500px] overflow-y-auto">
|
||||
{filteredTasks.filter((task: TaskWithRelations) => task.status === 'todo').map((task: TaskWithRelations) => {
|
||||
return renderKanbanCard(task, canEditTasks);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Colonne En cours */}
|
||||
<div className="bg-yellow-50 rounded-lg p-3">
|
||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2 text-xs">
|
||||
<Clock className="w-4 h-4 text-yellow-500" />
|
||||
En cours ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'in_progress').length})
|
||||
</h3>
|
||||
<div className="space-y-2 max-h-[500px] overflow-y-auto">
|
||||
{filteredTasks.filter((task: TaskWithRelations) => task.status === 'in_progress').map((task: TaskWithRelations) => {
|
||||
return renderKanbanCard(task, canEditTasks);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Colonne Révision */}
|
||||
<div className="bg-purple-50 rounded-lg p-3">
|
||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2 text-xs">
|
||||
<Edit className="w-4 h-4 text-purple-500" />
|
||||
Révision ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'review').length})
|
||||
</h3>
|
||||
<div className="space-y-2 max-h-[500px] overflow-y-auto">
|
||||
{filteredTasks.filter((task: TaskWithRelations) => task.status === 'review').map((task: TaskWithRelations) => {
|
||||
return renderKanbanCard(task, canEditTasks);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Colonne Test */}
|
||||
<div className="bg-orange-50 rounded-lg p-3">
|
||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2 text-xs">
|
||||
<AlertTriangle className="w-4 h-4 text-orange-500" />
|
||||
Test ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'testing').length})
|
||||
</h3>
|
||||
<div className="space-y-2 max-h-[500px] overflow-y-auto">
|
||||
{filteredTasks.filter((task: TaskWithRelations) => task.status === 'testing').map((task: TaskWithRelations) => {
|
||||
return renderKanbanCard(task, canEditTasks);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Colonne Terminé */}
|
||||
<div className="bg-green-50 rounded-lg p-3">
|
||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2 text-xs">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
Terminé ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'done').length})
|
||||
</h3>
|
||||
<div className="space-y-2 max-h-[500px] overflow-y-auto">
|
||||
{filteredTasks.filter((task: TaskWithRelations) => task.status === 'done').map((task: TaskWithRelations) => {
|
||||
return renderKanbanCard(task, canEditTasks, true);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Colonne Archivé */}
|
||||
<div className="bg-gray-100 rounded-lg p-3">
|
||||
<h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2 text-xs">
|
||||
<Circle className="w-4 h-4 text-gray-400" />
|
||||
Archivé ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'archived').length})
|
||||
</h3>
|
||||
<div className="space-y-2 max-h-[500px] overflow-y-auto">
|
||||
{filteredTasks.filter((task: TaskWithRelations) => task.status === 'archived').map((task: TaskWithRelations) => {
|
||||
return renderKanbanCard(task, canEditTasks, true);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -245,7 +245,7 @@ export const tasks = pgTable("tasks", {
|
||||
description: text("description"), // Description de la tâche (optionnel)
|
||||
dueDate: timestamp("due_date"), // Date d'échéance (optionnel)
|
||||
priority: varchar("priority").notNull().default("medium"), // low, medium, high
|
||||
status: varchar("status").notNull().default("todo"), // backlog, todo, in_progress, review, testing, done, archived
|
||||
status: varchar("status").notNull().default("todo"), // backlog, todo, in_progress, review, testing, blocked, ready_to_deploy, deployed, done, archived
|
||||
assignedTo: text("assigned_to").notNull(), // Utilisateur responsable (champ libre)
|
||||
createdBy: varchar("created_by").notNull(), // Utilisateur créateur
|
||||
groupId: integer("group_id").notNull(), // Magasin/groupe associé
|
||||
|
||||
Reference in new issue
Block a user