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
+98 -78
+98 -78
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,45 +418,79 @@ 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">
<div className="overflow-x-auto">
{isLoading ? ( {isLoading ? (
Array.from({ length: 6 }).map((_, i) => ( <div className="p-4">
<Card key={i} className="animate-pulse"> <div className="animate-pulse space-y-4">
<CardHeader> {Array.from({ length: 3 }).map((_, i) => (
<div className="h-4 bg-gray-200 rounded w-3/4"></div> <div key={i} className="h-16 bg-gray-200 rounded"></div>
<div className="h-3 bg-gray-200 rounded w-1/2"></div> ))}
</CardHeader> </div>
<CardContent>
<div className="space-y-2">
<div className="h-3 bg-gray-200 rounded"></div>
<div className="h-3 bg-gray-200 rounded w-4/5"></div>
</div> </div>
</CardContent>
</Card>
))
) : filteredAvoirs.length === 0 ? ( ) : filteredAvoirs.length === 0 ? (
<div className="col-span-full text-center py-12"> <div className="p-12 text-center">
<FileText className="mx-auto h-12 w-12 text-gray-400" /> <FileText className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">Aucun avoir</h3> <h3 className="mt-4 text-sm font-medium text-gray-900">Aucun avoir</h3>
<p className="mt-1 text-sm text-gray-500"> <p className="mt-1 text-sm text-gray-500">
Commencez par créer votre premier avoir. {searchTerm ? "Aucun avoir ne correspond à votre recherche." : "Commencez par créer votre premier avoir."}
</p> </p>
{canCreate && !searchTerm && (
<Button className="mt-4" onClick={() => setIsCreateDialogOpen(true)}>
<Plus className="h-4 w-4 mr-2" />
Créer un avoir
</Button>
)}
</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
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Référence
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Fournisseur
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Magasin
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Montant
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Créé le
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredAvoirs.map((avoir) => {
const canEditDelete = ['admin', 'directeur'].includes((user as any)?.role);
return (
<tr key={avoir.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
{getStatusIcon(avoir.status)} {getStatusIcon(avoir.status)}
<span className="ml-2">#{avoir.invoiceReference || 'Sans référence'}</span> <Badge variant={getStatusVariant(avoir.status)} className="ml-2">
</CardTitle>
<Badge variant={getStatusVariant(avoir.status)}>
{avoir.status} {avoir.status}
</Badge> </Badge>
</div> </div>
<CardDescription> </td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
#{avoir.invoiceReference || 'Sans référence'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{avoir.supplier.name}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<div className="flex items-center"> <div className="flex items-center">
<div <div
className="w-3 h-3 rounded-full mr-2" className="w-3 h-3 rounded-full mr-2"
@@ -464,67 +498,53 @@ export default function Avoirs() {
/> />
{avoir.group.name} {avoir.group.name}
</div> </div>
</CardDescription> </td>
</CardHeader> <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
<CardContent> {avoir.amount ? `${avoir.amount.toFixed(2)} €` : 'Non spécifié'}
<div className="space-y-2"> </td>
<div className="flex justify-between items-center"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<span className="text-sm text-muted-foreground">Fournisseur:</span> <div>
<span className="font-medium">{avoir.supplier.name}</span> <div>{format(new Date(avoir.createdAt), "dd/MM/yyyy", { locale: fr })}</div>
</div> <div className="text-xs text-gray-400">
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Montant:</span>
<span className="font-bold text-lg">{avoir.amount ? avoir.amount.toFixed(2) + ' €' : 'Non spécifié'}</span>
</div>
{avoir.comment && (
<div className="pt-2 border-t">
<p className="text-sm text-muted-foreground italic">
"{avoir.comment}"
</p>
</div>
)}
<div className="pt-2 border-t">
<div className="flex justify-between text-xs text-muted-foreground">
<span>
Par {avoir.creator.firstName && avoir.creator.lastName Par {avoir.creator.firstName && avoir.creator.lastName
? `${avoir.creator.firstName} ${avoir.creator.lastName}` ? `${avoir.creator.firstName} ${avoir.creator.lastName}`
: avoir.creator.username : avoir.creator.username
} }
</span>
<span>
{format(new Date(avoir.createdAt), "dd/MM/yyyy", { locale: fr })}
</span>
</div> </div>
</div> </div>
</td>
{avoir.commercialProcessed && ( <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center text-xs text-green-600"> {canEditDelete ? (
<CheckCircle className="h-3 w-3 mr-1" />
Traité par commercial
</div>
)}
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
{avoir.webhookSent && ( <Button
<Badge variant="secondary" className="text-xs"> variant="outline"
Webhook envoyé size="sm"
</Badge> className="h-8 w-8 p-0"
title="Modifier"
>
<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>
)} )}
{avoir.nocodbVerified && ( </td>
<Badge variant="default" className="text-xs"> </tr>
Vérifié NocoDB );
</Badge> })}
</tbody>
</table>
)} )}
</div> </div>
</div> </div>
</CardContent>
</Card>
))
)}
</div>
</div> </div>
); );
} }