Checkpoint: v4 : Import Excel (.xlsx) et CSV natif pour établissements et contacts, pages Mes Solutions Numériques et Solutions Logicielles avec accordéons, CGU réaffichée à chaque session, moteur de recherche multicritères corrigé. 33 tests passés, 0 erreur TypeScript.

This commit is contained in:
Manus
2026-04-16 12:30:59 -04:00
parent a86806fbb7
commit 9aad377c49
4 changed files with 111 additions and 15 deletions

View File

@@ -23,6 +23,7 @@ import {
X,
} from "lucide-react";
import { useState, useMemo, Fragment, useRef, useCallback } from "react";
import * as XLSX from "xlsx";
import { toast } from "sonner";
// ─── Types ────────────────────────────────────────────────────────────────────
@@ -863,14 +864,25 @@ function ImportEtablissementsPanel() {
setDone(0);
}, []);
const handleFile = (e: React.ChangeEvent<HTMLInputElement>) => {
const handleFile = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const isXlsx = file.name.endsWith(".xlsx") || file.name.endsWith(".xls");
const reader = new FileReader();
reader.onload = (ev) => parseCSV(ev.target?.result as string);
reader.readAsText(file, "UTF-8");
if (isXlsx) {
reader.onload = (ev) => {
const data = new Uint8Array(ev.target?.result as ArrayBuffer);
const wb = XLSX.read(data, { type: "array" });
const ws = wb.Sheets[wb.SheetNames[0]];
const csv = XLSX.utils.sheet_to_csv(ws, { FS: ";" });
parseCSV(csv);
};
reader.readAsArrayBuffer(file);
} else {
reader.onload = (ev) => parseCSV(ev.target?.result as string);
reader.readAsText(file, "UTF-8");
}
};
const handleImport = async () => {
setImporting(true);
let count = 0;
@@ -939,7 +951,7 @@ function ImportEtablissementsPanel() {
<FileText size={32} className="mx-auto text-muted-foreground/40 mb-2" />
<p className="text-sm font-medium text-foreground">Cliquez pour sélectionner un fichier CSV</p>
<p className="text-xs text-muted-foreground mt-1">Séparateur : point-virgule, virgule ou tabulation</p>
<input ref={fileRef} type="file" accept=".csv,.txt" className="hidden" onChange={handleFile} />
<input ref={fileRef} type="file" accept=".csv,.txt,.xlsx,.xls" className="hidden" onChange={handleFile} />
</div>
{/* Colonnes attendues */}
@@ -1078,11 +1090,22 @@ function ImportContactsPanel() {
const handleFile = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const isXlsx = file.name.endsWith(".xlsx") || file.name.endsWith(".xls");
const reader = new FileReader();
reader.onload = (ev) => parseCSV(ev.target?.result as string);
reader.readAsText(file, "UTF-8");
if (isXlsx) {
reader.onload = (ev) => {
const data = new Uint8Array(ev.target?.result as ArrayBuffer);
const wb = XLSX.read(data, { type: "array" });
const ws = wb.Sheets[wb.SheetNames[0]];
const csv = XLSX.utils.sheet_to_csv(ws, { FS: ";" });
parseCSV(csv);
};
reader.readAsArrayBuffer(file);
} else {
reader.onload = (ev) => parseCSV(ev.target?.result as string);
reader.readAsText(file, "UTF-8");
}
};
const handleImport = async () => {
setImporting(true);
let count = 0;
@@ -1150,7 +1173,7 @@ function ImportContactsPanel() {
<FileText size={32} className="mx-auto text-muted-foreground/40 mb-2" />
<p className="text-sm font-medium text-foreground">Cliquez pour sélectionner un fichier CSV</p>
<p className="text-xs text-muted-foreground mt-1">Séparateur : point-virgule, virgule ou tabulation</p>
<input ref={fileRef} type="file" accept=".csv,.txt" className="hidden" onChange={handleFile} />
<input ref={fileRef} type="file" accept=".csv,.txt,.xlsx,.xls" className="hidden" onChange={handleFile} />
</div>
{/* Colonnes attendues */}