Checkpoint: Ajout du support XLSX natif via la librairie SheetJS (xlsx). Un helper fileToRows() unifie la lecture CSV et XLSX : pour XLSX, lecture via arrayBuffer + XLSX.read + sheet_to_json ; pour CSV, détection du séparateur et split classique. Le parsing ISI-APP fonctionne désormais identiquement sur les deux formats. Les métadonnées d'import incluent le format détecté (isi-app ou classique).
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
// Design: Corporate Modernism — Itinova Budget SI
|
||||
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import * as XLSX from 'xlsx';
|
||||
import {
|
||||
Upload,
|
||||
X,
|
||||
@@ -21,6 +22,25 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
// ─── Helper : lecture fichier → tableau de lignes (CSV ou XLSX) ────────────────
|
||||
async function fileToRows(file: File): Promise<string[][]> {
|
||||
const isXlsx = file.name.match(/\.(xlsx|xls)$/i);
|
||||
if (isXlsx) {
|
||||
const buffer = await file.arrayBuffer();
|
||||
const wb = XLSX.read(buffer, { type: 'array', cellDates: true });
|
||||
const ws = wb.Sheets[wb.SheetNames[0]];
|
||||
// sheet_to_json avec header:1 retourne un tableau de tableaux
|
||||
const rows = XLSX.utils.sheet_to_json<string[]>(ws, { header: 1, defval: '' });
|
||||
return rows.map(r => r.map(c => String(c ?? '').trim()));
|
||||
} else {
|
||||
// CSV : détection du séparateur
|
||||
const text = await file.text();
|
||||
const lines = text.split('\n').filter(l => l.trim());
|
||||
const sep = lines[0].includes(';') ? ';' : ',';
|
||||
return lines.map(l => l.split(sep).map(c => c.trim().replace(/"/g, '').replace(/[\r\n]/g, '')));
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface Etablissement {
|
||||
@@ -274,17 +294,15 @@ export function ImportModal({ open, onClose }: ImportModalProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
const text = await file.text();
|
||||
const lines = text.split('\n').filter(l => l.trim());
|
||||
if (lines.length < 2) {
|
||||
// Lecture unifiée CSV + XLSX via SheetJS
|
||||
const rows = await fileToRows(file);
|
||||
if (rows.length < 2) {
|
||||
setInventaireStatus('error');
|
||||
setInventaireMsg('Fichier vide ou format invalide (moins de 2 lignes)');
|
||||
return;
|
||||
}
|
||||
|
||||
// Détection du séparateur
|
||||
const sep = lines[0].includes(';') ? ';' : ',';
|
||||
const rawHeaders = lines[0].split(sep).map(h => h.trim().toLowerCase().replace(/"/g, '').replace(/[\r\n]/g, ''));
|
||||
const rawHeaders = rows[0].map(h => h.toLowerCase().replace(/[\r\n]/g, ''));
|
||||
|
||||
// ── Format ISI-APP : extraction par libellé et entité ──────────────────
|
||||
// Colonne "libellé" : contient le code poste (ex: 1083MAS-F4833)
|
||||
@@ -344,8 +362,7 @@ export function ImportModal({ open, onClose }: ImportModalProps) {
|
||||
|
||||
// Extraction des établissements uniques depuis l'inventaire
|
||||
const etabMap = new Map<string, { nom: string; groupe?: string; ville?: string }>();
|
||||
for (const line of lines.slice(1)) {
|
||||
const cols = line.split(sep).map(c => c.trim().replace(/"/g, '').replace(/[\r\n]/g, ''));
|
||||
for (const cols of rows.slice(1)) {
|
||||
|
||||
let code = '';
|
||||
let nom = '';
|
||||
@@ -412,11 +429,12 @@ export function ImportModal({ open, onClose }: ImportModalProps) {
|
||||
saveEtablissements(merged);
|
||||
|
||||
// Enregistrement des métadonnées du fichier importé
|
||||
const nbLignes = rows.length - 1;
|
||||
localStorage.setItem('budgetsi_inventaire_import', JSON.stringify({
|
||||
filename: file.name,
|
||||
date: new Date().toISOString(),
|
||||
size: file.size,
|
||||
nbLignes: lines.length - 1,
|
||||
nbLignes,
|
||||
nbEtablissements: etabMap.size,
|
||||
nbFixes: useIsiFormat ? nbFixes : null,
|
||||
nbPortables: useIsiFormat ? nbPortables : null,
|
||||
@@ -424,7 +442,7 @@ export function ImportModal({ open, onClose }: ImportModalProps) {
|
||||
}));
|
||||
|
||||
const details = [
|
||||
`${lines.length - 1} poste${lines.length > 2 ? 's' : ''}`,
|
||||
`${nbLignes} poste${nbLignes > 1 ? 's' : ''}`,
|
||||
`${etabMap.size} établissement${etabMap.size > 1 ? 's' : ''} détectés`,
|
||||
nbNouveaux > 0 ? `${nbNouveaux} nouveau${nbNouveaux > 1 ? 'x' : ''}` : null,
|
||||
nbMisAJour > 0 ? `${nbMisAJour} mis à jour` : null,
|
||||
|
||||
Reference in New Issue
Block a user