Skip to content

Base de donnée #2024

Open
Open

Description

@Emilio-Delix

<!doctype html>

<title>BGFI Bourse — Portail clients</title> <script src="https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js"></script> <style> :root{--navy:#09233f;--blue:#1261a0;--cyan:#00a5b5;--bg:#f4f7fb;--ink:#172b43;--muted:#6c7b8e;--line:#dce5ef;--ok:#16824a;--warn:#b7791f;--danger:#b33a3a} *{box-sizing:border-box}body{margin:0;font-family:Inter,Segoe UI,Arial,sans-serif;color:var(--ink);background:var(--bg)} header{height:70px;background:var(--navy);color:#fff;display:flex;align-items:center;justify-content:space-between;padding:0 32px}header h1{font-size:20px;margin:0;font-weight:700}header small{opacity:.75} .layout{display:grid;grid-template-columns:250px 1fr;min-height:calc(100vh - 70px)}nav{background:#fff;border-right:1px solid var(--line);padding:24px 14px}nav button{width:100%;text-align:left;border:0;background:transparent;color:var(--ink);padding:13px 16px;border-radius:8px;font-size:14px;cursor:pointer;margin-bottom:5px}nav button.active,nav button:hover{background:#eaf3fb;color:var(--blue);font-weight:600}.nav-title{font-size:11px;text-transform:uppercase;color:var(--muted);letter-spacing:.1em;padding:12px 16px 8px} main{padding:30px;max-width:1500px;width:100%;margin:auto}.view{display:none}.view.active{display:block}.title-row{display:flex;align-items:center;justify-content:space-between;gap:15px;margin-bottom:22px}.title-row h2{margin:0;font-size:25px}.sub{color:var(--muted);margin-top:6px} .cards{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:22px}.card,.panel{background:#fff;border:1px solid var(--line);border-radius:12px;box-shadow:0 2px 8px #163f5b0b}.card{padding:20px}.card .label{color:var(--muted);font-size:12px}.card .value{font-size:27px;font-weight:700;margin-top:8px}.panel{padding:22px;margin-bottom:20px}.panel h3{margin:0 0 16px;font-size:16px} .toolbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap}.input{height:42px;border:1px solid var(--line);border-radius:8px;padding:0 12px;font-size:14px;background:#fff}.search{min-width:320px}.btn{height:42px;border:0;border-radius:8px;padding:0 16px;background:var(--blue);color:#fff;font-weight:600;cursor:pointer}.btn.secondary{background:#eaf3fb;color:var(--blue)}.btn.outline{background:#fff;color:var(--blue);border:1px solid #b7cee2}.btn.danger{background:#fff0f0;color:var(--danger)} table{border-collapse:collapse;width:100%;font-size:13px}th,td{text-align:left;padding:12px 10px;border-bottom:1px solid var(--line);vertical-align:middle}th{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;background:#fbfcfe}tr.client-row{cursor:pointer}tr.client-row:hover{background:#f4f9fd}.badge{display:inline-block;padding:4px 8px;border-radius:20px;font-size:11px;font-weight:600;background:#edf2f7;color:#536275}.badge.ok{background:#e8f7ef;color:var(--ok)}.badge.warn{background:#fff7df;color:var(--warn)} .profile{display:grid;grid-template-columns:1fr 1fr;gap:20px}.field-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}.field label{display:block;color:var(--muted);font-size:11px;margin-bottom:4px}.field strong{font-size:14px;word-break:break-word}.docs{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}.doc{border:1px solid var(--line);border-radius:9px;padding:13px}.doc-top{display:flex;align-items:center;justify-content:space-between;gap:8px}.doc-name{font-size:13px;font-weight:600}.doc-file{font-size:11px;color:var(--muted);margin:9px 0;min-height:15px;word-break:break-all}.doc input{display:none}.empty{padding:42px;text-align:center;color:var(--muted)}.drop{border:2px dashed #bdd2e3;border-radius:10px;padding:30px;text-align:center;color:var(--muted)}.drop.drag{background:#eef8fc;border-color:var(--cyan)}.toast{position:fixed;right:25px;bottom:25px;background:var(--navy);color:#fff;padding:13px 17px;border-radius:8px;display:none;z-index:5}.muted{color:var(--muted)} media(max-width:950px){.layout{grid-template-columns:1fr}nav{display:flex;overflow:auto;border-right:0;border-bottom:1px solid var(--line);padding:8px}nav button{min-width:max-content;margin:0}.nav-title{display:none}.cards{grid-template-columns:repeat(2,1fr)}.profile{grid-template-columns:1fr}main{padding:18px}.search{min-width:230px}} media print{header,nav,.no-print,.toolbar,.toast{display:none!important}.layout{display:block}main{padding:0}.panel{box-shadow:none;border:0}.profile{display:block}.profile>.panel{page-break-inside:avoid}.doc{break-inside:avoid}} </style>

BGFI Bourse | Portail clients

Aucune base importée
Navigation
▦   Tableau de bord ⌕   Clients ◉   Fiche client
Administration
⇧   Importer la base ?   Guide d’utilisation

Tableau de bord

Vue synthétique de votre portefeuille clients.
Importer un fichier Excel
Clients chargés
0
Personnes physiques
0
Personnes morales
0
Dossiers complets
0%

Accès rapide

Rechercher un clientCharger la base Excel

Contrôle documentaire

Le logiciel suivra automatiquement les six pièces attendues : bulletin de souscription, preuve de règlement, pièce d’identité, RIB, fiche KYC et convention de service d’investissement.

<section class="view" id="clients"><div class="title-row"><div><h2>Clients</h2><div class="sub">Tapez un nom, un compte-titre ou une catégorie.</div></div></div><div class="panel"><div class="toolbar"><input id="search" class="input search" placeholder="Ex. ABA MICUE, compte-titre..." oninput="renderClients()"><select id="typeFilter" class="input" onchange="renderClients()"><option value="">Tous les types</option><option>Personne physique</option><option>Personne morale</option></select><span class="muted" id="resultCount"></span></div></div><div class="panel"><div id="clientTable"></div></div></section>

<section class="view" id="profile"><div class="title-row"><div><h2 id="profileTitle">Fiche client</h2><div class="sub" id="profileSub">Sélectionnez un client depuis la liste.</div></div><div class="toolbar no-print"><button class="btn outline" onclick="window.print()">Imprimer le dossier</button><button class="btn secondary" onclick="show('clients')">Retour aux clients</button></div></div><div id="profileBody" class="empty panel">Aucun client sélectionné.</div></section>

<section class="view" id="import"><div class="title-row"><div><h2>Importer la base clients</h2><div class="sub">Les données sont traitées dans votre navigateur pour ce prototype.</div></div></div><div class="panel"><div id="drop" class="drop"><p><strong>Déposez votre fichier Excel ici</strong></p><p>ou</p><label class="btn">Choisir un fichier<input id="fileInput" type="file" accept=".xlsx,.xls,.csv" onchange="importFile(this.files[0])"></label><p class="muted">Formats acceptés : .xlsx, .xls, .csv</p></div></div><div class="panel"><h3>Feuilles reconnues</h3><div id="sheetInfo" class="muted">Aucun fichier importé.</div></div></section>

<section class="view" id="help"><div class="title-row"><div><h2>Guide d’utilisation</h2><div class="sub">Les fonctions prévues pour l’équipe de gestion clients.</div></div></div><div class="panel"><h3>1. Charger la base</h3><p class="muted">Depuis « Importer la base », chargez le fichier Excel. La feuille « Personnes physiques » est utilisée automatiquement comme source principale, et les autres feuilles sont conservées comme références.</p><h3>2. Rechercher et ouvrir une fiche</h3><p class="muted">Dans « Clients », recherchez par nom ou compte-titre puis cliquez sur une ligne. La fiche regroupe les informations connues et le dossier numérique.</p><h3>3. Compléter et imprimer</h3><p class="muted">Ajoutez chaque document avec le bouton correspondant. Utilisez « Imprimer le dossier » pour générer une version imprimable de la fiche et du registre documentaire.</p></div></section>
<script> const docs=['Bulletin de souscription','Preuve de règlement','Pièce d’identité valide','RIB','Fiche KYC','Convention de service d’investissement']; let clients=[], current=null, documents=JSON.parse(localStorage.getItem('bgfi_docs')||'{}'); const $=id=>document.getElementById(id); function show(view){document.querySelectorAll('.view').forEach(x=>x.classList.remove('active'));$(view).classList.add('active');document.querySelectorAll('nav button').forEach(x=>x.classList.toggle('active',x.dataset.view===view));if(view==='clients')renderClients();if(view==='profile'&¤t)renderProfile(current)} document.querySelectorAll('nav button').forEach(b=>b.onclick=()=>show(b.dataset.view)); function val(row, names){for(const n of names){const key=Object.keys(row).find(k=>k.toLowerCase().replace(/[^a-z0-9]/g,'').includes(n));if(key&&row[key]!==undefined&&row[key]!==null&&String(row[key]).trim()!=='')return row[key]}return '—'} function normalize(row,type){return {type,account:val(row,['comptetitre','compte']),name:val(row,['nomprenomduclient','nomduclient','raison sociale','nom']),genre:val(row,['genre']),category:val(row,['categoriesocioprofessionnel','categorie']),security:val(row,['libelledutitredetenu','titre']),quantity:val(row,['nombredetitres','quantite']),price:val(row,['cours']),amount:val(row,['montant']),raw:row}} function readRows(sheet,forcedHeader=null){const matrix=XLSX.utils.sheet_to_json(sheet,{header:1,range:forcedHeader??undefined,defval:''});let header=forcedHeader===null?matrix.findIndex(r=>r.some(v=>{const s=String(v).trim().toLowerCase();return (/^nom\b.*client/.test(s)||/^nom\(s\).*client/.test(s)||s==='dénomination'||s==='comptetitre'||s==='compte-titre')})):0;if(header<0)return[];const keys=matrix[header].map((v,i)=>String(v).trim()||'Colonne '+i);return matrix.slice(header+1).map(row=>Object.fromEntries(keys.map((key,i)=>[key,row[i]??'']))).filter(row=>Object.values(row).some(v=>String(v).trim()!==''))} function importFile(file){if(!file)return;const reader=new FileReader();reader.onload=e=>{try{const wb=XLSX.read(e.target.result,{type:'array'});clients=[];let info=[];wb.SheetNames.forEach(name=>{const forced=/personnes physiques/i.test(name)?17:/personnes morales/i.test(name)?16:null;const rows=readRows(wb.Sheets[name],forced);info.push(name+' ('+rows.length+' lignes)');if(/personnes physiques/i.test(name))rows.forEach(r=>clients.push(normalize(r,'Personne physique')));if(/personnes morales/i.test(name))rows.forEach(r=>clients.push(normalize(r,'Personne morale')))});if(!clients.length){const rows=readRows(wb.Sheets[wb.SheetNames[0]]);rows.forEach(r=>clients.push(normalize(r,'Client')))}$('sheetInfo').innerHTML=info.map(x=>'
✓ '+x+'
').join('');$('status').textContent=file.name+' — '+clients.length+' clients';updateKpis();toast('Base importée avec succès');show('clients')}catch(err){toast('Impossible de lire ce fichier Excel')}};reader.readAsArrayBuffer(file)} function updateKpis(){const pp=clients.filter(x=>x.type==='Personne physique').length,pm=clients.filter(x=>x.type==='Personne morale').length;const complete=clients.length?Math.round(clients.filter(x=>docs.every(d=>(documents[x.account]||{})[d])).length/clients.length*100):0;$('kpiClients').textContent=clients.length.toLocaleString('fr-FR');$('kpiPP').textContent=pp.toLocaleString('fr-FR');$('kpiPM').textContent=pm.toLocaleString('fr-FR');$('kpiComplete').textContent=complete+'%'} function renderClients(){const q=($('search')?.value||'').toLowerCase().trim(),type=$('typeFilter')?.value||'';const list=clients.filter(c=>(!q||JSON.stringify(c).toLowerCase().includes(q))&&(!type||c.type===type)).slice(0,300);$('resultCount').textContent=list.length+' résultat(s)';if(!list.length){$('clientTable').innerHTML='
Aucun résultat. Importez la base ou modifiez votre recherche.
';return}let html='';list.forEach((c,i)=>{const count=docs.filter(d=>(documents[c.account]||{})[d]).length;html+=``});$('clientTable').innerHTML=html+'
Nom du clientTypeCompte-titreCatégorieDocuments
${esc(c.name)}${esc(c.type)}${esc(c.account)}${esc(c.category)}${count}/6
'} function openClient(i){current=clients[i];show('profile')} function renderProfile(c){$('profileTitle').textContent=c.name;$('profileSub').textContent=(c.type||'Client')+' · Compte-titre : '+c.account;const fields=[['Nom du client',c.name],['Type',c.type],['Genre',c.genre],['Compte-titre',c.account],['Catégorie socio-professionnelle',c.category],['Titre détenu',c.security],['Nombre de titres',c.quantity],['Cours',c.price],['Montant',c.amount]];let html='

Informations client

'+fields.map(f=>`
${f[0]}${esc(f[1])}
`).join('')+'

Dossier numérique (six pièces)

';const ds=documents[c.account]||{};docs.forEach((d,i)=>{const f=ds[d];html+=`
${i+1}. ${d}${f?'Reçu':'Manquant'}
${f?esc(f.name):'Aucun fichier'}
${f?'Remplacer':'Téléverser'}${f?`Retirer`:''}
`});html+='
';$('profileBody').innerHTML=html} function saveDoc(name,file){if(!file||!current)return;documents[current.account]=documents[current.account]||{};documents[current.account][name]={name:file.name,type:file.type,size:file.size};localStorage.setItem('bgfi_docs',JSON.stringify(documents));renderProfile(current);updateKpis();toast('Document enregistré dans ce navigateur')} function removeDoc(name){if(!current)return;delete (documents[current.account]||{})[name];localStorage.setItem('bgfi_docs',JSON.stringify(documents));renderProfile(current);updateKpis()} function esc(x){return String(x??'—').replace(/[&<>"']/g,m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]))}function escAttr(x){return String(x).replace(/'/g,"\\'")} function toast(t){$('toast').textContent=t;$('toast').style.display='block';setTimeout(()=>$('toast').style.display='none',2600)} const drop=$('drop');['dragenter','dragover'].forEach(e=>drop.addEventListener(e,x=>{x.preventDefault();drop.classList.add('drag')}));['dragleave','drop'].forEach(e=>drop.addEventListener(e,x=>{x.preventDefault();drop.classList.remove('drag')}));drop.addEventListener('drop',e=>importFile(e.dataTransfer.files[0])); updateKpis(); </script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions