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",
|
"version": "a13a3f60",
|
||||||
"timestamp": 1777149849085
|
"timestamp": 1777365200860
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo } from "react";
|
||||||
|
import { useLocalAuth } from "@/contexts/LocalAuthContext";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { trpc } from "@/lib/trpc";
|
import { trpc } from "@/lib/trpc";
|
||||||
import { FilterBar } from "@/components/FilterBar";
|
import { FilterBar } from "@/components/FilterBar";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -17,7 +19,20 @@ import {
|
|||||||
ChevronRight,
|
ChevronRight,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
Clock,
|
Clock,
|
||||||
|
Trash2,
|
||||||
|
AlertTriangle,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
} from "@/components/ui/alert-dialog";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { format, isPast, differenceInDays } from "date-fns";
|
import { format, isPast, differenceInDays } from "date-fns";
|
||||||
import { fr } from "date-fns/locale";
|
import { fr } from "date-fns/locale";
|
||||||
@@ -88,6 +103,19 @@ function ClotureStatus({ date }: { date: Date | null | undefined }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function AAPDashboard() {
|
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 [viewMode, setViewMode] = useState<"list" | "grid">("list");
|
||||||
const [activeTab, setActiveTab] = useState<AAPCategorie | "all">("all");
|
const [activeTab, setActiveTab] = useState<AAPCategorie | "all">("all");
|
||||||
const [page, setPage] = useState(1);
|
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">
|
<Button variant={viewMode === "grid" ? "default" : "outline"} size="sm" onClick={() => setViewMode("grid")} className="gap-2">
|
||||||
<LayoutGrid size={15} />Vignettes
|
<LayoutGrid size={15} />Vignettes
|
||||||
</Button>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo } from "react";
|
||||||
|
import { useLocalAuth } from "@/contexts/LocalAuthContext";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { trpc } from "@/lib/trpc";
|
import { trpc } from "@/lib/trpc";
|
||||||
import { FilterBar } from "@/components/FilterBar";
|
import { FilterBar } from "@/components/FilterBar";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -26,7 +28,20 @@ import {
|
|||||||
Eye,
|
Eye,
|
||||||
Globe,
|
Globe,
|
||||||
BookOpen,
|
BookOpen,
|
||||||
|
Trash2,
|
||||||
|
AlertTriangle,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
} from "@/components/ui/alert-dialog";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { fr } from "date-fns/locale";
|
import { fr } from "date-fns/locale";
|
||||||
@@ -214,6 +229,19 @@ function VeilleDetailDialog({
|
|||||||
// ─── Composant principal ──────────────────────────────────────────────────────
|
// ─── Composant principal ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
export default function VeilleDashboard() {
|
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 [viewMode, setViewMode] = useState<"list" | "grid">("list");
|
||||||
const [activeTab, setActiveTab] = useState<TypeVeille | "all">("all");
|
const [activeTab, setActiveTab] = useState<TypeVeille | "all">("all");
|
||||||
const [page, setPage] = useState(1);
|
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">
|
<Button variant={viewMode === "grid" ? "default" : "outline"} size="sm" onClick={() => setViewMode("grid")} className="gap-2">
|
||||||
<LayoutGrid size={15} />Vignettes
|
<LayoutGrid size={15} />Vignettes
|
||||||
</Button>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Onglets */}
|
{/* Onglets */}
|
||||||
<Tabs value={activeTab} onValueChange={(v) => { setActiveTab(v as TypeVeille | "all"); setPage(1); }}>
|
<Tabs value={activeTab} onValueChange={(v) => { setActiveTab(v as TypeVeille | "all"); setPage(1); }}>
|
||||||
<TabsList className="bg-muted/50">
|
<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,
|
getVeilleDistinctValues,
|
||||||
getAapItems,
|
getAapItems,
|
||||||
getAapDistinctValues,
|
getAapDistinctValues,
|
||||||
|
purgeVeilleItems,
|
||||||
|
purgeAapItems,
|
||||||
getAllSettings,
|
getAllSettings,
|
||||||
setSettings,
|
setSettings,
|
||||||
getImportLogs,
|
getImportLogs,
|
||||||
@@ -98,9 +100,12 @@ export const appRouter = router({
|
|||||||
filters: publicProcedure.query(async () => {
|
filters: publicProcedure.query(async () => {
|
||||||
return getVeilleDistinctValues();
|
return getVeilleDistinctValues();
|
||||||
}),
|
}),
|
||||||
|
purge: adminProcedure.mutation(async () => {
|
||||||
|
const count = await purgeVeilleItems();
|
||||||
|
return { success: true, deleted: count };
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
// ─── AAP ────────────────────────────────────────────────────────────────────
|
// ─── AAPP ────────────────────────────────────────────────────────────────────
|
||||||
aap: router({
|
aap: router({
|
||||||
list: publicProcedure
|
list: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
@@ -124,9 +129,12 @@ export const appRouter = router({
|
|||||||
filters: publicProcedure.query(async () => {
|
filters: publicProcedure.query(async () => {
|
||||||
return getAapDistinctValues();
|
return getAapDistinctValues();
|
||||||
}),
|
}),
|
||||||
|
purge: adminProcedure.mutation(async () => {
|
||||||
|
const count = await purgeAapItems();
|
||||||
|
return { success: true, deleted: count };
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
// ─── Import ─────────────────────────────────────────────────────────────────
|
// ─── Importt ─────────────────────────────────────────────────────────────────
|
||||||
import: router({
|
import: router({
|
||||||
run: adminProcedure
|
run: adminProcedure
|
||||||
.input(z.object({ type: z.enum(["veille", "aap", "all"]).default("all") }))
|
.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] 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)
|
- [x] Navigation : ajouter l'entrée RSS dans le menu latéral (DashboardLayout)
|
||||||
- [ ] Déploiement VPS via Gitea CI/CD
|
- [ ] 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