« Back to History
quality_add.php
|
20260722_120325.php
Initial Domain Snapshot
Copy Code
<?php /* ========================================================= quality_add.php (PAGE-ONLY) - Beam source (single/double) -> warp rows auto - Warp row: Weight[100M] (g), Crimp %, Wastage % - Weave -> default crimp (warp+weft), pills UI - Weft: auto Weight[100M] from formula: weft_weight100 = (Den/9000) × (Picks@100m × Width_m × (1+Crimp)) where Picks@100m = PPI × 39.3701 × 100 - Totals show g + kg; tips hidden (only formula line) - Save -> qualities row; extras snapshot JSON in notes ========================================================= */ error_reporting(E_ALL); ini_set('display_errors', 1); require __DIR__ . '/modules/auth/page_acl.php'; require_once __DIR__ . '/modules/activity/activity_logger.php'; $ctx = page_require_access('quality_add'); // module-scoped ACL $u = $ctx['user']; $company_id = (int)$ctx['company_id']; $pdo = isset($ctx['pdo']) && $ctx['pdo'] instanceof PDO ? $ctx['pdo'] : null; if (!$pdo) { require __DIR__ . '/core/db.php'; } /* ---------- helpers ---------- */ function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } function pv($k,$d=null){ return isset($_POST[$k])? (is_array($_POST[$k])?$_POST[$k]:trim((string)$_POST[$k])) : $d; } function nvl($v){ return ($v==='' || $v===null) ? null : $v; } /* auto code like Q0001 per company */ function gen_quality_code(PDO $pdo, int $company_id): string { $st = $pdo->prepare("SELECT MAX(CAST(SUBSTRING(quality_code,2) AS UNSIGNED)) AS mx FROM qualities WHERE company_id=? AND quality_code REGEXP '^Q[0-9]+$'"); $st->execute([$company_id]); $n = (int)($st->fetchColumn() ?: 0); return 'Q' . str_pad((string)($n+1), 4, '0', STR_PAD_LEFT); } /* ---------- schema ensure (page-scoped) ---------- */ $pdo->exec("CREATE TABLE IF NOT EXISTS qualities ( id BIGINT PRIMARY KEY AUTO_INCREMENT, company_id BIGINT NOT NULL, quality_name VARCHAR(191) NOT NULL, quality_code VARCHAR(100), category VARCHAR(100), loom_type VARCHAR(100), design_no VARCHAR(100), collection_name VARCHAR(150), epi DECIMAL(10,2), ppi DECIMAL(10,2), reed VARCHAR(50), pick VARCHAR(50), draft VARCHAR(100), denting VARCHAR(100), width_greige DECIMAL(10,2), width_finish DECIMAL(10,2), gsm DECIMAL(10,2), default_rate_m DECIMAL(10,2), pissing_cost DECIMAL(10,2) DEFAULT 0.00, processing_dye DECIMAL(10,2), warping_rate DECIMAL(10,2), sizing_rate DECIMAL(10,2), peak_value DECIMAL(10,2), wastage_percent DECIMAL(10,2), unit_label VARCHAR(30) DEFAULT 'meter', notes TEXT, active TINYINT(1) DEFAULT 1, created_by BIGINT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, INDEX idx_company (company_id), UNIQUE KEY uniq_company_code (company_id, quality_code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); /* ---------- AJAX: fetch beam rows ---------- */ if (($_GET['action'] ?? '') === 'beam') { header('Content-Type: application/json; charset=utf-8'); $bid = (int)($_GET['beam_id'] ?? 0); if ($bid<=0) { echo json_encode(['ok'=>false,'error'=>'invalid id']); exit; } try { $st = $pdo->prepare("SELECT id,name FROM beam_qualities WHERE id=:id AND company_id=:cid LIMIT 1"); $st->execute([':id'=>$bid, ':cid'=>$company_id]); $b = $st->fetch(PDO::FETCH_ASSOC); if (!$b) { echo json_encode(['ok'=>false,'error'=>'not found']); exit; } $it = $pdo->prepare("SELECT seq_no,yarn_type,denier,color,total_tar,tpm,weight,wastage FROM beam_quality_yarns WHERE beam_quality_id=:bid ORDER BY seq_no ASC, id ASC"); $it->execute([':bid'=>$bid]); $rows = $it->fetchAll(PDO::FETCH_ASSOC) ?: []; echo json_encode(['ok'=>true,'beam'=>$b,'items'=>$rows], JSON_UNESCAPED_UNICODE); } catch(Throwable $e){ echo json_encode(['ok'=>false,'error'=>$e->getMessage()]); } exit; } /* ---------- dropdowns ---------- */ // beams $beam_options = []; try{ $st = $pdo->prepare("SELECT id,name FROM beam_qualities WHERE company_id=? ORDER BY id DESC"); $st->execute([$company_id]); $beam_options = $st->fetchAll(PDO::FETCH_ASSOC) ?: []; }catch(Throwable $e){} // weft deniers $weft_deniers = []; try{ $st = $pdo->prepare("SELECT DISTINCT denier FROM yarn_company_data WHERE company_id=? AND denier IS NOT NULL AND denier<>'' ORDER BY denier"); $st->execute([$company_id]); $weft_deniers = array_map(fn($r)=>$r['denier'], $st->fetchAll(PDO::FETCH_ASSOC)); }catch(Throwable $e){} /* ---------- save ---------- */ $success = $error = null; if ($_SERVER['REQUEST_METHOD']==='POST') { $quality_name = pv('quality_name',''); if ($quality_name==='') { $error = 'Quality Name required.'; } $quality_code = pv('quality_code', null); $category = pv('quality_group', pv('category', null)); $loom_type = pv('select_machine', pv('loom_type', null)); $epi = pv('epi', null); $ppi = pv('ppi', null); $reed = pv('reed', null); $draft = pv('draft', null); $denting = pv('denting', null); $machine_cut = pv('machine_cut', null); $after_finish_cut= pv('after_finish_cut', null); // pricing/misc (optional) $production_rate_m = pv('production_rate_m', null); $sizing_rate = pv('sizing_rate', null); $peak_value = pv('peak_value', null); $wastage_percent = pv('wastage_percent', null); $unit_label = pv('unit_label', 'meter'); $pissing_cost = pv('pissing_cost', null); $processing_dye = pv('processing_dye', null); $warping_rate = pv('warping_rate', null); // extras snapshot $extras = [ 'beam_type' => pv('beam_type','double'), 'primary_beam_id' => (int)pv('primary_beam_id',0), 'secondary_beam_id' => (int)pv('secondary_beam_id',0), 'prod_type' => pv('prod_type','taka'), 'weave_type' => pv('weave_type','dobby'), 'panna' => pv('panna','single'), 'machine_cut' => $machine_cut, 'after_finish_cut' => $after_finish_cut, // warp snapshot (arrays; including crimp) 'tana_yarn_type' => pv('tana_yarn_type',[]), 'tana_denier' => pv('tana_denier',[]), 'tana_color' => pv('tana_color',[]), 'tana_taar' => pv('tana_taar',[]), 'tana_tpm' => pv('tana_tpm',[]), 'tana_weight100' => pv('tana_weight100',[]), 'tana_crimp' => pv('tana_crimp',[]), 'tana_wastage' => pv('tana_wastage',[]), // weft snapshot (single row) 'weft_denier' => pv('weft_denier',''), 'weft_pano' => pv('weft_pano',''), 'weft_peak' => pv('weft_peak',''), 'weft_tpm' => pv('weft_tpm',''), 'weft_weight100' => pv('weft_weight100',''), 'weft_crimp' => pv('weft_crimp',''), 'weft_wastage' => pv('weft_wastage',''), ]; $notes_final = "__SNAPSHOT__\n".json_encode($extras, JSON_UNESCAPED_UNICODE); if ($quality_code === null || $quality_code === '') { $quality_code = gen_quality_code($pdo, $company_id); } if (!$error) { try{ $st=$pdo->prepare("INSERT INTO qualities (company_id, quality_name, quality_code, category, loom_type, epi, ppi, reed, draft, denting, default_rate_m, pissing_cost, processing_dye, warping_rate, sizing_rate, peak_value, wastage_percent, unit_label, notes, active, created_by) VALUES (:company_id,:quality_name,:quality_code,:category,:loom_type, :epi,:ppi,:reed,:draft,:denting, :default_rate_m,:pissing_cost,:processing_dye,:warping_rate,:sizing_rate,:peak_value, :wastage_percent,:unit_label,:notes,1,:created_by)"); $st->execute([ ':company_id'=>$company_id, ':quality_name'=>$quality_name, ':quality_code'=>nvl($quality_code), ':category'=>nvl($category), ':loom_type'=>nvl($loom_type), ':epi'=>nvl($epi), ':ppi'=>nvl($ppi), ':reed'=>nvl($reed), ':draft'=>nvl($draft), ':denting'=>nvl($denting), ':default_rate_m'=>nvl($production_rate_m), ':pissing_cost'=>($pissing_cost===''?0.00:$pissing_cost), ':processing_dye'=>nvl($processing_dye), ':warping_rate'=>nvl($warping_rate), ':sizing_rate'=>nvl($sizing_rate), ':peak_value'=>nvl($peak_value), ':wastage_percent'=>nvl($wastage_percent), ':unit_label'=>($unit_label===''?'meter':$unit_label), ':notes'=>$notes_final, ':created_by'=>(int)$u['id'], ]); activity_log([ 'company_id' => $company_id ?? 0, 'user_id' => $u['id'] ?? ($_SESSION['user_id'] ?? 0), 'module' => 'quality', 'action_name' => 'create', 'entity_type' => 'quality', 'entity_id' => (int)$pdo->lastInsertId(), 'remarks' => 'Quality created' ]); $success="Quality saved."; }catch(Throwable $e){ $error="Save failed: ".h($e->getMessage()); } } } ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Add Quality • Beams + Crimped Totals</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style> :root{ --primary:#34A853; --bg:#F7FFFA; --text:#202124; --muted:#6b7280; --card:#fff; --shadow:0 8px 20px rgba(0,0,0,.06); --radius:14px; } *{box-sizing:border-box} body{margin:0;background:var(--bg);font-family:Inter,system-ui,Segoe UI,Roboto,Arial,sans-serif;color:var(--text)} .wrap{max-width:1180px;margin:22px auto;padding:0 16px} .card{background:var(--card);border-radius:var(--radius);box-shadow:var(--shadow);padding:16px;margin-bottom:14px} h2,h3{margin:0 0 10px} label{font-size:12px;color:var(--muted);display:block;margin-bottom:6px} input,select{width:100%;padding:9px 10px;border:1px solid #e5e7eb;border-radius:10px} input:read-only{background:#f7f8fa} table{width:100%;border-collapse:collapse;margin-top:8px} th,td{border:1px solid #eef2f7;padding:6px 8px;font-size:13px} .note{font-size:12px;color:#1e8e3e} .warn{font-size:12px;color:#b00020} .grid{display:grid;gap:12px} .g-2{grid-template-columns:repeat(2,minmax(0,1fr))} .g-3{grid-template-columns:repeat(3,minmax(0,1fr))} .row-flex{display:flex;align-items:center;gap:12px;flex-wrap:wrap} /* segmented radio pills */ .seg { display:flex; gap:8px; flex-wrap:wrap } .seg input[type=radio]{ display:none } .seg label{ padding:8px 12px; border:1px solid #e5e7eb; border-radius:999px; cursor:pointer; user-select:none; font-size:13px; color:#374151; background:#fff; } .seg input[type=radio]:checked + label{ border-color:#34A853; background:#eaf7ef; color:#136b2f; font-weight:600; box-shadow:0 0 0 2px rgba(52,168,83,.15) inset; } .calcline{font-size:12px;color:#4b5563;margin-top:6px} </style> </head> <body> <?php require __DIR__ . '/partials/header.php'; ?> <div class="wrap"> <div class="card"> <h2>Beam Source</h2> <?php if($success): ?><div class="note"><?= h($success) ?></div><?php endif; ?> <?php if($error): ?><div class="warn" style="background:#fde7ea;padding:10px;border-radius:10px"><?= h($error) ?></div><?php endif; ?> <div class="row-flex" style="margin-bottom:8px"> <label style="margin:0">Beam Type *</label> <div class="seg" id="beamTypeWrap"> <input id="bt-single" type="radio" name="beam_type_toggle" value="single"> <label for="bt-single">Single</label> <input id="bt-double" type="radio" name="beam_type_toggle" value="double" checked> <label for="bt-double">Double</label> </div> </div> <div class="grid g-2"> <div> <label>Primary Beam</label> <select id="primary_beam"> <option value="">— Select beam —</option> <?php foreach($beam_options as $o): ?> <option value="<?= (int)$o['id'] ?>"><?= h($o['name']) ?></option> <?php endforeach; ?> </select> </div> <div id="secBeamBox"> <label>Secondary Beam (append all rows)</label> <select id="secondary_beam"> <option value="">— Select beam —</option> <?php foreach($beam_options as $o): ?> <option value="<?= (int)$o['id'] ?>"><?= h($o['name']) ?></option> <?php endforeach; ?> </select> </div> </div> <input type="hidden" name="primary_beam_id" id="primary_beam_id"> <input type="hidden" name="secondary_beam_id" id="secondary_beam_id"> </div> <div class="card"> <h2>Quality Details</h2> <form method="post" id="qForm" autocomplete="off"> <input type="hidden" name="beam_type" id="beam_type_val" value="double"> <div class="grid g-3"> <div><label>Quality/Design Name *</label><input name="quality_name" required></div> <div><label>Quality Group</label> <select name="quality_group"><option>SAREE</option><option>DRESS</option><option>OTHER</option></select> </div> <div><label>Quality Code</label><input name="quality_code" placeholder="Leave blank to auto-generate"></div> <div><label>Select Machine</label> <select name="select_machine"><option>Looms</option><option>Rapier</option><option>Jacquard</option><option>Dobby</option></select> </div> <div><label>EPI</label><input name="epi" type="number" step="0.01"></div> <div><label>PPI</label><input name="ppi" type="number" step="0.01"></div> <div><label>Reed</label><input name="reed"></div> <div><label>Draft</label><input name="draft"></div> <div><label>Denting</label><input name="denting"></div> </div> <div class="card" style="margin:14px 0 8px;padding:14px"> <h3>Production Details</h3> <div class="grid g-3"> <div> <label>Production Type *</label> <div class="seg"> <input id="pt-taka" type="radio" name="prod_type" value="taka" checked><label for="pt-taka">Taka (In house)</label> <input id="pt-purc" type="radio" name="prod_type" value="purchase"><label for="pt-purc">Purchase/Trading</label> <input id="pt-job" type="radio" name="prod_type" value="job"><label for="pt-job">Job</label> </div> </div> <div> <label>Weave Type *</label> <div id="weaveTypeWrap" class="seg"> <input id="wv-plain" type="radio" name="weave_type" value="plain"><label for="wv-plain">Plain</label> <input id="wv-twill" type="radio" name="weave_type" value="twill"><label for="wv-twill">Twill</label> <input id="wv-satin" type="radio" name="weave_type" value="satin"><label for="wv-satin">Satin</label> <input id="wv-dobby" type="radio" name="weave_type" value="dobby" checked><label for="wv-dobby">Dobby</label> </div> </div> <div> <label>Panna *</label> <div class="seg"> <input id="pn-single" type="radio" name="panna" value="single" checked><label for="pn-single">Single</label> <input id="pn-double" type="radio" name="panna" value="double"><label for="pn-double">Double</label> <input id="pn-triple" type="radio" name="panna" value="triple"><label for="pn-triple">Triple</label> <input id="pn-four" type="radio" name="panna" value="four"><label for="pn-four">Four</label> </div> </div> <div><label>Machine Cut</label><input name="machine_cut" type="number" step="0.01" value="6.15"></div> <div><label>After Finish Cut</label><input name="after_finish_cut" type="number" step="0.01" value="6.10"></div> </div> </div> <div class="card" style="padding:10px"> <h3>Warping Detail (Tana) — from Beams</h3> <table> <thead> <tr> <th>#</th><th>Yarn Type</th><th>Denier</th><th>Color</th><th>Total Taar</th><th>TPM</th> <th>Weight [100M] (g)</th><th>Crimp %</th><th>Wastage %</th> </tr> </thead> <tbody id="tanaBody"></tbody> </table> <div class="note" id="warpTot">Warp Total = 0.000 g (0.000 kg) per 100 m</div> </div> <div class="card" style="padding:10px"> <h3>Weft Detail (Wana)</h3> <table> <thead> <tr> <th>Denier/Count</th><th>Pano (inch)</th><th>Peak (PPI)</th><th>TPM</th> <th>Weight [100M] (g)</th><th>Crimp %</th><th>Wastage %</th> </tr> </thead> <tbody> <tr> <td> <select name="weft_denier" id="weft_denier"> <option value="">— Select —</option> <?php foreach($weft_deniers as $d): ?> <option value="<?= h($d) ?>"><?= h($d) ?></option> <?php endforeach; ?> </select> </td> <td><input name="weft_pano" type="number" step="0.01" placeholder="e.g., 45"></td> <td><input name="weft_peak" type="number" step="0.01" placeholder="PPI"></td> <td><input name="weft_tpm" type="number" step="1" placeholder="loom tpm"></td> <td><input name="weft_weight100" type="number" step="0.001" readonly></td> <td><input name="weft_crimp" type="number" step="0.01"></td> <td><input name="weft_wastage" type="number" step="0.01" value="5"></td> </tr> </tbody> </table> <div id="weftCalcLine" class="calcline"></div> <div class="note" id="weftTot">Weft Total = 0.000 g (0.000 kg) per 100 m</div> </div> <div class="note" id="grandTot" style="font-weight:600">Grand Total (Warp + Weft) = 0.000 g (0.000 kg) per 100 m</div> <div style="display:flex;gap:10px;margin-top:14px"> <button type="submit" class="btn btn-primary" style="background:#34A853;color:#fff;border:0;border-radius:10px;padding:10px 14px;cursor:pointer">Save Quality</button> </div> <!-- hidden mirror fields to persist beam selection --> <input type="hidden" name="primary_beam_id" id="h_primary"> <input type="hidden" name="secondary_beam_id" id="h_secondary"> </form> </div> </div> <script> /* ---------- tiny DOM helpers ---------- */ const $ = (q,ctx=document)=>ctx.querySelector(q); const $$ = (q,ctx=document)=>Array.from(ctx.querySelectorAll(q)); /* ---------- UI: beam-type show/hide secondary ---------- */ function syncBeamBox(){ const isDouble = $('#bt-double').checked; $('#secBeamBox').style.display = isDouble ? '' : 'none'; $('#beam_type_val').value = isDouble ? 'double' : 'single'; } $$('#beamTypeWrap input[type=radio]').forEach(r=>r.addEventListener('change', syncBeamBox)); syncBeamBox(); /* ---------- fetch beam ---------- */ async function getBeam(id){ try{ const r = await fetch('?action=beam&beam_id='+encodeURIComponent(id)); const j = await r.json(); if(!j.ok){ alert('Beam fetch failed: '+j.error); return null; } return j; }catch(e){ alert('Network error'); return null; } } /* ---------- build warp rows ---------- */ const tanaBody = $('#tanaBody'); function addRows(items, append){ items = Array.isArray(items)?items:[]; if(!append){ tanaBody.innerHTML=''; } items.forEach((r)=>{ const tr = document.createElement('tr'); const ytype = (r.yarn_type??'').replace(/"/g,'"'); const den = (r.denier??'').replace(/"/g,'"'); const color = (r.color??'').replace(/"/g,'"'); const taar = Number.isFinite(+r.total_tar) ? +r.total_tar : ''; const tpm = Number.isFinite(+r.tpm) ? +r.tpm : ''; const w100 = Number.isFinite(+r.weight) ? +r.weight : ''; const wz = Number.isFinite(+r.wastage) ? +r.wastage : 0; tr.innerHTML = ` <td></td> <td><input name="tana_yarn_type[]" value="${ytype}"></td> <td><input name="tana_denier[]" value="${den}"></td> <td><input name="tana_color[]" value="${color}"></td> <td><input name="tana_taar[]" type="number" step="1" value="${taar}"></td> <td><input name="tana_tpm[]" type="number" step="1" value="${tpm}"></td> <td><input name="tana_weight100[]" type="number" step="0.001" value="${w100}" oninput="recalcTotals()"></td> <td><input name="tana_crimp[]" type="number" step="0.01" value="" oninput="recalcTotals()"></td> <td><input name="tana_wastage[]" type="number" step="0.01" value="${wz}" oninput="recalcTotals()"></td> `; tanaBody.appendChild(tr); }); // re-number $$('#tanaBody tr').forEach((tr,i)=>tr.firstElementChild.textContent = (i+1)); // defaults & totals applyWarpCrimpDefaults(); recalcTotals(); } /* beam selects */ $('#primary_beam').addEventListener('change', async ()=>{ const id = $('#primary_beam').value; $('#primary_beam_id').value=id||''; if(!id) return; const j = await getBeam(id); if(!j) return; addRows(j.items, false); }); $('#secondary_beam').addEventListener('change', async ()=>{ const id = $('#secondary_beam').value; $('#secondary_beam_id').value=id||''; if(!id) return; const j = await getBeam(id); if(!j) return; addRows(j.items, true); }); /* ---------- weave defaults + calculations ---------- */ function parseDenier(str){ if(!str) return 0; const s = String(str).trim(); if (s.includes('/')) { const left = s.split('/',1)[0].trim(); const m = left.match(/\d+(\.\d+)?/); return m ? parseFloat(m[0]) : 0; } const m = s.match(/\d+(\.\d+)?/); return m ? parseFloat(m[0]) : 0; } const CRIMP_DEFAULTS = { plain:{warp:2.0,weft:4.0}, twill:{warp:1.5,weft:3.0}, satin:{warp:1.0,weft:2.0}, dobby:{warp:2.0,weft:4.0} }; function currentWeave(){ return ($('input[name="weave_type"]:checked')?.value) || 'dobby'; } function applyWarpCrimpDefaults(){ const d = CRIMP_DEFAULTS[currentWeave()] || CRIMP_DEFAULTS.dobby; $$('input[name="tana_crimp[]"]').forEach(inp=>{ if (inp.value==='' || isNaN(parseFloat(inp.value))) inp.value = d.warp; }); } function applyWeftCrimpDefaultIfBlank(){ const d = CRIMP_DEFAULTS[currentWeave()] || CRIMP_DEFAULTS.dobby; const c = $('input[name="weft_crimp"]'); if (c && (c.value==='' || isNaN(parseFloat(c.value)))) c.value = d.weft; } $$('#weaveTypeWrap input[type="radio"]').forEach(r=>r.addEventListener('change', ()=>{ applyWarpCrimpDefaults(); applyWeftCrimpDefaultIfBlank(); recalcTotals(); })); /* weft calc block */ function recalcWeft(){ const denStr = $('#weft_denier')?.value || ''; const den = parseDenier(denStr); const panoIn = parseFloat($('input[name="weft_pano"]')?.value)||0; // inch const width_m = panoIn * 0.0254; const ppi = parseFloat($('input[name="weft_peak"]')?.value)||0; const crimp = parseFloat($('input[name="weft_crimp"]')?.value)||0; const wast = parseFloat($('input[name="weft_wastage"]')?.value)||0; const picks100 = ppi * 39.3701 * 100; const base_g = (den/9000) * (picks100 * width_m) * (1 + crimp/100); const wIn = $('input[name="weft_weight100"]'); if (wIn) wIn.value = isFinite(base_g) ? base_g.toFixed(3) : ''; const line = `Width from Pano: ${width_m.toFixed(3)} m • Picks @100m: ${Math.round(picks100).toLocaleString()} • Formula: weft_weight100 = (Den/9000) × (Picks@100m × Width_m × (1+Crimp))`; $('#weftCalcLine').textContent = line; return { base_g, wast }; } /* recalc totals (warp+weft) */ function recalcTotals(){ // warp let warp_g = 0; const w = $$('input[name="tana_weight100[]"]'); const wc = $$('input[name="tana_crimp[]"]'); const wz = $$('input[name="tana_wastage[]"]'); for(let i=0;i<w.length;i++){ const val = parseFloat(w[i].value)||0; const c = parseFloat(wc[i]?.value)||0; const z = parseFloat(wz[i]?.value)||0; warp_g += val * (1 + c/100) * (1 + z/100); } $('#warpTot').textContent = `Warp Total = ${warp_g.toFixed(3)} g (${(warp_g/1000).toFixed(3)} kg) per 100 m`; // weft const wf = recalcWeft(); const weft_total_g = (wf.base_g||0) * (1 + (wf.wast||0)/100); $('#weftTot').textContent = `Weft Total = ${weft_total_g.toFixed(3)} g (${(weft_total_g/1000).toFixed(3)} kg) per 100 m`; // grand const grand = warp_g + weft_total_g; $('#grandTot').textContent = `Grand Total (Warp + Weft) = ${grand.toFixed(3)} g (${(grand/1000).toFixed(3)} kg) per 100 m`; } /* wire up input listeners */ ['weft_denier','weft_pano','weft_peak','weft_crimp','weft_wastage'].forEach(name=>{ const el = (name==='weft_denier') ? $('#weft_denier') : $(`input[name="${name}"]`); if(el){ el.addEventListener('input', recalcTotals); el.addEventListener('change', recalcTotals); } }); $$('input[name="tana_weight100[]"], input[name="tana_crimp[]"], input[name="tana_wastage[]"]').forEach(el=>{ el.addEventListener('input', recalcTotals); }); /* first paint */ applyWarpCrimpDefaults(); applyWeftCrimpDefaultIfBlank(); recalcTotals(); /* keep beam ids on submit */ $('#qForm').addEventListener('submit', ()=>{ $('#h_primary').value = $('#primary_beam').value || ''; $('#h_secondary').value = $('#secondary_beam').value || ''; }); </script> </body> </html>