feat: icone verte si solution utilisee par au moins 1 etablissement

This commit is contained in:
Manus Agent
2026-04-24 05:45:56 -04:00
parent 3bcd8141f9
commit 968b6ca2a3
3 changed files with 119 additions and 10 deletions

View File

@@ -195,8 +195,8 @@ export default function SolutionsLogicielles() {
className="flex-1 flex items-center justify-between px-5 py-4 text-left hover:bg-muted/30 transition-colors"
>
<div className="flex items-center gap-4 min-w-0">
<div className="w-9 h-9 rounded-lg bg-blue-500/10 flex items-center justify-center flex-shrink-0">
<Package size={16} className="text-blue-600" />
<div className={`w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0 ${sol.nbEtablissements > 0 ? 'bg-green-500/10' : 'bg-blue-500/10'}`}>
<Package size={16} className={sol.nbEtablissements > 0 ? 'text-green-600' : 'text-blue-600'} />
</div>
<div className="min-w-0">
<div className="font-semibold text-foreground truncate">{sol.solutionNom}</div>
@@ -312,7 +312,7 @@ export default function SolutionsLogicielles() {
<>
<td className={`px-4 py-3 font-semibold text-foreground align-top ${rowBgSpan}`} rowSpan={sol.etablissements.length}>
<div className="flex items-center gap-2">
<Package size={13} className="text-blue-500 flex-shrink-0" />
<Package size={13} className={sol.nbEtablissements > 0 ? 'text-green-600 flex-shrink-0' : 'text-blue-500 flex-shrink-0'} />
{sol.solutionNom}
</div>
</td>

98
export_sandbox_data.mjs Normal file
View File

@@ -0,0 +1,98 @@
/**
* Export des données de la base Sandbox SONUM vers un script SQL d'injection.
* Tables exportées : blocs_fonctionnels, editeurs, etablissements, solutions,
* logiciels_etablissements, consultations, demandes_contact, user_etablissements
* Tables exclues : users, local_credentials, __drizzle_migrations
*/
import mysql from 'mysql2/promise';
import fs from 'fs';
const TABLES_TO_EXPORT = [
'blocs_fonctionnels',
'editeurs',
'etablissements',
'solutions',
'logiciels_etablissements',
'consultations',
'demandes_contact',
'user_etablissements',
];
const conn = await mysql.createConnection(process.env.DATABASE_URL);
let sql = `-- Export données SONUM Sandbox → VPS
-- Généré le ${new Date().toISOString()}
-- Tables : ${TABLES_TO_EXPORT.join(', ')}
-- ATTENTION : exclut users, local_credentials, __drizzle_migrations
SET FOREIGN_KEY_CHECKS = 0;
SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';
`;
for (const table of TABLES_TO_EXPORT) {
console.log(`Exporting table: ${table}...`);
// Récupérer la structure de la table
const [[createResult]] = await conn.execute(`SHOW CREATE TABLE \`${table}\``);
const createSQL = createResult['Create Table'];
// Récupérer les données
const [rows] = await conn.execute(`SELECT * FROM \`${table}\``);
sql += `-- ─────────────────────────────────────────────────────────────────\n`;
sql += `-- Table: ${table} (${rows.length} lignes)\n`;
sql += `-- ─────────────────────────────────────────────────────────────────\n`;
sql += `TRUNCATE TABLE \`${table}\`;\n\n`;
if (rows.length === 0) {
sql += `-- (aucune donnée)\n\n`;
continue;
}
// Générer les INSERT par lots de 50
const columns = Object.keys(rows[0]);
const colList = columns.map(c => `\`${c}\``).join(', ');
const BATCH_SIZE = 50;
for (let i = 0; i < rows.length; i += BATCH_SIZE) {
const batch = rows.slice(i, i + BATCH_SIZE);
const values = batch.map(row => {
const vals = columns.map(col => {
const v = row[col];
if (v === null || v === undefined) return 'NULL';
if (typeof v === 'number' || typeof v === 'bigint') return String(v);
if (typeof v === 'boolean') return v ? '1' : '0';
if (v instanceof Date) return `'${v.toISOString().slice(0, 19).replace('T', ' ')}'`;
// Escape string
const escaped = String(v)
.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\0/g, '\\0');
return `'${escaped}'`;
});
return `(${vals.join(', ')})`;
});
sql += `INSERT INTO \`${table}\` (${colList}) VALUES\n${values.join(',\n')};\n`;
}
sql += `\n`;
}
sql += `SET FOREIGN_KEY_CHECKS = 1;\n`;
sql += `-- Fin de l'export\n`;
await conn.end();
const outputPath = '/home/ubuntu/sonum_data_export.sql';
fs.writeFileSync(outputPath, sql, 'utf8');
const stats = fs.statSync(outputPath);
console.log(`\nExport terminé : ${outputPath}`);
console.log(`Taille : ${(stats.size / 1024).toFixed(1)} KB`);
console.log(`\nRésumé par table :`);
for (const table of TABLES_TO_EXPORT) {
const matches = sql.match(new RegExp(`-- Table: ${table} \\((\\d+) lignes\\)`));
if (matches) console.log(` ${table}: ${matches[1]} lignes`);
}

View File

@@ -4,22 +4,33 @@
*/
import mysql from 'mysql2/promise';
import bcrypt from 'bcrypt';
import { randomUUID } from 'crypto';
const conn = await mysql.createConnection(process.env.DATABASE_URL);
// Vérifier si adminItinova existe déjà
const [rows] = await conn.execute("SELECT id FROM users WHERE login = 'adminItinova' LIMIT 1");
const [rows] = await conn.execute("SELECT id, openId FROM users WHERE login = 'adminItinova' LIMIT 1");
if (rows.length > 0) {
const existing = rows[0];
// Si openId est null, le corriger pour permettre la connexion JWT
if (!existing.openId) {
const openId = `local:adminItinova:${randomUUID()}`;
await conn.execute("UPDATE users SET openId = ? WHERE id = ?", [openId, existing.id]);
console.log(`Compte adminItinova : openId corrigé (id=${existing.id})`);
} else {
console.log('Compte adminItinova déjà existant, seed ignoré.');
}
await conn.end();
process.exit(0);
}
// Créer l'utilisateur adminItinova
// Créer l'utilisateur adminItinova avec un openId local unique
const openId = `local:adminItinova:${randomUUID()}`;
const [result] = await conn.execute(
`INSERT INTO users (login, email, firstName, lastName, name, role, sonumRole, isActive, loginMethod, cguAccepted, lastSignedIn, createdAt, updatedAt)
VALUES (?, ?, ?, ?, ?, 'admin', 'gestionnaire', 1, 'local', 1, NOW(), NOW(), NOW())`,
['adminItinova', 'adminItinova@santinova-soft.org', 'Admin', 'SONUM', 'Admin SONUM']
`INSERT INTO users (login, email, firstName, lastName, name, openId, role, sonumRole, isActive, loginMethod, cguAccepted, lastSignedIn, createdAt, updatedAt)
VALUES (?, ?, ?, ?, ?, ?, 'admin', 'gestionnaire', 1, 'local', 1, NOW(), NOW(), NOW())`,
['adminItinova', 'adminItinova@santinova-soft.org', 'Admin', 'SONUM', 'Admin SONUM', openId]
);
const userId = result.insertId;
@@ -31,5 +42,5 @@ await conn.execute(
[userId, hash]
);
console.log(`Compte adminItinova créé avec succès (id=${userId})`);
console.log(`Compte adminItinova créé avec succès (id=${userId}, openId=${openId})`);
await conn.end();