Some checks failed
CI/CD Pipeline / Backend - Build, Test & Push (push) Failing after 1m38s
CI/CD Pipeline / Frontend - Build, Test & Push (push) Successful in 25m29s
CI/CD Pipeline / Integration Tests (push) Has been skipped
CI/CD Pipeline / Deployment Summary (push) Has been skipped
CI/CD Pipeline / Discord Notification (Success) (push) Has been skipped
CI/CD Pipeline / Discord Notification (Failure) (push) Has been skipped
Added comprehensive CSV rates management interface to the frontend dashboard with full CRUD operations. ## Backend Changes - Added `GET /api/v1/admin/csv-rates/files` endpoint to list all uploaded CSV files with metadata - Added `DELETE /api/v1/admin/csv-rates/files/:filename` endpoint to delete CSV files and their configurations - Both endpoints provide frontend-compatible responses with file info (filename, size, rowCount, uploadedAt) - File deletion includes both filesystem cleanup and database configuration removal ## Frontend Changes - Added "CSV Rates" navigation item to dashboard sidebar (ADMIN only) - Moved CSV rates page from `/app/admin/csv-rates` to `/app/dashboard/admin/csv-rates` for proper dashboard integration - Updated CsvUpload component to include required `companyEmail` field - Component now properly validates and sends all required fields (companyName, companyEmail, file) - Enhanced form validation with email input type ## Features - ✅ Upload CSV rate files with company name and email - ✅ List all uploaded CSV files with metadata (filename, size, row count, upload date) - ✅ Delete CSV files with confirmation dialog - ✅ Real-time file validation (format, size limit 10MB) - ✅ Auto-refresh after successful operations - ✅ ADMIN role-based access control - ✅ Integrated into dashboard navigation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
189 lines
6.2 KiB
TypeScript
189 lines
6.2 KiB
TypeScript
/**
|
|
* Admin CSV Rates Management Page
|
|
*
|
|
* ADMIN-only page for:
|
|
* - Uploading CSV rate files
|
|
* - Viewing CSV configurations
|
|
* - Managing carrier rate data
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
|
import { Loader2, RefreshCw, Trash2 } from 'lucide-react';
|
|
import { CsvUpload } from '@/components/admin/CsvUpload';
|
|
import { listCsvFiles, deleteCsvFile, type CsvFileInfo } from '@/lib/api/admin/csv-rates';
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from '@/components/ui/table';
|
|
|
|
export default function AdminCsvRatesPage() {
|
|
const [files, setFiles] = useState<CsvFileInfo[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchFiles = async () => {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const data = await listCsvFiles();
|
|
setFiles(data.files || []);
|
|
} catch (err: any) {
|
|
setError(err?.message || 'Erreur lors du chargement des fichiers');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchFiles();
|
|
}, []);
|
|
|
|
const handleDelete = async (filename: string) => {
|
|
if (!confirm(`Êtes-vous sûr de vouloir supprimer le fichier ${filename} ?`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await deleteCsvFile(filename);
|
|
alert(`Fichier supprimé: ${filename}`);
|
|
fetchFiles(); // Refresh list
|
|
} catch (err: any) {
|
|
alert(`Erreur: ${err?.message || 'Impossible de supprimer le fichier'}`);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="container mx-auto py-8 space-y-6">
|
|
{/* Page Header */}
|
|
<div>
|
|
<h1 className="text-3xl font-bold tracking-tight">Gestion des tarifs CSV</h1>
|
|
<p className="text-muted-foreground mt-2">
|
|
Interface d'administration pour gérer les fichiers CSV de tarifs maritimes
|
|
</p>
|
|
<Badge variant="destructive" className="mt-2">
|
|
ADMIN SEULEMENT
|
|
</Badge>
|
|
</div>
|
|
|
|
{/* Upload Section */}
|
|
<CsvUpload />
|
|
|
|
{/* Configurations Table */}
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between">
|
|
<div>
|
|
<CardTitle>Configurations CSV actives</CardTitle>
|
|
<CardDescription>
|
|
Liste de toutes les compagnies avec fichiers CSV configurés
|
|
</CardDescription>
|
|
</div>
|
|
<Button variant="outline" size="sm" onClick={fetchFiles} disabled={loading}>
|
|
{loading ? (
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
) : (
|
|
<RefreshCw className="h-4 w-4" />
|
|
)}
|
|
</Button>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{error && (
|
|
<Alert variant="destructive" className="mb-4">
|
|
<AlertDescription>{error}</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
{loading ? (
|
|
<div className="flex items-center justify-center py-12">
|
|
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
|
</div>
|
|
) : files.length === 0 ? (
|
|
<div className="text-center py-12 text-muted-foreground">
|
|
Aucun fichier trouvé. Uploadez un fichier CSV pour commencer.
|
|
</div>
|
|
) : (
|
|
<div className="rounded-md border">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Fichier</TableHead>
|
|
<TableHead>Taille</TableHead>
|
|
<TableHead>Lignes</TableHead>
|
|
<TableHead>Date d'upload</TableHead>
|
|
<TableHead>Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{files.map((file) => (
|
|
<TableRow key={file.filename}>
|
|
<TableCell className="font-medium font-mono text-xs">{file.filename}</TableCell>
|
|
<TableCell>
|
|
{(file.size / 1024).toFixed(2)} KB
|
|
</TableCell>
|
|
<TableCell>
|
|
{file.rowCount ? (
|
|
<span className="font-semibold">{file.rowCount} lignes</span>
|
|
) : (
|
|
<span className="text-muted-foreground">-</span>
|
|
)}
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="text-xs text-muted-foreground">
|
|
{new Date(file.uploadedAt).toLocaleDateString('fr-FR')}
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => handleDelete(file.filename)}
|
|
>
|
|
<Trash2 className="h-4 w-4 text-red-600" />
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Info Card */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Informations</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-2 text-sm">
|
|
<p>
|
|
<strong>Format CSV requis :</strong> Consultez la documentation pour la liste complète
|
|
des colonnes obligatoires.
|
|
</p>
|
|
<p>
|
|
<strong>Taille maximale :</strong> 10 MB par fichier
|
|
</p>
|
|
<p>
|
|
<strong>Mise à jour :</strong> Uploader un nouveau fichier pour une compagnie existante
|
|
écrasera l'ancien fichier.
|
|
</p>
|
|
<p>
|
|
<strong>Validation :</strong> Le système valide automatiquement la structure du CSV lors
|
|
de l'upload.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|