Add edit and delete functionality for task management entries

Update the Avoirs page to display entries in a table format, including edit and delete icons for administrative users.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 9fcf4b21-eb0c-4e53-a567-e2ce4a6ad869
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/9fcf4b21-eb0c-4e53-a567-e2ce4a6ad869/5h3gCuD
This commit is contained in:
michaelschal committed 2025-09-08 15:25:34 +00:00
1 parent b3eb0644e0
commit 3ae1dc31fe
1 file changed
+126 -106
+126 -106
View File
@@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Plus, Search, FileText, CheckCircle, AlertCircle, Clock } from "lucide-react"; import { Plus, Search, FileText, CheckCircle, AlertCircle, Clock, Edit, Trash2 } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
@@ -418,112 +418,132 @@ export default function Avoirs() {
)} )}
</div> </div>
{/* Avoirs List */} {/* Avoirs List - Format Table */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> <div className="bg-white rounded-lg shadow overflow-hidden">
{isLoading ? ( <div className="overflow-x-auto">
Array.from({ length: 6 }).map((_, i) => ( {isLoading ? (
<Card key={i} className="animate-pulse"> <div className="p-4">
<CardHeader> <div className="animate-pulse space-y-4">
<div className="h-4 bg-gray-200 rounded w-3/4"></div> {Array.from({ length: 3 }).map((_, i) => (
<div className="h-3 bg-gray-200 rounded w-1/2"></div> <div key={i} className="h-16 bg-gray-200 rounded"></div>
</CardHeader> ))}
<CardContent> </div>
<div className="space-y-2"> </div>
<div className="h-3 bg-gray-200 rounded"></div> ) : filteredAvoirs.length === 0 ? (
<div className="h-3 bg-gray-200 rounded w-4/5"></div> <div className="p-12 text-center">
</div> <FileText className="mx-auto h-12 w-12 text-gray-400" />
</CardContent> <h3 className="mt-4 text-sm font-medium text-gray-900">Aucun avoir</h3>
</Card> <p className="mt-1 text-sm text-gray-500">
)) {searchTerm ? "Aucun avoir ne correspond à votre recherche." : "Commencez par créer votre premier avoir."}
) : filteredAvoirs.length === 0 ? ( </p>
<div className="col-span-full text-center py-12"> {canCreate && !searchTerm && (
<FileText className="mx-auto h-12 w-12 text-gray-400" /> <Button className="mt-4" onClick={() => setIsCreateDialogOpen(true)}>
<h3 className="mt-2 text-sm font-medium text-gray-900">Aucun avoir</h3> <Plus className="h-4 w-4 mr-2" />
<p className="mt-1 text-sm text-gray-500"> Créer un avoir
Commencez par créer votre premier avoir. </Button>
</p> )}
</div> </div>
) : ( ) : (
filteredAvoirs.map((avoir) => ( <table className="min-w-full divide-y divide-gray-200">
<Card key={avoir.id} className="hover:shadow-md transition-shadow"> <thead className="bg-gray-50">
<CardHeader> <tr>
<div className="flex items-center justify-between"> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<CardTitle className="text-lg flex items-center"> Statut
{getStatusIcon(avoir.status)} </th>
<span className="ml-2">#{avoir.invoiceReference || 'Sans référence'}</span> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</CardTitle> Référence
<Badge variant={getStatusVariant(avoir.status)}> </th>
{avoir.status} <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</Badge> Fournisseur
</div> </th>
<CardDescription> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<div className="flex items-center"> Magasin
<div </th>
className="w-3 h-3 rounded-full mr-2" <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
style={{ backgroundColor: avoir.group.color }} Montant
/> </th>
{avoir.group.name} <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</div> Créé le
</CardDescription> </th>
</CardHeader> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<CardContent> Actions
<div className="space-y-2"> </th>
<div className="flex justify-between items-center"> </tr>
<span className="text-sm text-muted-foreground">Fournisseur:</span> </thead>
<span className="font-medium">{avoir.supplier.name}</span> <tbody className="bg-white divide-y divide-gray-200">
</div> {filteredAvoirs.map((avoir) => {
const canEditDelete = ['admin', 'directeur'].includes((user as any)?.role);
<div className="flex justify-between items-center"> return (
<span className="text-sm text-muted-foreground">Montant:</span> <tr key={avoir.id} className="hover:bg-gray-50">
<span className="font-bold text-lg">{avoir.amount ? avoir.amount.toFixed(2) + ' €' : 'Non spécifié'}</span> <td className="px-6 py-4 whitespace-nowrap">
</div> <div className="flex items-center">
{getStatusIcon(avoir.status)}
{avoir.comment && ( <Badge variant={getStatusVariant(avoir.status)} className="ml-2">
<div className="pt-2 border-t"> {avoir.status}
<p className="text-sm text-muted-foreground italic"> </Badge>
"{avoir.comment}" </div>
</p> </td>
</div> <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
)} #{avoir.invoiceReference || 'Sans référence'}
</td>
<div className="pt-2 border-t"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<div className="flex justify-between text-xs text-muted-foreground"> {avoir.supplier.name}
<span> </td>
Par {avoir.creator.firstName && avoir.creator.lastName <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
? `${avoir.creator.firstName} ${avoir.creator.lastName}` <div className="flex items-center">
: avoir.creator.username <div
} className="w-3 h-3 rounded-full mr-2"
</span> style={{ backgroundColor: avoir.group.color }}
<span> />
{format(new Date(avoir.createdAt), "dd/MM/yyyy", { locale: fr })} {avoir.group.name}
</span> </div>
</div> </td>
</div> <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{avoir.amount ? `${avoir.amount.toFixed(2)} €` : 'Non spécifié'}
{avoir.commercialProcessed && ( </td>
<div className="flex items-center text-xs text-green-600"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<CheckCircle className="h-3 w-3 mr-1" /> <div>
Traité par commercial <div>{format(new Date(avoir.createdAt), "dd/MM/yyyy", { locale: fr })}</div>
</div> <div className="text-xs text-gray-400">
)} Par {avoir.creator.firstName && avoir.creator.lastName
? `${avoir.creator.firstName} ${avoir.creator.lastName}`
<div className="flex items-center space-x-2"> : avoir.creator.username
{avoir.webhookSent && ( }
<Badge variant="secondary" className="text-xs"> </div>
Webhook envoyé </div>
</Badge> </td>
)} <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
{avoir.nocodbVerified && ( {canEditDelete ? (
<Badge variant="default" className="text-xs"> <div className="flex items-center space-x-2">
Vérifié NocoDB <Button
</Badge> variant="outline"
)} size="sm"
</div> className="h-8 w-8 p-0"
</div> title="Modifier"
</CardContent> >
</Card> <Edit className="h-4 w-4" />
)) </Button>
)} <Button
variant="outline"
size="sm"
className="h-8 w-8 p-0 text-red-600 hover:text-red-700 hover:bg-red-50"
title="Supprimer"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
) : (
<span className="text-gray-400 text-xs">Lecture seule</span>
)}
</td>
</tr>
);
})}
</tbody>
</table>
)}
</div>
</div> </div>
</div> </div>
); );