Chiffrage BTP — Déboursé Sec :root { --bg: #0f0f0f; --surface: #1a1a1a; --surface2: #222222; --border: #2e2e2e; --accent: #f0c040; --accent2: #e87040; --text: #f0ece4; --muted: #888; --green: #5dbb7a; --red: #e05555; } * { box-sizing: border-box; margin: 0; padding: 0; } body { background: var(--bg); color: var(--text); font-family: 'DM Mono', monospace; min-height: 100vh; padding: 0 0 80px 0; } header { background: var(--surface); border-bottom: 2px solid var(--accent); padding: 20px 28px; display: flex; align-items: center; justify-content: space-between; position: sticky; top: 0; z-index: 100; } header h1 { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 1.3rem; letter-spacing: 0.05em; color: var(--accent); } header span { font-size: 0.75rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.1em; } .container { max-width: 960px; margin: 0 auto; padding: 24px 16px; } /* Corps d'état selector */ .tabs { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 24px; } .tab { padding: 7px 14px; border-radius: 4px; border: 1px solid var(--border); background: var(--surface); color: var(--muted); font-family: 'DM Mono', monospace; font-size: 0.78rem; cursor: pointer; transition: all 0.15s; text-transform: uppercase; letter-spacing: 0.06em; } .tab:hover { border-color: var(--accent); color: var(--accent); } .tab.active { background: var(--accent); color: #0f0f0f; border-color: var(--accent); font-weight: 600; } /* Section */ .section-label { font-family: 'Syne', sans-serif; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.15em; color: var(--muted); margin-bottom: 10px; } /* Table chiffrage */ .chiffrage-table { width: 100%; border-collapse: collapse; background: var(--surface); border-radius: 8px; overflow: hidden; margin-bottom: 20px; } .chiffrage-table thead tr { background: var(--surface2); border-bottom: 2px solid var(--accent); } .chiffrage-table th { padding: 10px 12px; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); text-align: left; font-weight: 500; } .chiffrage-table th:last-child { text-align: right; } .chiffrage-table td { padding: 8px 12px; border-bottom: 1px solid var(--border); font-size: 0.82rem; vertical-align: middle; } .chiffrage-table tr:last-child td { border-bottom: none; } .chiffrage-table tr:hover td { background: #1e1e1e; } input[type="number"], input[type="text"] { background: var(--bg); border: 1px solid var(--border); color: var(--text); font-family: 'DM Mono', monospace; font-size: 0.82rem; padding: 5px 8px; border-radius: 4px; width: 100%; transition: border-color 0.15s; } input[type="number"]:focus, input[type="text"]:focus { outline: none; border-color: var(--accent); } input[type="number"] { text-align: right; } .col-designation { width: 28%; } .col-unite { width: 7%; } .col-qte { width: 9%; } .col-mo { width: 11%; } .col-fourniture { width: 11%; } .col-taux { width: 10%; } .col-ds { width: 11%; text-align: right; } .col-total { width: 13%; text-align: right; } .ds-val { color: var(--accent2); font-weight: 500; } .total-val { color: var(--accent); font-weight: 600; } /* Add row button */ .btn-add { display: flex; align-items: center; gap: 8px; padding: 8px 16px; background: transparent; border: 1px dashed var(--border); color: var(--muted); font-family: 'DM Mono', monospace; font-size: 0.78rem; border-radius: 4px; cursor: pointer; transition: all 0.15s; margin-bottom: 28px; } .btn-add:hover { border-color: var(--accent); color: var(--accent); } .btn-del { background: none; border: none; color: var(--red); cursor: pointer; font-size: 1rem; padding: 2px 6px; border-radius: 3px; opacity: 0.5; transition: opacity 0.15s; } .btn-del:hover { opacity: 1; } /* Récap */ .recap { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 20px 24px; margin-top: 16px; } .recap-title { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 1rem; color: var(--accent); margin-bottom: 16px; letter-spacing: 0.05em; } .recap-rows { display: flex; flex-direction: column; gap: 6px; } .recap-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; border-radius: 4px; background: var(--surface2); font-size: 0.83rem; } .recap-row .corps { color: var(--text); } .recap-row .montant { color: var(--accent); font-weight: 600; } .recap-total { display: flex; justify-content: space-between; align-items: center; padding: 12px 14px; margin-top: 10px; background: var(--accent); border-radius: 4px; font-family: 'Syne', sans-serif; font-weight: 800; font-size: 1.05rem; color: #0f0f0f; } /* Coefficients */ .coeff-bar { display: flex; gap: 12px; flex-wrap: wrap; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 14px 20px; margin-bottom: 24px; align-items: center; } .coeff-item { display: flex; align-items: center; gap: 8px; font-size: 0.8rem; } .coeff-item label { color: var(--muted); white-space: nowrap; } .coeff-item input { width: 70px; } .coeff-sep { color: var(--border); font-size: 1.2rem; } .taux-ho { margin-left: auto; display: flex; align-items: center; gap: 8px; font-size: 0.8rem; color: var(--muted); } .taux-ho input { width: 80px; } /* Export */ .btn-export { margin-top: 20px; padding: 10px 24px; background: var(--accent); color: #0f0f0f; font-family: 'Syne', sans-serif; font-weight: 700; font-size: 0.85rem; border: none; border-radius: 4px; cursor: pointer; letter-spacing: 0.05em; transition: opacity 0.15s; } .btn-export:hover { opacity: 0.85; } @media (max-width: 600px) { .col-mo, .col-fourniture, .col-taux { display: none; } .chiffrage-table th.col-mo, .chiffrage-table th.col-fourniture, .chiffrage-table th.col-taux { display: none; } }

⬛ CHIFFRAGE BTP

Déboursé sec — Tous corps d'état
|
|
×1.265
RÉCAPITULATIF GÉNÉRAL
TOTAL DÉBOURSÉ SEC 0,00 €
TOTAL PRIX DE VENTE HT 0,00 €
const CORPS = [ { id: 'GO', label: 'Gros Œuvre', color: '#c0783a' }, { id: 'ET', label: 'Étanchéité', color: '#5a8fc0' }, { id: 'CH', label: 'Charpente', color: '#8b6fa0' }, { id: 'CO', label: 'Couverture', color: '#5dbb7a' }, { id: 'ME', label: 'Menuiserie', color: '#d4a020' }, { id: 'PL', label: 'Plâtrerie', color: '#a0a0a0' }, { id: 'CA', label: 'Carrelage', color: '#60b0a0' }, { id: 'PE', label: 'Peinture', color: '#e07070' }, { id: 'EL', label: 'Électricité', color: '#f0c040' }, { id: 'PB', label: 'Plomberie', color: '#4090d0' }, { id: 'CVC', label: 'CVC', color: '#f08040' }, { id: 'VRD', label: 'VRD', color: '#80a060' }, ]; // Data: { corpsId: [ {designation, unite, qte, tmo, pfourniture}, ... ] } let data = {}; let activeCorps = 'GO'; // Default rows per corps const defaultRows = () => [ { designation: '', unite: 'm²', qte: 0, tmo: 0, pfourniture: 0 }, ]; CORPS.forEach(c => { data[c.id] = defaultRows(); }); // Add some example rows data['GO'] = [ { designation: 'Maçonnerie parpaings 20cm', unite: 'm²', qte: 50, tmo: 1.5, pfourniture: 18 }, { designation: 'Dalle béton e=12cm', unite: 'm²', qte: 80, tmo: 0.8, pfourniture: 22 }, ]; data['PE'] = [ { designation: 'Peinture murs (2 couches)', unite: 'm²', qte: 120, tmo: 0.3, pfourniture: 4.5 }, { designation: 'Enduit plâtre', unite: 'm²', qte: 80, tmo: 0.6, pfourniture: 3 }, ]; data['EL'] = [ { designation: 'Tableau électrique 3x18', unite: 'ens.', qte: 1, tmo: 8, pfourniture: 320 }, { designation: 'Prise de courant 2P+T', unite: 'u', qte: 20, tmo: 0.5, pfourniture: 8 }, ]; function getTauxMO() { return parseFloat(document.getElementById('tauxMO').value) || 0; } function getCoeffFG() { return parseFloat(document.getElementById('coeffFG').value) || 1; } function getCoeffBen() { return parseFloat(document.getElementById('coeffBen').value) || 1; } function getCoeffTotal() { return getCoeffFG() * getCoeffBen(); } function fmt(n) { return n.toLocaleString('fr-FR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €'; } function calcDS(row) { const mo = (row.tmo || 0) * getTauxMO(); const fourniture = parseFloat(row.pfourniture) || 0; return mo + fourniture; } function calcTotal(row) { return calcDS(row) * parseFloat(row.qte || 0); } function renderTabs() { const el = document.getElementById('tabs'); el.innerHTML = CORPS.map(c => ` `).join(''); } function setCorps(id) { activeCorps = id; renderTabs(); renderTable(); } function renderTable() { const rows = data[activeCorps]; const corps = CORPS.find(c => c.id === activeCorps); const container = document.getElementById('tableContainer'); container.innerHTML = ` ${rows.map((r, i) => renderRow(r, i)).join('')}
Désignation Unité Qté T.MO (h/u) Fourniture (€/u) DS (€/u) Total DS (€)
`; } function renderRow(r, i) { const ds = calcDS(r); const total = calcTotal(r); return ` ${fmt(ds)} ${fmt(total)} `; } function updateRow(i, field, value) { data[activeCorps][i][field] = field === 'designation' || field === 'unite' ? value : parseFloat(value) || 0; refreshRowCalcs(i); updateRecap(); } function refreshRowCalcs(i) { const row = data[activeCorps][i]; const ds = calcDS(row); const total = calcTotal(row); const tbody = document.getElementById('tbody'); if (!tbody) return; const tr = tbody.children[i]; if (!tr) return; tr.querySelector('.ds-val').textContent = fmt(ds); tr.querySelector('.total-val').textContent = fmt(total); } function addRow() { data[activeCorps].push({ designation: '', unite: 'm²', qte: 0, tmo: 0, pfourniture: 0 }); renderTable(); updateRecap(); } function deleteRow(i) { data[activeCorps].splice(i, 1); if (data[activeCorps].length === 0) data[activeCorps] = defaultRows(); renderTable(); updateRecap(); } function recalcAll() { // Update coeff display document.getElementById('coeffTotal').textContent = '×' + getCoeffTotal().toFixed(3); renderTable(); updateRecap(); } function getCorpsTotal(id) { return (data[id] || []).reduce((sum, r) => sum + calcTotal(r), 0); } function updateRecap() { const recapRows = document.getElementById('recapRows'); let totalDS = 0; recapRows.innerHTML = CORPS.map(c => { const t = getCorpsTotal(c.id); totalDS += t; if (t === 0) return ''; return `
${c.label} ${fmt(t)}
`; }).join(''); document.getElementById('recapTotalDS').textContent = fmt(totalDS); document.getElementById('recapTotalPV').textContent = fmt(totalDS * getCoeffTotal()); } function exportCSV() { let csv = 'Corps d\'état;Désignation;Unité;Quantité;T.MO (h/u);Fourniture (€/u);DS unitaire (€);Total DS (€)\n'; CORPS.forEach(c => { (data[c.id] || []).forEach(r => { if (!r.designation && r.qte === 0) return; const ds = calcDS(r); const total = calcTotal(r); csv += `${c.label};${r.designation};${r.unite};${r.qte};${r.tmo};${r.pfourniture};${ds.toFixed(2)};${total.toFixed(2)}\n`; }); }); const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'chiffrage_btp.csv'; a.click(); } // Init renderTabs(); renderTable(); recalcAll();