@@ -19,6 +19,14 @@ interface Stats {
1919 activePartners : number ;
2020}
2121
22+ interface ScrapingResult {
23+ matchesCreated : number ;
24+ matchesUpdated : number ;
25+ standings : number ;
26+ errors : string [ ] ;
27+ logs : string [ ] ;
28+ }
29+
2230interface QuickLink {
2331 label : string ;
2432 href : string ;
@@ -46,11 +54,6 @@ const QUICK_LINKS: QuickLink[] = [
4654 href : "/admin/partners" ,
4755 description : "Gérer les sponsors et partenaires" ,
4856 } ,
49- {
50- label : "Matches & scraping" ,
51- href : "/admin/matches" ,
52- description : "Voir les matches et lancer le scraping FFVB" ,
53- } ,
5457 {
5558 label : "Infos du club" ,
5659 href : "/admin/club" ,
@@ -62,27 +65,101 @@ export default function AdminDashboardPage() {
6265 const [ stats , setStats ] = React . useState < Stats | null > ( null ) ;
6366 const [ loading , setLoading ] = React . useState ( true ) ;
6467
68+ const [ scraping , setScraping ] = React . useState ( false ) ;
69+ const [ scrapingResult , setScrapingResult ] =
70+ React . useState < ScrapingResult | null > ( null ) ;
71+ const [ scrapingError , setScrapingError ] = React . useState < string | null > ( null ) ;
72+ const [ showLogs , setShowLogs ] = React . useState ( false ) ;
73+
6574 React . useEffect ( ( ) => {
66- apiFetch ( "/api/stats" )
75+ apiFetch < Stats > ( "/api/stats" )
6776 . then ( setStats )
6877 . catch ( console . error )
6978 . finally ( ( ) => setLoading ( false ) ) ;
7079 } , [ ] ) ;
7180
81+ const handleScraping = async ( ) => {
82+ if (
83+ ! confirm ( "Lancer le scraping FFVB ? Cela peut prendre plusieurs minutes." )
84+ )
85+ return ;
86+ setScraping ( true ) ;
87+ setScrapingResult ( null ) ;
88+ setScrapingError ( null ) ;
89+ try {
90+ const result = await apiFetch < ScrapingResult > ( "/api/scraping/run" , {
91+ method : "POST" ,
92+ } ) ;
93+ setScrapingResult ( result ) ;
94+ // Refresh stats after scraping
95+ const updated = await apiFetch < Stats > ( "/api/stats" ) . catch ( ( ) => null ) ;
96+ if ( updated ) setStats ( updated ) ;
97+ } catch ( err ) {
98+ setScrapingError (
99+ err instanceof Error ? err . message : "Erreur lors du scraping" ,
100+ ) ;
101+ } finally {
102+ setScraping ( false ) ;
103+ }
104+ } ;
105+
72106 return (
73107 < div className = "flex flex-1 flex-col gap-8" >
74- < div >
75- < h1 className = "text-2xl font-bold" > Dashboard</ h1 >
76- { stats ?. activeSeason && (
77- < p className = "text-sm text-muted-foreground mt-1" >
78- Saison active :{ " " }
79- < span className = "font-medium text-foreground" >
80- { stats . activeSeason }
81- </ span >
82- </ p >
83- ) }
108+ < div className = "flex items-start justify-between gap-4 flex-wrap" >
109+ < div >
110+ < h1 className = "text-2xl font-bold" > Dashboard</ h1 >
111+ { stats ?. activeSeason && (
112+ < p className = "text-sm text-muted-foreground mt-1" >
113+ Saison active :{ " " }
114+ < span className = "font-medium text-foreground" >
115+ { stats . activeSeason }
116+ </ span >
117+ </ p >
118+ ) }
119+ </ div >
120+
121+ < Button
122+ onClick = { handleScraping }
123+ disabled = { scraping }
124+ className = "bg-orange-600 hover:bg-orange-700 text-white shrink-0"
125+ >
126+ { scraping ? "⟳ Scraping en cours..." : "Lancer le scraping FFVB" }
127+ </ Button >
84128 </ div >
85129
130+ { /* Résultat scraping */ }
131+ { scrapingResult && (
132+ < div className = "border rounded-lg p-4 bg-green-50 border-green-200 flex flex-col gap-2" >
133+ < div className = "font-semibold text-green-800" > Scraping terminé</ div >
134+ < div className = "text-sm text-green-700 flex flex-wrap gap-4" >
135+ < span > { scrapingResult . matchesCreated } match(es) créé(s)</ span >
136+ < span > { scrapingResult . matchesUpdated } mis à jour</ span >
137+ < span > { scrapingResult . standings } classement(s)</ span >
138+ </ div >
139+ { scrapingResult . errors . filter ( Boolean ) . length > 0 && (
140+ < div className = "text-sm text-red-600" >
141+ Erreurs : { scrapingResult . errors . join ( ", " ) }
142+ </ div >
143+ ) }
144+ < button
145+ className = "text-xs text-green-600 underline self-start"
146+ onClick = { ( ) => setShowLogs ( ( v ) => ! v ) }
147+ >
148+ { showLogs ? "Masquer les logs" : "Voir les logs" }
149+ </ button >
150+ { showLogs && (
151+ < pre className = "text-xs bg-white border rounded p-3 overflow-auto max-h-48 whitespace-pre-wrap" >
152+ { scrapingResult . logs . join ( "\n" ) }
153+ </ pre >
154+ ) }
155+ </ div >
156+ ) }
157+ { scrapingError && (
158+ < div className = "border rounded-lg p-4 bg-red-50 border-red-200 text-red-700 text-sm" >
159+ { scrapingError }
160+ </ div >
161+ ) }
162+
86163 < SectionAdminCards stats = { stats } loading = { loading } />
87164
88165 { /* Accès rapides */ }
0 commit comments