mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-14 10:49:10 +02:00
Enhance task management with due dates and filtering
Integrates calendar functionality for task due dates, adds due date filtering options (today, this week, overdue, no due date), and introduces visual indicators for task urgency and status. Also includes a Kanban view toggle for tasks. 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/g7eox7N
This commit is contained in:
1 parent
7ff6a493fb
commit
1e8d8f8d19
3 files changed
+379
-53
No files matched your search
@@ -9,11 +9,16 @@ import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import { apiRequest } from "@/lib/queryClient";
|
||||
import { insertTaskSchema, Task, Group } from "@shared/schema";
|
||||
import { z } from "zod";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
import { fr } from "date-fns/locale";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type TaskWithRelations = Task & {
|
||||
assignedUser: { id: string; username: string; firstName?: string; lastName?: string; };
|
||||
@@ -21,15 +26,14 @@ type TaskWithRelations = Task & {
|
||||
group: { id: number; name: string; color: string; };
|
||||
};
|
||||
|
||||
// Schéma simplifié sans magasin et sans date d'échéance
|
||||
const taskFormSchema = insertTaskSchema.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
completedAt: true,
|
||||
createdBy: true,
|
||||
groupId: true,
|
||||
dueDate: true, // Suppression du champ échéance
|
||||
// Schéma avec date d'échéance incluse
|
||||
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(["pending", "completed"]).default("pending"),
|
||||
assignedTo: z.string().min(1, "L'assignation est requise"),
|
||||
dueDate: z.date().optional(),
|
||||
});
|
||||
|
||||
type TaskFormData = z.infer<typeof taskFormSchema>;
|
||||
@@ -113,10 +117,10 @@ export default function TaskForm({ task, onClose }: TaskFormProps) {
|
||||
defaultValues: {
|
||||
title: task?.title || "",
|
||||
description: task?.description || "",
|
||||
priority: task?.priority || "medium",
|
||||
status: task?.status || "pending",
|
||||
priority: (task?.priority as "low" | "medium" | "high") || "medium",
|
||||
status: (task?.status as "pending" | "completed") || "pending",
|
||||
assignedTo: task?.assignedTo || "",
|
||||
|
||||
dueDate: task?.dueDate ? new Date(task.dueDate) : undefined,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -190,6 +194,7 @@ export default function TaskForm({ task, onClose }: TaskFormProps) {
|
||||
priority: data.priority,
|
||||
status: data.status,
|
||||
assignedTo: data.assignedTo,
|
||||
dueDate: data.dueDate?.toISOString(),
|
||||
};
|
||||
updateMutation.mutate(submitData);
|
||||
} else {
|
||||
@@ -217,7 +222,7 @@ export default function TaskForm({ task, onClose }: TaskFormProps) {
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<form onSubmit={form.handleSubmit(onSubmit as any)} className="space-y-6">
|
||||
{/* Affichage du magasin auto-sélectionné */}
|
||||
{localSelectedStoreId && (
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-md p-3">
|
||||
@@ -236,7 +241,7 @@ export default function TaskForm({ task, onClose }: TaskFormProps) {
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<div
|
||||
className="w-3 h-3 rounded-full"
|
||||
style={{ backgroundColor: selectedGroup.color }}
|
||||
style={{ backgroundColor: selectedGroup.color || "#1976D2" }}
|
||||
/>
|
||||
{selectedGroup.name}
|
||||
</span>
|
||||
@@ -369,7 +374,49 @@ export default function TaskForm({ task, onClose }: TaskFormProps) {
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
{/* Date d'échéance */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dueDate"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Date d'échéance (optionnel)</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-full pl-3 text-left font-normal",
|
||||
!field.value && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{field.value ? (
|
||||
format(field.value, "PPP", { locale: fr })
|
||||
) : (
|
||||
<span>Sélectionner une date</span>
|
||||
)}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={field.onChange}
|
||||
disabled={(date) =>
|
||||
date < new Date(new Date().setHours(0, 0, 0, 0))
|
||||
}
|
||||
initialFocus
|
||||
locale={fr}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Boutons d'action */}
|
||||
<div className="flex justify-end space-x-3 pt-6">
|
||||
|
||||
+315
-36
@@ -10,6 +10,7 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { Pagination, usePagination } from "@/components/ui/pagination";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import {
|
||||
ListTodo,
|
||||
Plus,
|
||||
@@ -19,9 +20,13 @@ import {
|
||||
AlertTriangle,
|
||||
CheckCircle,
|
||||
Edit,
|
||||
Trash2
|
||||
Trash2,
|
||||
Calendar,
|
||||
CalendarX,
|
||||
CalendarClock,
|
||||
Kanban
|
||||
} from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
import { format, isToday, isThisWeek, isPast, differenceInDays } from "date-fns";
|
||||
import { fr } from "date-fns/locale";
|
||||
import TaskForm from "@/components/tasks/TaskForm";
|
||||
import { Task } from "@shared/schema";
|
||||
@@ -42,6 +47,8 @@ export default function Tasks() {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [statusFilter, setStatusFilter] = useState<string>("all");
|
||||
const [priorityFilter, setPriorityFilter] = useState<string>("all");
|
||||
const [dueDateFilter, setDueDateFilter] = useState<string>("all");
|
||||
const [viewMode, setViewMode] = useState<string>("list");
|
||||
|
||||
// Modals
|
||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||
@@ -176,6 +183,27 @@ export default function Tasks() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filtre par date d'échéance
|
||||
if (dueDateFilter !== "all" && task.dueDate) {
|
||||
const dueDate = new Date(task.dueDate);
|
||||
switch (dueDateFilter) {
|
||||
case "today":
|
||||
if (!isToday(dueDate)) return false;
|
||||
break;
|
||||
case "this_week":
|
||||
if (!isThisWeek(dueDate)) return false;
|
||||
break;
|
||||
case "overdue":
|
||||
if (!isPast(dueDate) || task.status === 'completed') return false;
|
||||
break;
|
||||
case "no_due_date":
|
||||
if (task.dueDate) return false;
|
||||
break;
|
||||
}
|
||||
} else if (dueDateFilter === "no_due_date" && task.dueDate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.sort((a: TaskWithRelations, b: TaskWithRelations) => {
|
||||
@@ -238,6 +266,48 @@ export default function Tasks() {
|
||||
}
|
||||
};
|
||||
|
||||
const getDueDateStatus = (dueDate: string | null, status: string) => {
|
||||
if (!dueDate || status === 'completed') return null;
|
||||
|
||||
const due = new Date(dueDate);
|
||||
const now = new Date();
|
||||
const daysDiff = differenceInDays(due, now);
|
||||
|
||||
if (isPast(due)) {
|
||||
return {
|
||||
type: 'overdue',
|
||||
color: 'destructive' as const,
|
||||
icon: CalendarX,
|
||||
label: `En retard de ${Math.abs(daysDiff)} jour${Math.abs(daysDiff) > 1 ? 's' : ''}`,
|
||||
text: 'En retard'
|
||||
};
|
||||
} else if (isToday(due)) {
|
||||
return {
|
||||
type: 'today',
|
||||
color: 'secondary' as const,
|
||||
icon: CalendarClock,
|
||||
label: "Échéance aujourd'hui",
|
||||
text: "Aujourd'hui"
|
||||
};
|
||||
} else if (daysDiff <= 3) {
|
||||
return {
|
||||
type: 'soon',
|
||||
color: 'default' as const,
|
||||
icon: Calendar,
|
||||
label: `Dans ${daysDiff} jour${daysDiff > 1 ? 's' : ''}`,
|
||||
text: `${daysDiff}j`
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'normal',
|
||||
color: 'outline' as const,
|
||||
icon: Calendar,
|
||||
label: format(due, 'dd/MM/yyyy', { locale: fr }),
|
||||
text: format(due, 'dd/MM', { locale: fr })
|
||||
};
|
||||
};
|
||||
|
||||
const canCreateTasks = user?.role === 'admin' || user?.role === 'manager' || user?.role === 'directeur';
|
||||
const canEditTasks = user?.role === 'admin' || user?.role === 'manager' || user?.role === 'directeur';
|
||||
|
||||
@@ -263,24 +333,40 @@ export default function Tasks() {
|
||||
{totalItems} tâche{totalItems !== 1 ? 's' : ''} trouvée{totalItems !== 1 ? 's' : ''}
|
||||
</p>
|
||||
</div>
|
||||
{canCreateTasks && (
|
||||
<Dialog open={showCreateModal} onOpenChange={setShowCreateModal}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="bg-blue-600 hover:bg-blue-700 text-white shadow-md">
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Nouvelle Tâche
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Créer une nouvelle tâche</DialogTitle>
|
||||
</DialogHeader>
|
||||
<TaskForm
|
||||
onClose={() => setShowCreateModal(false)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Sélecteur de vue */}
|
||||
<Tabs value={viewMode} onValueChange={setViewMode} className="w-auto">
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="list" className="flex items-center gap-2">
|
||||
<ListTodo className="w-4 h-4" />
|
||||
Liste
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="kanban" className="flex items-center gap-2">
|
||||
<Kanban className="w-4 h-4" />
|
||||
Kanban
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
{canCreateTasks && (
|
||||
<Dialog open={showCreateModal} onOpenChange={setShowCreateModal}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="bg-blue-600 hover:bg-blue-700 text-white shadow-md">
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Nouvelle Tâche
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Créer une nouvelle tâche</DialogTitle>
|
||||
</DialogHeader>
|
||||
<TaskForm
|
||||
onClose={() => setShowCreateModal(false)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -338,14 +424,36 @@ export default function Tasks() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Filtre par échéance */}
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-700 mb-2 block">
|
||||
Échéance
|
||||
</label>
|
||||
<Select value={dueDateFilter} onValueChange={setDueDateFilter}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Toutes</SelectItem>
|
||||
<SelectItem value="today">Aujourd'hui</SelectItem>
|
||||
<SelectItem value="this_week">Cette semaine</SelectItem>
|
||||
<SelectItem value="overdue">En retard</SelectItem>
|
||||
<SelectItem value="no_due_date">Sans échéance</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Zone principale avec les tâches */}
|
||||
<div className="flex-1">
|
||||
{/* Liste des tâches */}
|
||||
<div className="p-6">
|
||||
{/* Contenu selon la vue sélectionnée */}
|
||||
<Tabs value={viewMode} onValueChange={setViewMode} className="h-full">
|
||||
<TabsContent value="list" className="mt-0">
|
||||
{/* Liste des tâches */}
|
||||
<div className="p-6">
|
||||
{totalItems === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<ListTodo className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
||||
@@ -392,7 +500,7 @@ export default function Tasks() {
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-4 text-xs text-gray-500">
|
||||
<div className="flex items-center gap-4 text-xs text-gray-500 mb-2">
|
||||
<span>
|
||||
Assigné à: {task.assignedTo}
|
||||
</span>
|
||||
@@ -400,6 +508,33 @@ export default function Tasks() {
|
||||
Créée: {format(new Date(task.createdAt), 'dd/MM/yyyy HH:mm', { locale: fr })}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Affichage de l'échéance */}
|
||||
{(() => {
|
||||
const dueDateStatus = getDueDateStatus(task.dueDate, task.status);
|
||||
if (dueDateStatus) {
|
||||
const DueDateIcon = dueDateStatus.icon;
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant={dueDateStatus.color} className="flex items-center gap-1 text-xs">
|
||||
<DueDateIcon className="w-3 h-3" />
|
||||
{dueDateStatus.text}
|
||||
</Badge>
|
||||
<span className="text-xs text-gray-500">{dueDateStatus.label}</span>
|
||||
</div>
|
||||
);
|
||||
} else if (task.dueDate) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline" className="flex items-center gap-1 text-xs">
|
||||
<Calendar className="w-3 h-3" />
|
||||
{format(new Date(task.dueDate), 'dd/MM', { locale: fr })}
|
||||
</Badge>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})()}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 ml-4">
|
||||
@@ -512,19 +647,163 @@ export default function Tasks() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Pagination */}
|
||||
{totalItems > 0 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
totalItems={totalItems}
|
||||
itemsPerPage={itemsPerPage}
|
||||
onPageChange={setCurrentPage}
|
||||
onItemsPerPageChange={setItemsPerPage}
|
||||
className="mt-4 border-t border-gray-200 pt-4 mx-6"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{/* Pagination */}
|
||||
{totalItems > 0 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
totalItems={totalItems}
|
||||
itemsPerPage={itemsPerPage}
|
||||
onPageChange={setCurrentPage}
|
||||
onItemsPerPageChange={setItemsPerPage}
|
||||
className="mt-4 border-t border-gray-200 pt-4 mx-6"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="kanban" className="mt-0">
|
||||
{/* Vue Kanban */}
|
||||
<div className="p-6">
|
||||
<div className="grid grid-cols-2 gap-6 h-full">
|
||||
{/* Colonne En cours */}
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<h3 className="font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
||||
<Circle className="w-5 h-5 text-yellow-500" />
|
||||
En cours ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'pending').length})
|
||||
</h3>
|
||||
<div className="space-y-3 max-h-[600px] overflow-y-auto">
|
||||
{filteredTasks.filter((task: TaskWithRelations) => task.status === 'pending').map((task: TaskWithRelations) => {
|
||||
const priorityConfig = getPriorityConfig(task.priority);
|
||||
const PriorityIcon = priorityConfig.icon;
|
||||
const dueDateStatus = getDueDateStatus(task.dueDate, task.status);
|
||||
const DueDateIcon = dueDateStatus?.icon;
|
||||
|
||||
return (
|
||||
<Card key={task.id} className="hover:shadow-md transition-shadow cursor-pointer">
|
||||
<CardContent className="p-3">
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<h5 className="font-medium text-gray-900 text-sm">{task.title}</h5>
|
||||
<Badge variant={priorityConfig.color} className="flex items-center gap-1 text-xs">
|
||||
<PriorityIcon className="w-3 h-3" />
|
||||
{priorityConfig.label}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{task.description && (
|
||||
<p className="text-xs text-gray-600 mb-2 line-clamp-2">
|
||||
{task.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="text-xs text-gray-500 mb-2">
|
||||
Assigné à: {task.assignedTo}
|
||||
</div>
|
||||
|
||||
{dueDateStatus && DueDateIcon && (
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Badge variant={dueDateStatus.color} className="flex items-center gap-1 text-xs">
|
||||
<DueDateIcon className="w-3 h-3" />
|
||||
{dueDateStatus.text}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-1 mt-2">
|
||||
{canEditTasks && (
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleCompleteTask(task.id)}
|
||||
className="text-green-600 hover:text-green-700 text-xs h-7"
|
||||
>
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleEditTask(task)}
|
||||
className="text-xs h-7"
|
||||
>
|
||||
<Edit className="w-3 h-3" />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleDeleteClick(task)}
|
||||
className="text-red-600 hover:text-red-700 text-xs h-7"
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Colonne Terminées */}
|
||||
<div className="bg-green-50 rounded-lg p-4">
|
||||
<h3 className="font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
||||
<CheckCircle className="w-5 h-5 text-green-500" />
|
||||
Terminées ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'completed').length})
|
||||
</h3>
|
||||
<div className="space-y-3 max-h-[600px] overflow-y-auto">
|
||||
{filteredTasks.filter((task: TaskWithRelations) => task.status === 'completed').map((task: TaskWithRelations) => {
|
||||
const priorityConfig = getPriorityConfig(task.priority);
|
||||
const PriorityIcon = priorityConfig.icon;
|
||||
|
||||
return (
|
||||
<Card key={task.id} className="hover:shadow-md transition-shadow cursor-pointer opacity-75">
|
||||
<CardContent className="p-3">
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<h5 className="font-medium text-gray-900 text-sm line-through">{task.title}</h5>
|
||||
<Badge variant={priorityConfig.color} className="flex items-center gap-1 text-xs">
|
||||
<PriorityIcon className="w-3 h-3" />
|
||||
{priorityConfig.label}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-gray-500">
|
||||
Terminée le: {task.completedAt ? format(new Date(task.completedAt), 'dd/MM/yyyy', { locale: fr }) : 'Date inconnue'}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1 mt-2">
|
||||
{canEditTasks && (
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleEditTask(task)}
|
||||
className="text-xs h-7"
|
||||
>
|
||||
<Edit className="w-3 h-3" />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleDeleteClick(task)}
|
||||
className="text-red-600 hover:text-red-700 text-xs h-7"
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in new issue
Block a user