Update .gitignore to exclude user-uploaded CSV files and remove the group-dev-ad.csv file. Refactor page.tsx to handle CSV file uploads, localStorage management, and enhance user interface for data management. Remove API route for fetching people data from CSV.

This commit is contained in:
Julien Froidefond
2025-12-19 10:29:02 +01:00
parent 165928d64e
commit 5418447d29
4 changed files with 207 additions and 31 deletions

4
.gitignore vendored
View File

@@ -62,5 +62,9 @@ logs
*.tmp *.tmp
*.temp *.temp
# CSV files (uploaded by users)
*.csv
!group-dev-ad.csv.example
.pnpm-store .pnpm-store

View File

@@ -1,18 +0,0 @@
import { NextResponse } from 'next/server';
import { parseCSV } from '@/lib/csv-parser';
import { join } from 'path';
export async function GET() {
try {
const filePath = join(process.cwd(), 'group-dev-ad.csv');
const people = await parseCSV(filePath);
return NextResponse.json(people);
} catch (error) {
console.error('Error parsing CSV:', error);
return NextResponse.json(
{ error: 'Failed to parse CSV file' },
{ status: 500 }
);
}
}

View File

@@ -1,7 +1,9 @@
'use client'; 'use client';
import { useState, useEffect, useMemo } from 'react'; import { useState, useEffect, useMemo, useRef } from 'react';
import { parseCSV, Person } from '@/lib/csv-parser'; import { parseCSVFromText, Person } from '@/lib/csv-parser-client';
const STORAGE_KEY = 'people-randomizr-data';
export default function Home() { export default function Home() {
const [people, setPeople] = useState<Person[]>([]); const [people, setPeople] = useState<Person[]>([]);
@@ -11,21 +13,71 @@ export default function Home() {
const [randomPeople, setRandomPeople] = useState<Person[]>([]); const [randomPeople, setRandomPeople] = useState<Person[]>([]);
const [showResults, setShowResults] = useState(false); const [showResults, setShowResults] = useState(false);
const [searchPoste, setSearchPoste] = useState<string>(''); const [searchPoste, setSearchPoste] = useState<string>('');
const [hasData, setHasData] = useState<boolean>(false);
const fileInputRef = useRef<HTMLInputElement>(null);
// Charger les données depuis localStorage au démarrage
useEffect(() => { useEffect(() => {
async function loadData() { try {
try { const stored = localStorage.getItem(STORAGE_KEY);
const response = await fetch('/api/people'); if (stored) {
const data = await response.json(); const data = JSON.parse(stored);
setPeople(data); setPeople(data);
setHasData(true);
}
} catch (error) {
console.error('Error loading from localStorage:', error);
} finally {
setLoading(false);
}
}, []);
// Sauvegarder dans localStorage quand les données changent
useEffect(() => {
if (people.length > 0) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(people));
setHasData(true);
}
}, [people]);
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
if (!file.name.endsWith('.csv')) {
alert('Veuillez sélectionner un fichier CSV');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
try {
const content = e.target?.result as string;
const parsed = parseCSVFromText(content);
setPeople(parsed);
setSelectedPostes(new Set());
setShowResults(false);
alert(`${parsed.length} personnes chargées avec succès !`);
} catch (error) { } catch (error) {
console.error('Error loading data:', error); console.error('Error parsing CSV:', error);
} finally { alert('Erreur lors du parsing du CSV. Vérifiez le format du fichier.');
setLoading(false); }
};
reader.readAsText(file, 'UTF-8');
};
const handleClearData = () => {
if (confirm('Êtes-vous sûr de vouloir supprimer toutes les données ?')) {
localStorage.removeItem(STORAGE_KEY);
setPeople([]);
setHasData(false);
setSelectedPostes(new Set());
setShowResults(false);
if (fileInputRef.current) {
fileInputRef.current.value = '';
} }
} }
loadData(); };
}, []);
// Compter le nombre de personnes par poste // Compter le nombre de personnes par poste
const posteCounts = useMemo(() => { const posteCounts = useMemo(() => {
@@ -121,6 +173,51 @@ export default function Home() {
); );
} }
if (!hasData) {
return (
<div className="min-h-screen py-8 px-4 relative overflow-hidden">
{/* Effets de fond animés */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-cyan-500 rounded-full mix-blend-multiply filter blur-3xl opacity-20 animate-blob"></div>
<div className="absolute top-1/3 right-1/4 w-96 h-96 bg-purple-500 rounded-full mix-blend-multiply filter blur-3xl opacity-20 animate-blob animation-delay-2000"></div>
<div className="absolute bottom-1/4 left-1/3 w-96 h-96 bg-pink-500 rounded-full mix-blend-multiply filter blur-3xl opacity-20 animate-blob animation-delay-4000"></div>
</div>
<div className="max-w-2xl mx-auto relative z-10 flex items-center justify-center min-h-screen">
<div className="glass-strong rounded-2xl p-12 shadow-2xl border border-cyan-500/20 text-center">
<div className="mb-8">
<div className="text-6xl mb-4">📊</div>
<h1 className="text-4xl font-black mb-4 text-gradient tracking-tight">
People Randomizr
</h1>
<p className="text-cyan-300 text-lg font-light mb-8">
Importez votre fichier CSV pour commencer
</p>
</div>
<div className="space-y-4">
<label className="block">
<input
ref={fileInputRef}
type="file"
accept=".csv"
onChange={handleFileUpload}
className="hidden"
/>
<div className="px-8 py-4 bg-gradient-to-r from-cyan-600 to-purple-600 text-white font-bold rounded-xl hover:from-cyan-500 hover:to-purple-500 transition-all duration-300 shadow-lg hover:shadow-cyan-500/50 border border-cyan-400/30 cursor-pointer inline-block">
📁 Choisir un fichier CSV
</div>
</label>
<p className="text-gray-400 text-sm mt-4">
Le fichier CSV doit contenir les colonnes : Nom, Description, Type
</p>
</div>
</div>
</div>
</div>
);
}
return ( return (
<div className="min-h-screen py-8 px-4 relative overflow-hidden"> <div className="min-h-screen py-8 px-4 relative overflow-hidden">
{/* Effets de fond animés */} {/* Effets de fond animés */}
@@ -131,11 +228,33 @@ export default function Home() {
</div> </div>
<div className="max-w-6xl mx-auto relative z-10"> <div className="max-w-6xl mx-auto relative z-10">
<div className="text-center mb-12"> <div className="text-center mb-8">
<h1 className="text-6xl font-black mb-4 text-gradient tracking-tight"> <h1 className="text-6xl font-black mb-4 text-gradient tracking-tight">
People Randomizr People Randomizr
</h1> </h1>
<p className="text-cyan-300 text-lg font-light">Sélection intelligente Extraction aléatoire</p> <p className="text-cyan-300 text-lg font-light mb-6">Sélection intelligente Extraction aléatoire</p>
{/* Boutons de gestion de données */}
<div className="flex items-center justify-center gap-4 mb-4">
<label className="cursor-pointer">
<input
ref={fileInputRef}
type="file"
accept=".csv"
onChange={handleFileUpload}
className="hidden"
/>
<span className="px-4 py-2 glass rounded-lg text-cyan-300 hover:text-white border border-cyan-500/30 hover:border-cyan-400/50 transition-all duration-300 text-sm font-semibold">
📁 Changer le CSV
</span>
</label>
<button
onClick={handleClearData}
className="px-4 py-2 glass rounded-lg text-red-300 hover:text-red-200 border border-red-500/30 hover:border-red-400/50 transition-all duration-300 text-sm font-semibold"
>
🗑 Supprimer les données
</button>
</div>
</div> </div>
<div className="glass-strong rounded-2xl p-8 mb-8 shadow-2xl border border-cyan-500/20"> <div className="glass-strong rounded-2xl p-8 mb-8 shadow-2xl border border-cyan-500/20">

71
lib/csv-parser-client.ts Normal file
View File

@@ -0,0 +1,71 @@
export interface Person {
nom: string;
description: string;
type: string;
}
export function parseCSVFromText(content: string): Person[] {
const lines = content.split('\n');
// Skip header line
const dataLines = lines.slice(1);
const people: Person[] = [];
for (const line of dataLines) {
if (!line.trim()) continue;
// Parse CSV line (handling quoted fields)
const fields = parseCSVLine(line);
if (fields.length >= 7) {
const nom = fields[4]?.trim() || '';
const description = fields[5]?.trim() || '';
const type = fields[6]?.trim() || '';
// Skip empty names and service accounts
if (nom && !nom.startsWith('svc.') && !nom.startsWith('!') && nom !== 'datascience') {
people.push({
nom,
description,
type,
});
}
}
}
return people;
}
function parseCSVLine(line: string): string[] {
const fields: string[] = [];
let currentField = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (char === '"') {
if (inQuotes && line[i + 1] === '"') {
// Escaped quote
currentField += '"';
i++;
} else {
// Toggle quote state
inQuotes = !inQuotes;
}
} else if (char === ',' && !inQuotes) {
// Field separator
fields.push(currentField);
currentField = '';
} else {
currentField += char;
}
}
// Add last field
fields.push(currentField);
return fields.map(field => field.trim());
}