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:
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": "30c14d7e",
|
||||
"timestamp": 1777149849085
|
||||
"version": "a13a3f60",
|
||||
"timestamp": 1777365200860
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
15
server/db.ts
15
server/db.ts
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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") }))
|
||||
|
||||
7
todo.md
7
todo.md
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user