Checkpoint: Ajout bouton "Purger les données" (admin uniquement) avec boîte de dialogue de confirmation sur VeilleDashboard et AAPDashboard. Procédures tRPC veille.purge et aap.purge ajoutées côté serveur.

This commit is contained in:
Manus
2026-04-28 04:33:20 -04:00
parent c3e1720e83
commit 8f2a22e4b1
6 changed files with 160 additions and 7 deletions

View File

@@ -1,4 +1,4 @@
{
"version": "30c14d7e",
"timestamp": 1777149849085
"version": "a13a3f60",
"timestamp": 1777365200860
}

View File

@@ -1,4 +1,6 @@
import { useState, useMemo } from "react";
import { useLocalAuth } from "@/contexts/LocalAuthContext";
import { toast } from "sonner";
import { trpc } from "@/lib/trpc";
import { FilterBar } from "@/components/FilterBar";
import { Button } from "@/components/ui/button";
@@ -17,7 +19,20 @@ import {
ChevronRight,
AlertCircle,
Clock,
Trash2,
AlertTriangle,
} from "lucide-react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { cn } from "@/lib/utils";
import { format, isPast, differenceInDays } from "date-fns";
import { fr } from "date-fns/locale";
@@ -88,6 +103,19 @@ function ClotureStatus({ date }: { date: Date | null | undefined }) {
}
export default function AAPDashboard() {
const { user } = useLocalAuth();
const isAdmin = user?.role === "admin";
const utils = trpc.useUtils();
const purgeMutation = trpc.aap.purge.useMutation({
onSuccess: (data) => {
toast.success(`Purge effectuée — ${data.deleted} entrée(s) supprimée(s)`);
utils.aap.list.invalidate();
utils.aap.filters.invalidate();
},
onError: (err) => {
toast.error(`Erreur lors de la purge : ${err.message}`);
},
});
const [viewMode, setViewMode] = useState<"list" | "grid">("list");
const [activeTab, setActiveTab] = useState<AAPCategorie | "all">("all");
const [page, setPage] = useState(1);
@@ -155,6 +183,40 @@ export default function AAPDashboard() {
<Button variant={viewMode === "grid" ? "default" : "outline"} size="sm" onClick={() => setViewMode("grid")} className="gap-2">
<LayoutGrid size={15} />Vignettes
</Button>
{isAdmin && (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline" size="sm" className="gap-2 border-destructive/50 text-destructive hover:bg-destructive hover:text-destructive-foreground ml-2">
<Trash2 size={15} />
Purger les données
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2">
<AlertTriangle size={20} className="text-destructive" />
Purger tous les appels à projets
</AlertDialogTitle>
<AlertDialogDescription asChild>
<div className="space-y-2">
<p>Cette action va <strong>supprimer définitivement</strong> tous les appels à projets (Handicap, PA, Enfance, Précarité, Sanitaire et Autre).</p>
<p className="text-destructive font-medium">Cette opération est irréversible. Les données ne pourront pas être récupérées.</p>
</div>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Annuler</AlertDialogCancel>
<AlertDialogAction
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
onClick={() => purgeMutation.mutate()}
disabled={purgeMutation.isPending}
>
{purgeMutation.isPending ? "Purge en cours..." : "Oui, purger tous les appels à projets"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)}
</div>
</div>

View File

@@ -1,4 +1,6 @@
import { useState, useMemo } from "react";
import { useLocalAuth } from "@/contexts/LocalAuthContext";
import { toast } from "sonner";
import { trpc } from "@/lib/trpc";
import { FilterBar } from "@/components/FilterBar";
import { Button } from "@/components/ui/button";
@@ -26,7 +28,20 @@ import {
Eye,
Globe,
BookOpen,
Trash2,
AlertTriangle,
} from "lucide-react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import { fr } from "date-fns/locale";
@@ -214,6 +229,19 @@ function VeilleDetailDialog({
// ─── Composant principal ──────────────────────────────────────────────────────
export default function VeilleDashboard() {
const { user } = useLocalAuth();
const isAdmin = user?.role === "admin";
const utils = trpc.useUtils();
const purgeMutation = trpc.veille.purge.useMutation({
onSuccess: (data) => {
toast.success(`Purge effectuée — ${data.deleted} entrée(s) supprimée(s)`);
utils.veille.list.invalidate();
utils.veille.filters.invalidate();
},
onError: (err) => {
toast.error(`Erreur lors de la purge : ${err.message}`);
},
});
const [viewMode, setViewMode] = useState<"list" | "grid">("list");
const [activeTab, setActiveTab] = useState<TypeVeille | "all">("all");
const [page, setPage] = useState(1);
@@ -284,9 +312,42 @@ export default function VeilleDashboard() {
<Button variant={viewMode === "grid" ? "default" : "outline"} size="sm" onClick={() => setViewMode("grid")} className="gap-2">
<LayoutGrid size={15} />Vignettes
</Button>
{isAdmin && (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline" size="sm" className="gap-2 border-destructive/50 text-destructive hover:bg-destructive hover:text-destructive-foreground ml-2">
<Trash2 size={15} />
Purger les données
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2">
<AlertTriangle size={20} className="text-destructive" />
Purger toutes les données de veille
</AlertDialogTitle>
<AlertDialogDescription asChild>
<div className="space-y-2">
<p>Cette action va <strong>supprimer définitivement</strong> toutes les entrées de la veille stratégique (réglementaire, concurrentielle, technologique et générale).</p>
<p className="text-destructive font-medium">Cette opération est irréversible. Les données ne pourront pas être récupérées.</p>
</div>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Annuler</AlertDialogCancel>
<AlertDialogAction
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
onClick={() => purgeMutation.mutate()}
disabled={purgeMutation.isPending}
>
{purgeMutation.isPending ? "Purge en cours..." : "Oui, purger toutes les données"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)}
</div>
</div>
{/* Onglets */}
<Tabs value={activeTab} onValueChange={(v) => { setActiveTab(v as TypeVeille | "all"); setPage(1); }}>
<TabsList className="bg-muted/50">

View File

@@ -433,3 +433,18 @@ export async function saveRssSettings(data: Partial<Omit<InsertRssSettings, "id"
});
}
}
// ─── Purge ───────────────────────────────────────────────────────────────────
export async function purgeVeilleItems(): Promise<number> {
const db = await getDb();
if (!db) throw new Error("Database not available");
const result = await db.delete(veilleItems);
return (result as any).affectedRows ?? 0;
}
export async function purgeAapItems(): Promise<number> {
const db = await getDb();
if (!db) throw new Error("Database not available");
const result = await db.delete(aapItems);
return (result as any).affectedRows ?? 0;
}

View File

@@ -9,6 +9,8 @@ import {
getVeilleDistinctValues,
getAapItems,
getAapDistinctValues,
purgeVeilleItems,
purgeAapItems,
getAllSettings,
setSettings,
getImportLogs,
@@ -98,9 +100,12 @@ export const appRouter = router({
filters: publicProcedure.query(async () => {
return getVeilleDistinctValues();
}),
purge: adminProcedure.mutation(async () => {
const count = await purgeVeilleItems();
return { success: true, deleted: count };
}),
}),
// ─── AAP ────────────────────────────────────────────────────────────────────
// ─── AAPP ────────────────────────────────────────────────────────────────────
aap: router({
list: publicProcedure
.input(
@@ -124,9 +129,12 @@ export const appRouter = router({
filters: publicProcedure.query(async () => {
return getAapDistinctValues();
}),
purge: adminProcedure.mutation(async () => {
const count = await purgeAapItems();
return { success: true, deleted: count };
}),
}),
// ─── Import ─────────────────────────────────────────────────────────────────
// ─── Importt ─────────────────────────────────────────────────────────────────
import: router({
run: adminProcedure
.input(z.object({ type: z.enum(["veille", "aap", "all"]).default("all") }))

View File

@@ -73,3 +73,10 @@
- [x] Page RssFeeds.tsx : liste des flux, ajout/édition/suppression, config fréquence, règles d'automatisme
- [x] Navigation : ajouter l'entrée RSS dans le menu latéral (DashboardLayout)
- [ ] Déploiement VPS via Gitea CI/CD
## Purge des données
- [ ] Procédures tRPC : veille.purge et aap.purge (adminProcedure)
- [ ] Bouton "Purger les données" en haut à droite de VeilleDashboard.tsx (admin uniquement)
- [ ] Bouton "Purger les données" en haut à droite de AAPDashboard.tsx (admin uniquement)
- [ ] Boîte de dialogue de confirmation avec message d'avertissement
- [ ] Déploiement VPS