mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-18 04:38:41 +02:00
feat: Introduce Cataloguemate.fr scraper service and associated catalogue administration UI.
This commit is contained in:
1 parent
d772e85ae4
commit
03d79e45fb
3 files changed
+86
-9
No files matched your search
@@ -278,7 +278,8 @@ async def scrape_enseigne(enseigne: Enseigne, db: Session) -> ScrapingLog:
|
||||
date_debut=datetime.now(), # Todo: extract real dates
|
||||
date_fin=datetime.now() + timedelta(days=14),
|
||||
image_couverture_url=pages[0]['image_url'],
|
||||
content_hash=content_hash
|
||||
content_hash=content_hash,
|
||||
nombre_pages=len(pages) # Set actual page count
|
||||
)
|
||||
db.add(new_cat)
|
||||
db.commit()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
import { toast } from 'sonner';
|
||||
import { Loader2, Play, Clock, CheckCircle2, XCircle, AlertCircle } from 'lucide-react';
|
||||
import { Loader2, Play, Clock, CheckCircle2, XCircle, AlertCircle, Trash2 } from 'lucide-react';
|
||||
import { useAuth } from '../../hooks/use-auth';
|
||||
|
||||
const API_BASE = '/api/catalogues/admin';
|
||||
@@ -77,6 +77,30 @@ export default function CatalogueAdmin() {
|
||||
}
|
||||
};
|
||||
|
||||
const triggerPurge = async () => {
|
||||
if (!confirm('Êtes-vous sûr de vouloir purger les catalogues expirés depuis plus de 3 mois ?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await axios.post(`${API_BASE}/purge`, null, {
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
toast.success(response.data.message);
|
||||
toast.info(`${response.data.deleted_count} catalogue(s) supprimé(s)`);
|
||||
|
||||
// Reload data
|
||||
await Promise.all([loadStats(), loadLogs()]);
|
||||
} catch (error) {
|
||||
console.error('Error triggering purge:', error);
|
||||
toast.error('Erreur lors de la purge');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusIcon = (status) => {
|
||||
switch (status) {
|
||||
case 'success':
|
||||
@@ -97,9 +121,28 @@ export default function CatalogueAdmin() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold mb-2">Administration Catalogues</h2>
|
||||
<p className="text-muted-foreground">Gérer le scraping des catalogues Bonial</p>
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold mb-2">Administration Catalogues</h2>
|
||||
<p className="text-muted-foreground">Gérer le scraping des catalogues Cataloguemate</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={triggerPurge}
|
||||
disabled={loading}
|
||||
className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 disabled:opacity-50 flex items-center gap-2"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Purge...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
Purger (> 3 mois)
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { Loader2, Calendar, BookOpen, Search, ChevronRight, Eye } from 'lucide-react';
|
||||
import { Loader2, Calendar, BookOpen, Search, ChevronRight, Eye, Trash2 } from 'lucide-react';
|
||||
import axios from 'axios';
|
||||
import { useAuth } from '../hooks/use-auth';
|
||||
|
||||
const API_BASE = '/api/catalogues';
|
||||
|
||||
@@ -14,6 +15,7 @@ export default function Catalogues() {
|
||||
const [selectedCatalogue, setSelectedCatalogue] = useState(null);
|
||||
const [cataloguePages, setCataloguePages] = useState([]);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const { user, getAuthHeaders } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
loadEnseignes();
|
||||
@@ -104,6 +106,26 @@ export default function Catalogues() {
|
||||
return now >= debut && now <= fin;
|
||||
};
|
||||
|
||||
const deleteCatalogue = async (catalogueId, event) => {
|
||||
event.stopPropagation(); // Prevent opening the catalogue
|
||||
|
||||
if (!confirm('Êtes-vous sûr de vouloir supprimer ce catalogue ?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.delete(`${API_BASE}/admin/${catalogueId}`, {
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
toast.success('Catalogue supprimé');
|
||||
loadCatalogues(); // Reload the list
|
||||
} catch (error) {
|
||||
console.error('Error deleting catalogue:', error);
|
||||
toast.error('Erreur lors de la suppression');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-7xl mx-auto">
|
||||
{/* Header */}
|
||||
@@ -244,9 +266,20 @@ export default function Catalogues() {
|
||||
</div>
|
||||
|
||||
{/* Pages */}
|
||||
<div className="flex items-center gap-1 text-sm text-muted-foreground">
|
||||
<BookOpen className="h-4 w-4" />
|
||||
<span>{catalogue.nombre_pages} pages</span>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1 text-sm text-muted-foreground">
|
||||
<BookOpen className="h-4 w-4" />
|
||||
<span>{catalogue.nombre_pages} pages</span>
|
||||
</div>
|
||||
{user?.is_admin && (
|
||||
<button
|
||||
onClick={(e) => deleteCatalogue(catalogue.id, e)}
|
||||
className="p-2 text-red-500 hover:bg-red-50 rounded-lg transition-colors"
|
||||
title="Supprimer le catalogue"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user