Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
* text=auto eol=lf
*.js text eol=lf
*.ts text eol=lf
*.tsx text eol=lf
*.json text eol=lf
*.md text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
9 changes: 5 additions & 4 deletions .github/workflows/ticket.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ jobs:
exit 0
fi

# Regex pour le commit / titre PR : type(nom): Fixes #<num> - message
if [[ "$PR_TITLE" =~ ^(feat|feature|fix|docs|chore|refactor|test|hotfix)\([a-zA-Z0-9_-]+\):\ Fixes\ \#[0-9]+\ -\ .+ ]]; then
# Regex pour le titre PR : type(nom): Fixes #<num> - message
TITLE_REGEX='^(feat|feature|fix|docs|chore|refactor|test|hotfix)\([a-zA-Z0-9_-]+\): Fixes #[0-9]+ - .+'
if [[ "$PR_TITLE" =~ $TITLE_REGEX ]]; then
echo "✅ Titre de PR valide avec référence au ticket"
exit 0
fi

# Regex pour le nom de branche : feature/123-description, fix/123-description, hotfix/123-description
if [[ "$BRANCH_NAME" =~ ^(feature|fix|hotfix)/[0-9]+-.+ ]]; then
# Regex pour le nom de branche : feat/123-description, feature/123-description, fix/123-description, hotfix/123-description
if [[ "$BRANCH_NAME" =~ ^(feat|feature|fix|hotfix)/[0-9]+-.+ ]]; then
echo "✅ Nom de branche valide avec référence au ticket"
exit 0
fi
Expand Down
5 changes: 3 additions & 2 deletions saintBarthVolleyApp/backend/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const PORT = process.env.PORT || 5000;
const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/saintbarthvolley';

// Connexion MongoDB
mongoose.connect(MONGO_URI)
mongoose
.connect(MONGO_URI)
.then(() => {
console.log('MongoDB connected');

Expand All @@ -18,4 +19,4 @@ mongoose.connect(MONGO_URI)
console.log(`Server launched on http://localhost:${PORT}`);
});
})
.catch(err => console.error('Error MongoDB:', err));
.catch((err) => console.error('Error MongoDB:', err));
6 changes: 3 additions & 3 deletions saintBarthVolleyApp/frontend/.lintstagedrc.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// const path = require('path')

// const buildEslintCommand = (filenames) =>
// `eslint --fix ${filenames
// .map((f) => `"${path.relative(process.cwd(), f)}"`)
// .join(' ')}`

// module.exports = {
// '*.{js,jsx,ts,tsx}': [buildEslintCommand],
// }
// }
2 changes: 1 addition & 1 deletion saintBarthVolleyApp/frontend/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ export function middleware(req: NextRequest) {

export const config = {
matcher: ["/admin/:path*"],
};
};
109 changes: 93 additions & 16 deletions saintBarthVolleyApp/frontend/src/app/admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ interface Stats {
activePartners: number;
}

interface ScrapingResult {
matchesCreated: number;
matchesUpdated: number;
standings: number;
errors: string[];
logs: string[];
}

interface QuickLink {
label: string;
href: string;
Expand Down Expand Up @@ -46,11 +54,6 @@ const QUICK_LINKS: QuickLink[] = [
href: "/admin/partners",
description: "Gérer les sponsors et partenaires",
},
{
label: "Matches & scraping",
href: "/admin/matches",
description: "Voir les matches et lancer le scraping FFVB",
},
{
label: "Infos du club",
href: "/admin/club",
Expand All @@ -62,27 +65,101 @@ export default function AdminDashboardPage() {
const [stats, setStats] = React.useState<Stats | null>(null);
const [loading, setLoading] = React.useState(true);

const [scraping, setScraping] = React.useState(false);
const [scrapingResult, setScrapingResult] =
React.useState<ScrapingResult | null>(null);
const [scrapingError, setScrapingError] = React.useState<string | null>(null);
const [showLogs, setShowLogs] = React.useState(false);

React.useEffect(() => {
apiFetch("/api/stats")
apiFetch<Stats>("/api/stats")
.then(setStats)
.catch(console.error)
.finally(() => setLoading(false));
}, []);

const handleScraping = async () => {
if (
!confirm("Lancer le scraping FFVB ? Cela peut prendre plusieurs minutes.")
)
return;
setScraping(true);
setScrapingResult(null);
setScrapingError(null);
try {
const result = await apiFetch<ScrapingResult>("/api/scraping/run", {
method: "POST",
});
setScrapingResult(result);
// Refresh stats after scraping
const updated = await apiFetch<Stats>("/api/stats").catch(() => null);
if (updated) setStats(updated);
} catch (err) {
setScrapingError(
err instanceof Error ? err.message : "Erreur lors du scraping",
);
} finally {
setScraping(false);
}
};

return (
<div className="flex flex-1 flex-col gap-8">
<div>
<h1 className="text-2xl font-bold">Dashboard</h1>
{stats?.activeSeason && (
<p className="text-sm text-muted-foreground mt-1">
Saison active :{" "}
<span className="font-medium text-foreground">
{stats.activeSeason}
</span>
</p>
)}
<div className="flex items-start justify-between gap-4 flex-wrap">
<div>
<h1 className="text-2xl font-bold">Dashboard</h1>
{stats?.activeSeason && (
<p className="text-sm text-muted-foreground mt-1">
Saison active :{" "}
<span className="font-medium text-foreground">
{stats.activeSeason}
</span>
</p>
)}
</div>

<Button
onClick={handleScraping}
disabled={scraping}
className="bg-orange-600 hover:bg-orange-700 text-white shrink-0"
>
{scraping ? "⟳ Scraping en cours..." : "Lancer le scraping FFVB"}
</Button>
</div>

{/* Résultat scraping */}
{scrapingResult && (
<div className="border rounded-lg p-4 bg-green-50 border-green-200 flex flex-col gap-2">
<div className="font-semibold text-green-800">Scraping terminé</div>
<div className="text-sm text-green-700 flex flex-wrap gap-4">
<span>{scrapingResult.matchesCreated} match(es) créé(s)</span>
<span>{scrapingResult.matchesUpdated} mis à jour</span>
<span>{scrapingResult.standings} classement(s)</span>
</div>
{scrapingResult.errors.filter(Boolean).length > 0 && (
<div className="text-sm text-red-600">
Erreurs : {scrapingResult.errors.join(", ")}
</div>
)}
<button
className="text-xs text-green-600 underline self-start"
onClick={() => setShowLogs((v) => !v)}
>
{showLogs ? "Masquer les logs" : "Voir les logs"}
</button>
{showLogs && (
<pre className="text-xs bg-white border rounded p-3 overflow-auto max-h-48 whitespace-pre-wrap">
{scrapingResult.logs.join("\n")}
</pre>
)}
</div>
)}
{scrapingError && (
<div className="border rounded-lg p-4 bg-red-50 border-red-200 text-red-700 text-sm">
{scrapingError}
</div>
)}

<SectionAdminCards stats={stats} loading={loading} />

{/* Accès rapides */}
Expand Down
2 changes: 2 additions & 0 deletions saintBarthVolleyApp/frontend/src/app/admin/partners/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export default function PartnersPage() {
>
<div className="flex items-center gap-3">
{p.logo ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={p.logo}
alt={p.name}
Expand Down Expand Up @@ -256,6 +257,7 @@ export default function PartnersPage() {
placeholder="https://example.com/logo.png"
/>
{editing.logo && (
// eslint-disable-next-line @next/next/no-img-element
<img
src={editing.logo}
alt="Aperçu logo"
Expand Down
Loading
Loading