Some checks failed
Dev CI / Unit Tests (${{ matrix.app }}) (backend) (push) Blocked by required conditions
Dev CI / Unit Tests (${{ matrix.app }}) (frontend) (push) Blocked by required conditions
Dev CI / Notify Failure (push) Blocked by required conditions
Dev CI / Quality (${{ matrix.app }}) (backend) (push) Has been cancelled
Dev CI / Quality (${{ matrix.app }}) (frontend) (push) Has been cancelled
Aligns dev with the complete application codebase (cicd branch). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
197 lines
6.5 KiB
TypeScript
197 lines
6.5 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>
|
|
<div className="flex 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>
|
|
</div>
|
|
</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>Email</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>
|
|
<div className="text-xs text-muted-foreground">
|
|
{file.companyEmail ?? '—'}
|
|
</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>
|
|
);
|
|
}
|