« Back to History
production_entry_2.php
|
20260723_000646.php
Initial Domain Snapshot
Copy Code
<?php /* ====================================================================== Loom Production Entry (Desktop) — Top Stats + Target Meter (created_at based) Scope: File-local only. No global/base edits. ====================================================================== */ header('X-Frame-Options: SAMEORIGIN'); error_reporting(E_ALL); ini_set('display_errors','0'); if (isset($_GET['debug']) && $_GET['debug']=='1') ini_set('display_errors','1'); require __DIR__ . '/modules/auth/auth.php'; require_login(); $u = auth_user(); $COMPANY_ID = (int)$u['company_id']; $USER_ID = (int)$u['id']; function jexit($arr,$code=200){ if (!headers_sent()){ header('Content-Type: application/json; charset=utf-8'); http_response_code($code); } echo json_encode($arr, JSON_UNESCAPED_UNICODE); exit; } $IS_AJAX = !empty($_GET['act']); /* Fatal catcher */ register_shutdown_function(function()use($IS_AJAX){ $e=error_get_last(); if($e && in_array($e['type']??0,[E_ERROR,E_PARSE,E_CORE_ERROR,E_COMPILE_ERROR])){ if($IS_AJAX){ jexit(['ok'=>false,'msg'=>'Fatal','detail'=>$e['message'],'file'=>basename($e['file']??''),'line'=>$e['line']??null],500); } if(!headers_sent()){ http_response_code(500); header('Content-Type:text/html;charset=utf-8'); } $m=htmlspecialchars($e['message']??'',ENT_QUOTES,'UTF-8'); $f=htmlspecialchars(basename($e['file']??''),ENT_QUOTES,'UTF-8'); $l=(int)($e['line']??0); echo "<div style='font:14px/1.4 system-ui;max-width:900px;margin:40px auto;padding:16px;border:1px solid #f3d27a;background:#fff8d6;border-radius:12px'> <b>Fatal Error</b><div style='margin-top:6px'>$m</div> <div style='color:#666'>in <code>$f</code> at line <b>$l</b></div> </div>"; exit; } }); /* DB bootstrap */ $pdo = $GLOBALS['pdo'] ?? null; if (!$pdo) { require __DIR__ . '/core/db.php'; } $pdo = $GLOBALS['pdo']; if (!$pdo) jexit(['ok'=>false,'msg'=>'DB connect fail'],500); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); /* ====================================================================== AJAX router — ?act=... ====================================================================== */ $act = $_GET['act'] ?? ''; if ($act) { $company_id = $COMPANY_ID; $get_khata = function($code) use($pdo,$company_id){ $s=$pdo->prepare("SELECT id, code, machine_from, machine_to FROM khatas WHERE company_id=? AND code=? AND is_active=1 LIMIT 1"); $s->execute([$company_id, trim($code)]); return $s->fetch(PDO::FETCH_ASSOC); }; try { switch ($act) { /* ----------- FIXED: count created today, not entry_date ----------- */ case 'today_stats': { $s = $pdo->prepare(" SELECT COUNT(*) FROM loom_production_entry WHERE company_id = ? AND DATE(created_at) = CURDATE() "); $s->execute([$company_id]); $cnt = (int)$s->fetchColumn(); jexit(['ok'=>true,'takas_today'=>$cnt,'today'=>date('Y-m-d')]); } case 'target_progress': { $khata_id = (int)($_GET['khata_id'] ?? 0); $start = (int)($_GET['start'] ?? 0); $end = (int)($_GET['end'] ?? 0); if($khata_id<=0 || $start<=0 || $end<$start) jexit(['ok'=>false,'msg'=>'Bad params'],400); $q=$pdo->prepare("SELECT COUNT(*) FROM loom_production_entry WHERE company_id=? AND khata_id=? AND taka_no BETWEEN ? AND ?"); $q->execute([$company_id,$khata_id,$start,$end]); $done=(int)$q->fetchColumn(); jexit(['ok'=>true,'done'=>$done]); } case 'options': { $khata_code = $_GET['khata'] ?? ''; if ($khata_code==='') jexit(['ok'=>false,'msg'=>'Missing khata']); $kh = $get_khata($khata_code); if(!$kh) jexit(['ok'=>false,'msg'=>"Khata not found: $khata_code"]); $from=(int)$kh['machine_from']; $to=(int)$kh['machine_to']; $machines=[]; for($i=$from; $i<=$to; $i++) $machines[]=$i; jexit(['ok'=>true,'khata'=>['id'=>(int)$kh['id'],'from'=>$from,'to'=>$to],'machines'=>$machines]); } case 'machine_defaults': { $khata_code = $_GET['khata'] ?? ''; $machine_no = (int)($_GET['machine'] ?? 0); if ($khata_code==='' || $machine_no<=0) jexit(['ok'=>false,'msg'=>'Missing params']); $kh = $get_khata($khata_code); if(!$kh) jexit(['ok'=>false,'msg'=>"Khata not found: $khata_code"]); $khata_id = (int)$kh['id']; // Assigned to this machine $sql = "SELECT id, karigar_name, CAST(machine_from AS UNSIGNED) AS mf, CAST(machine_to AS UNSIGNED) AS mt FROM loom_karigar_master WHERE company_id=:c AND khata_id=:k AND is_active=1 AND :m BETWEEN CAST(IFNULL(machine_from,:m0) AS UNSIGNED) AND CAST(IFNULL(machine_to, :m1) AS UNSIGNED) ORDER BY (machine_from IS NULL), mf, karigar_name"; $st=$pdo->prepare($sql); $st->execute([':c'=>$company_id, ':k'=>$khata_id, ':m'=>$machine_no, ':m0'=>$machine_no, ':m1'=>$machine_no]); $assigned=[]; while($r=$st->fetch(PDO::FETCH_ASSOC)){ $label=$r['karigar_name']; if ($r['mf'] && $r['mt']) $label.=" {$r['mf']}-{$r['mt']}"; $assigned[]=['id'=>(int)$r['id'],'name'=>$label]; } // All in khata $st2=$pdo->prepare("SELECT id, karigar_name, CAST(machine_from AS UNSIGNED) AS mf, CAST(machine_to AS UNSIGNED) AS mt FROM loom_karigar_master WHERE company_id=? AND khata_id=? AND is_active=1 ORDER BY (machine_from IS NULL), mf, karigar_name"); $st2->execute([$company_id,$khata_id]); $khata_karigars=[]; while($r2=$st2->fetch(PDO::FETCH_ASSOC)){ $label=$r2['karigar_name']; if ($r2['mf'] && $r2['mt']) $label.=" {$r2['mf']}-{$r2['mt']}"; $khata_karigars[]=['id'=>(int)$r2['id'],'name'=>$label]; } // Fixed (recent top) $fixed=[]; $last_entry_date=null; $q=$pdo->prepare("SELECT entry_date, lines_json FROM loom_production_entry WHERE company_id=? AND khata_id=? AND machine_id=? ORDER BY entry_date DESC, id DESC LIMIT 1"); $q->execute([$company_id,$khata_id,$machine_no]); if($row=$q->fetch(PDO::FETCH_ASSOC)){ $last_entry_date = $row['entry_date'] ?? null; $lines=json_decode($row['lines_json']??'[]',true); if(is_array($lines)){ usort($lines,fn($a,$b)=>($b['meter']??0)<=>($a['meter']??0)); $seen = []; foreach($lines as $ln){ if(isset($ln['karigar_id'])){ $kid = (int)$ln['karigar_id']; if(!isset($seen[$kid])){ $fixed[] = $kid; $seen[$kid]=1; } if(count($fixed)>=2) break; } } } } if(count($fixed)<2){ foreach($assigned as $opt){ if(!in_array($opt['id'],$fixed,true)) $fixed[]=$opt['id']; if(count($fixed)>=2) break; } } jexit([ 'ok'=>true, 'khata'=>['id'=>(int)$kh['id'],'from'=>(int)$kh['machine_from'],'to'=>(int)$kh['machine_to']], 'all_karigars'=>$assigned, 'khata_karigars'=>$khata_karigars, 'fixed_karigars'=>$fixed, 'last_entry_date'=>$last_entry_date, ]); } case 'taka_next': { $khata_id = (int)($_GET['khata_id'] ?? 0); if ($khata_id<=0) jexit(['ok'=>false,'msg'=>'Missing khata_id']); $s=$pdo->prepare("SELECT COALESCE(MAX(taka_no),0) FROM loom_production_entry WHERE company_id=? AND khata_id=?"); $s->execute([$company_id,$khata_id]); $last=(int)$s->fetchColumn(); jexit(['ok'=>true,'next_taka_no'=>$last+1]); } case 'quality_by_machine': { $machine_no = (int)($_GET['machine_no'] ?? 0); if ($machine_no<=0) jexit(['ok'=>false,'msg'=>'Missing machine_no']); $code = sprintf('loom %03d',$machine_no); $s=$pdo->prepare("SELECT quality FROM loom_quality_master WHERE company_id=? AND (machine_code=? OR machine_id=?) ORDER BY updated_at DESC, id DESC LIMIT 1"); $s->execute([$company_id,$code,$machine_no]); if($row=$s->fetch(PDO::FETCH_ASSOC)){ $qname = trim($row['quality'] ?? ''); $st=$pdo->prepare("SELECT quality_id FROM quality_rates WHERE company_id=? AND TRIM(LOWER(quality_name))=TRIM(LOWER(?)) ORDER BY updated_at DESC,id DESC LIMIT 1"); $st->execute([$company_id,$qname]); $qid=(int)$st->fetchColumn(); jexit(['ok'=>true,'quality'=>$qname,'quality_id'=>$qid]); } else { jexit(['ok'=>false,'msg'=>'Quality not found']); } } case 'submit': { $in = json_decode(file_get_contents('php://input'), true); if(!$in || !isset($in['entry']) || !is_array($in['entry'])) jexit(['ok'=>false,'msg'=>'Bad JSON'],400); $e = $in['entry']; $date_for_header = trim($e['entry_date'] ?? ''); $machine = (int)($e['machine_id'] ?? 0); $khata = (int)($e['khata_id'] ?? 0); $qName = trim($e['quality_name'] ?? ''); $qId = (int)($e['quality_id'] ?? 0); $taka = (int)($e['taka_no'] ?? 0); $extra = (float)($e['extra_mtr'] ?? 0); $remark = trim($e['remark'] ?? ''); $lines = $e['lines'] ?? []; if(!$date_for_header || !$machine || !$khata || empty($lines)){ jexit(['ok'=>false,'msg'=>'Missing date/machine/khata/lines'],400); } if($qId<=0){ $st=$pdo->prepare("SELECT quality_id FROM quality_rates WHERE company_id=? AND TRIM(LOWER(quality_name))=TRIM(LOWER(?)) ORDER BY updated_at DESC,id DESC LIMIT 1"); $st->execute([$company_id,$qName]); $qId=(int)$st->fetchColumn(); if($qId<=0){ jexit(['ok'=>false,'msg'=>'Quality ID not found for name: '.$qName],400); } } $norm=[]; $sum=0; foreach($lines as $ln){ $kid = (int)($ln['karigar_id'] ?? 0); $m = (float)($ln['meter'] ?? 0); $ld = trim((string)($ln['date'] ?? $date_for_header)); if($kid>0 && $m>0 && $ld!==''){ $norm[] = ['karigar_id'=>$kid,'meter'=>round($m,2),'date'=>$ld]; $sum += $m; } } if(empty($norm)) jexit(['ok'=>false,'msg'=>'No valid karigar lines'],400); if($taka<=0){ $q=$pdo->prepare("SELECT COALESCE(MAX(taka_no),0) FROM loom_production_entry WHERE company_id=? AND khata_id=?"); $q->execute([$company_id,$khata]); $taka=(int)$q->fetchColumn()+1; } $meter_total=round($sum+$extra,2); $ins=$pdo->prepare("INSERT INTO loom_production_entry (company_id,entry_date,machine_id,khata_id,quality_id,taka_no,pbn,sbn,extra_mtr,lines_json,meter_total,weight,remark,created_by) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); $ins->execute([ $company_id,$date_for_header,$machine,$khata,$qId,$taka,'','',$extra, json_encode($norm,JSON_UNESCAPED_UNICODE),$meter_total,0,$remark,$USER_ID ]); jexit([ 'ok'=>true, 'inserted_id'=>$pdo->lastInsertId(), 'taka_no'=>$taka, 'quality_id'=>$qId, 'meter_total'=>$meter_total, 'quality_name'=>$qName ]); } default: jexit(['ok'=>false,'msg'=>'Unknown act'],404); } } catch(Throwable $e){ jexit(['ok'=>false,'msg'=>'Server error','detail'=>$e->getMessage()],500); } } ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Loom Production Entry (Desktop)</title> <style> :root{ --green-600:#2C8F47; --green-700:#1E5A2C; --blue-50:#F0F7FF; --blue-700:#1565C0; --gray-900:#111827; --gray-700:#374151; --gray-500:#6B7280; --gray-300:#E5E7EB; --gray-100:#F3F4F6; --app-bg:#F9FAFB; --card:#FFFFFF; --line:var(--gray-300); --brand:var(--green-600); --brand-strong:var(--green-700); --shadow:0 10px 28px rgba(17,24,39,.10); --shadow-soft:0 6px 16px rgba(17,24,39,.08); } *,:before,:after{box-sizing:border-box} html,body{height:100%} body{margin:0;background:var(--app-bg);color:var(--gray-900);font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,Arial} .page{width:100%;max-width:1720px;margin:22px auto;padding:0 22px;display:grid;grid-template-columns:1fr;gap:22px} .card{background:var(--card);border:1px solid var(--line);border-radius:18px;box-shadow:var(--shadow)} .card .hd{padding:14px 18px;border-bottom:1px solid var(--line);font-weight:800;background:linear-gradient(0deg,#eaf6ff,#ffffff);color:var(--blue-700);border-radius:18px 18px 0 0;display:flex;align-items:center;justify-content:space-between} .card .bd{padding:16px} .linkbar{display:flex;gap:8px;flex-wrap:wrap} .link-btn{border:none;background:var(--blue-50);color:var(--blue-700);font-weight:700;padding:8px 10px;border-radius:10px;cursor:pointer;box-shadow:var(--shadow-soft)} .label{font-size:12px;color:var(--gray-500);margin-bottom:6px} .input,.select,button.btn{border:1px solid var(--line);background:#fff;border-radius:12px;padding:12px 14px;font-size:16px;line-height:24px;min-height:44px;width:100%} .btn{cursor:pointer;display:inline-flex;align-items:center;gap:10px;transition:.15s;box-shadow:var(--shadow-soft);font-weight:800} .btn-primary{background:var(--brand);color:#fff;border:none;padding:12px 20px;border-radius:12px} .btn-ghost{background:#fff} /* Compact topbar layout */ .topbar{display:flex;gap:12px;align-items:end;flex-wrap:nowrap;overflow-x:auto;padding-bottom:4px} .field{display:flex;flex-direction:column;gap:6px;flex:0 0 auto} .w-110{width:110px}.w-120{width:120px}.w-130{width:130px}.w-140{width:140px}.w-160{width:160px}.w-180{width:180px}.w-200{width:200px}.w-220{width:220px}.w-240{width:240px} /* Dark total chip */ .input.total-strong{background:var(--gray-900);color:#fff;border-color:#111827;font-weight:900;text-align:right;letter-spacing:.5px} .table-wrap{overflow:auto;border:1px solid var(--line);border-radius:14px;background:#fff} table.matrix{min-width:1200px;width:100%;border-collapse:separate;border-spacing:0} table.matrix th,table.matrix td{padding:10px;border-bottom:1px solid var(--line)} table.matrix thead th{background:#f4faf6;color:var(--brand-strong);font-weight:800} .date-cell{min-width:160px!important} .date-chip{display:flex;align-items:center;gap:8px} .date-chip input{flex:1} .date-kill{background:#fff;border:1px solid var(--line);border-radius:8px;padding:4px 10px;cursor:pointer;margin-left:auto} .footerbar{margin-top:14px;background:#fff;border:1px solid var(--line);border-radius:14px;padding:12px} .remark-wrap{flex:1 1 100%} /* ======= TOP STATS + TARGET METER ======= */ .stats{background:linear-gradient(0deg,#f0f7ff,#ffffff);border:1px solid var(--line);border-radius:16px;padding:12px 14px;display:flex;gap:16px;align-items:center;box-shadow:var(--shadow-soft)} .stat-box{display:flex;flex-direction:column;gap:2px} .stat-title{font-size:12px;color:var(--gray-500)} .stat-num{font-weight:900;font-size:20px} .meter-wrap{flex:1;min-width:380px} .meter{position:relative;height:18px;background:#eef3ff;border:1px solid #d7e2ff;border-radius:999px;overflow:hidden} .meter-fill{position:absolute;left:0;top:0;height:100%;width:0;background:linear-gradient(90deg,#2C8F47,#1E5A2C);transition:width .6s ease} .meter-meta{margin-top:6px;font-size:12px;color:#374151} .petal{position:fixed;bottom:12px;width:10px;height:10px;background:radial-gradient(circle at 30% 30%,#ffb7c5, #ff6fa1 60%, #ff3f86 100%);border-radius:50%;opacity:.9;pointer-events:none;animation:floatUp .9s ease-out forwards} @keyframes floatUp{0%{transform:translateY(0) scale(.9);opacity:1}100%{transform:translateY(-120px) scale(1.2);opacity:0}} /* FIX: Submit visible (override button.btn default) */ button.btn.btn-primary{ background: var(--brand); color:#fff; border:none; } button.btn.btn-primary:hover{ filter: brightness(0.95); } </style> </head> <body> <div class="page"> <!-- TOP STATS LINE (replaces old header) --> <div class="stats" id="topStats"> <div class="stat-box" style="min-width:120px"> <div class="stat-title">Today’s Taka Entries</div> <div class="stat-num"><span id="todayTakas">0</span></div> </div> <div class="meter-wrap"> <div class="meter"><div class="meter-fill" id="targetFill"></div></div> <div class="meter-meta" id="targetMeta">No target set</div> </div> <div style="display:flex;gap:8px;align-items:end"> <div class="field w-140"> <div class="label">Set Target (takas)</div> <input id="targetCount" type="number" min="1" step="1" class="input" placeholder="e.g. 10" /> </div> <button id="btnSetTarget" class="btn btn-primary">Set Target</button> </div> </div> <div class="main"> <div class="card" id="cardMatrix"> <div class="hd"> <div>Entry Matrix</div> <div class="linkbar"> <button id="btnKarigarList" class="link-btn">Karigar List</button> <button id="btnSeePatia" class="link-btn">See Patia</button> <button id="btnTakaEdit" class="link-btn">Taka Edit</button> </div> </div> <div class="bd"> <!-- COMPACT TOP BAR --> <div class="topbar"> <div class="field w-140"> <div class="label">Khata</div> <select id="khata" class="select"> <option value="">Select</option> <option value="MST1">MST1</option> <option value="MST2A">MST2A</option> <option value="MST2B">MST2B</option> </select> </div> <div class="field w-110"><div class="label">Machine</div><select id="machine" class="select"><option value="">—</option></select></div> <div class="field w-120"><div class="label">Taka</div><input id="taka_no" class="input" placeholder="auto" /></div> <div class="field w-220"><div class="label">Quality</div><input id="quality_name" class="input" placeholder="auto" /><input id="quality_id" type="hidden" value="0" /></div> <div class="field w-180"><div class="label">Weight</div><input id="weight_top" class="input" value="auto by quality" readonly /></div> <div class="field w-120"><div class="label">Extra</div><input id="extra_mtr_global" type="number" step="0.01" value="0" class="input" /></div> <div class="field w-140"><div class="label">Total mtr</div><input id="total_mtr_top" class="input total-strong" readonly /></div> <div class="field w-200"><div class="label">Entry Month</div><input id="sb_month" type="month" class="input" /></div> <div class="field w-110"><div class="label">F Date (1-31)</div><input id="sb_fdate" type="number" min="1" max="31" value="1" class="input" /></div> <div class="field w-120"><button id="btnSubmit" class="btn btn-primary" style="width:100%">Submit</button></div> <div class="field w-120"><button id="btnResetKeepDates" class="btn btn-ghost" style="width:100%">Reset</button></div> </div> <div class="table-wrap" style="margin-top:12px"> <table id="matrix" class="matrix"> <thead><tr id="headRow"><th style="min-width:200px">Date</th></tr></thead> <tbody id="bodyRows"></tbody> </table> </div> <!-- Footer: only Remark --> <div class="footerbar"> <div class="remark-wrap"> <div class="label">Remark</div> <input id="remark" class="input" placeholder="optional" /> </div> </div> </div> </div> <div class="card"> <div class="hd">Last Entry</div> <div class="bd"> <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px"> <div style="font-size:12px;color:#6B7280">Machine: <b id="le_machine">—</b></div> <div style="font-size:12px;color:#6B7280">Last Taka: <b id="le_taka">—</b></div> <div style="font-size:12px;color:#6B7280">Last Entry Date: <b id="le_date">—</b></div> <div style="font-size:12px;color:#6B7280">Total Mtr: <b id="le_total">—</b></div> </div> </div> </div> </div> </div> <script> /* ---------------- helpers & state ---------------- */ const $ = (id) => document.getElementById(id); const today = (()=>{const d=new Date(),p=n=>String(n).padStart(2,'0');return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())}`})(); let khataMeta=null, karigarsMaster=[], fixedPair=[], employees=[], takaBase=0; const TARGET_KEY = 'loom_target_v1'; function saveTarget(t){ localStorage.setItem(TARGET_KEY, JSON.stringify(t)); } function readTarget(){ try{ return JSON.parse(localStorage.getItem(TARGET_KEY)||'null'); }catch(_){ return null; } } function clearTarget(){ localStorage.removeItem(TARGET_KEY); } function celebrate(){ for(let i=0;i<24;i++){ const s=document.createElement('span'); s.className='petal'; s.style.left=(10+Math.random()*80)+'%'; s.style.animationDelay=(Math.random()*0.6)+'s'; document.body.appendChild(s); setTimeout(()=>s.remove(),1600); } } async function fetchJSON(url,opt){ const r=await fetch(url,opt); const t=await r.text(); let j; try{ j=JSON.parse(t); } catch(e){ throw new Error('Bad JSON: '+t.slice(0,180)); } if(!j.ok) throw new Error(j.msg||'Server'); return j; } /* today count (created today) */ async function loadToday(){ try{ const j=await fetchJSON(`?act=today_stats`); $('todayTakas').textContent = j.takas_today ?? 0; }catch(_){} } /* target progress */ async function updateTargetUI(){ const t=readTarget(); const fill=$('targetFill'), meta=$('targetMeta'); if(!t){ fill.style.width='0%'; meta.textContent='No target set'; return; } try{ const j=await fetchJSON(`?act=target_progress&khata_id=${t.khata_id}&start=${t.start}&end=${t.end}`); const done=parseInt(j.done||0,10); const pct=Math.max(0,Math.min(100, Math.round((done/t.total)*100))); fill.style.width=pct+'%'; meta.textContent=`Target ${t.total} takas — #${t.start} → #${t.end} • Completed ${done}/${t.total}`; if(done>=t.total){ meta.textContent += ' • Target Complete 🎯'; celebrate(); } }catch(e){ meta.textContent='Target: unable to load progress'; } } /* totals */ function recomputeTotals(){ let tot=0; document.querySelectorAll('.meter-cell').forEach(i=>{ const v=parseFloat(i.value||0); if(!isNaN(v)) tot+=v; }); const ex=parseFloat($('extra_mtr_global').value||0); if(!isNaN(ex)) tot+=ex; const top=$('total_mtr_top'); if(top) top.value=tot.toFixed(2); } document.addEventListener('input',(e)=>{ if(e.target.classList.contains('meter-cell') || e.target.id==='extra_mtr_global') recomputeTotals(); }); /* ---------------- header/columns render ---------------- */ function empOptionHtml(){ const used=new Set(employees.map(e=>String(e.id||''))); const opts=karigarsMaster.map(k=>{ const dis=used.has(String(k.id))?'disabled':''; return `<option value="${k.id}" ${dis}>${k.name}</option>`; }).join(''); return `<option value="">Select</option>${opts}`; } function addColumnCells(colIndex){ document.querySelectorAll('#bodyRows tr').forEach(tr=>{ const td=document.createElement('td'); td.innerHTML=`<input type="number" step="0.01" class="meter-cell input" />`; td.querySelector('input').addEventListener('input',recomputeTotals); if(colIndex>=tr.children.length-0) tr.appendChild(td); else tr.insertBefore(td,tr.children[colIndex+1]); }); } function removeColumnCells(colIndex){ document.querySelectorAll('#bodyRows tr').forEach(tr=>{ const del=tr.children[colIndex+1]; if(del) tr.removeChild(del); }); } function renderHeader(){ const head=$('headRow'); while(head.children.length>1) head.removeChild(head.lastElementChild); employees.forEach((emp,idx)=>{ const isLast=(idx===employees.length-1); const th=document.createElement('th'); th.innerHTML=`<div style="display:flex;gap:8px;align-items:center"> <select class="select emp-select" data-col="${idx}">${empOptionHtml()}</select> <div style="display:flex;gap:6px"> ${isLast?`<button type="button" class="btn btn-ghost btn-col-add" data-col="${idx}">+</button>`:''} ${isLast?`<button type="button" class="btn btn-ghost btn-col-del" data-col="${idx}" style="border-color:#ffd6dc;color:#b00020">×</button>`:''} </div> </div>`; head.appendChild(th); const sel=th.querySelector('.emp-select'); if(emp.id) sel.value=String(emp.id); }); head.querySelectorAll('.emp-select').forEach(sel=>{ sel.addEventListener('change',(e)=>{ const idx = parseInt(e.target.dataset.col,10); employees[idx].id = parseInt(e.target.value || 0, 10) || 0; renderHeader(); if(!document.querySelector('#bodyRows tr')) addDateRow(); const cell = document.querySelector(`#bodyRows tr:first-child td:nth-child(${idx+2}) input.meter-cell`); if(cell){ cell.focus(); try{ cell.select(); }catch(_){ } } }); }); head.querySelectorAll('.btn-col-add').forEach(btn=>{ btn.addEventListener('click',(e)=>{ const i=parseInt(e.currentTarget.dataset.col,10); employees.splice(i+1,0,{id:0}); addColumnCells(i+1); renderHeader(); }); }); head.querySelectorAll('.btn-col-del').forEach(btn=>{ btn.addEventListener('click',(e)=>{ const i=parseInt(e.currentTarget.dataset.col,10); employees.splice(i,1); removeColumnCells(i); renderHeader(); recomputeTotals(); }); }); } /* ---------------- dates & F-DATE ---------------- */ function addDays(iso,days){ if(!iso) return today; const [y,m,d]=iso.split('-').map(Number); const dt=new Date(y,m-1,d); dt.setDate(dt.getDate()+days); const p=n=>String(n).padStart(2,'0'); return `${dt.getFullYear()}-${p(dt.getMonth()+1)}-${p(dt.getDate())}`; } function daysInMonth(ym){ if(!ym) return 31; const [y,m]=ym.split('-').map(Number); return new Date(y,m,0).getDate(); } function firstDateFromMonthF(){ const ym=$('sb_month')?.value||''; const fd=parseInt($('sb_fdate')?.value||'1',10); if(!ym){return today} const dim=daysInMonth(ym); const day=Math.min(Math.max(fd||1,1),dim); const p=n=>String(n).padStart(2,'0'); const [y,m]=ym.split('-').map(Number); return `${y}-${p(m)}-${p(day)}`; } function resequenceFromFirst(){ const rows=Array.from(document.querySelectorAll('#bodyRows tr')); if(rows.length===0) return; const firstInput=rows[0].querySelector('.date-cell'); let d0=firstInput?(firstInput.value||today):today; if($('sb_month').value){ d0=firstDateFromMonthF(); if(firstInput) firstInput.value=d0; } for(let i=1;i<rows.length;i++){ const inp=rows[i].querySelector('.date-cell'); if(inp){ inp.value=addDays(d0,i); } } } function addDateRow(){ const body=$('bodyRows'); const rows=Array.from(body.children); let dflt=today; if(rows.length===0){ dflt=firstDateFromMonthF(); } else { const last=rows[rows.length-1]; const lastVal=last.querySelector('.date-cell')?.value||today; dflt=addDays(lastVal,1); } const tr=document.createElement('tr'); const td=document.createElement('td'); td.innerHTML=`<div class="date-chip"><input type="date" class="input date-cell" value="${dflt}" /><button type="button" class="date-kill" title="Remove">×</button></div>`; tr.appendChild(td); employees.forEach(()=>{ const c=document.createElement('td'); c.innerHTML=`<input type="number" step="0.01" class="meter-cell input" />`; c.querySelector('input').addEventListener('input',recomputeTotals); tr.appendChild(c); }); body.appendChild(tr); tr.querySelector('.date-kill').addEventListener('click',()=>{ tr.remove(); recomputeTotals(); }); if(body.children.length===1){ const first=tr.querySelector('.date-cell'); first.addEventListener('input',resequenceFromFirst); first.dataset._bindSeq='1'; } else { const first=body.querySelector('tr:first-child .date-cell'); if(first && !first.dataset._bindSeq){ first.addEventListener('input',resequenceFromFirst); first.dataset._bindSeq='1'; } } } /* ---------------- bindings & loads ---------------- */ $('btnSeePatia')?.addEventListener('click',()=>window.open('/erp/loom_patia_machine.php','_blank')); $('btnTakaEdit')?.addEventListener('click',()=>window.open('/erp/taka_edit.php','_blank')); $('btnKarigarList')?.addEventListener('click',()=>window.open('/erp/karigar_list.php','_blank')); $('khata').addEventListener('change',async (e)=>{ const code=e.target.value; $('taka_no').value=''; $('quality_name').value=''; $('quality_id').value='0'; $('quality_name').readOnly=false; $('machine').innerHTML='<option value="">—</option>'; khataMeta=null; karigarsMaster=[]; fixedPair=[]; employees=[]; $('bodyRows').innerHTML=''; renderHeader(); recomputeTotals(); if(!code) return; try{ const data=await fetchJSON(`?act=options&khata=${encodeURIComponent(code)}`); khataMeta=data.khata; $('machine').innerHTML='<option value="">Select</option>'+data.machines.map(m=>`<option value="${m}">${m}</option>`).join(''); }catch(err){ alert('options: '+err.message); } }); $('machine').addEventListener('change',async (e)=>{ const m=e.target.value, code=$('khata').value; if(!m||!code) return; const existingDates = Array.from(document.querySelectorAll('#bodyRows .date-cell')).map(inp=> (inp.value ?? '')); try{ const def=await fetchJSON(`?act=machine_defaults&khata=${encodeURIComponent(code)}&machine=${m}`); $('le_machine').textContent=m; $('le_date').textContent=def.last_entry_date||'—'; const tn=await fetchJSON(`?act=taka_next&khata_id=${def.khata.id}`); takaBase=parseInt(tn.next_taka_no||0)||0; $('taka_no').value=takaBase>0?String(takaBase):''; $('le_taka').textContent=takaBase>0?(takaBase-1):'—'; try{ const q=await fetchJSON(`?act=quality_by_machine&machine_no=${m}`); $('quality_name').value=q.quality||''; $('quality_id').value=(q.quality_id||0); $('quality_name').readOnly=!!q.quality; } catch{ $('quality_name').readOnly=false; $('quality_id').value='0'; } const assignedList = Array.isArray(def.all_karigars) ? def.all_karigars : []; const khataList = Array.isArray(def.khata_karigars) ? def.khata_karigars : []; fixedPair = Array.isArray(def.fixed_karigars) ? def.fixed_karigars : []; fixedPair = [...new Set((fixedPair || []).filter(id => !!id))]; const assignedIds = assignedList.map(x=>x.id); const assignedSet = new Set(assignedIds); const khataIds = khataList.map(x=>x.id); const restOptions = khataList.filter(x=>!assignedSet.has(x.id)); karigarsMaster = [...assignedList, ...restOptions]; let fixedAssigned = fixedPair.filter(id => assignedSet.has(id)); for (const id of assignedIds) { if (fixedAssigned.length >= 2) break; if (!fixedAssigned.includes(id)) fixedAssigned.push(id); } fixedAssigned = fixedAssigned.slice(0,2); const used = new Set(fixedAssigned); const occasional = khataIds.filter(id => !assignedSet.has(id) && !used.has(id)).slice(0,2); employees = [...fixedAssigned.map(id=>({id})), ...occasional.map(id=>({id}))]; { const seen = new Set(); employees = employees.map(e => { if (e.id && seen.has(e.id)) return {id:0}; if (e.id) seen.add(e.id); return e; }); } while (employees.length < 3) employees.push({id:0}); if (employees.length > 4) employees = employees.slice(0,4); $('bodyRows').innerHTML=''; renderHeader(); if(existingDates.length){ existingDates.forEach(d=>{ addDateRow(); const last=$('bodyRows').lastElementChild?.querySelector('.date-cell'); if(last) last.value=(d||today); }); } else if(def.last_entry_date){ addDateRow(); $('bodyRows').lastElementChild.querySelector('.date-cell').value = def.last_entry_date; } else { addDateRow(); } }catch(err){ alert('machine_defaults: '+err.message); } }); /* ---------------- TARGET: set & progress ---------------- */ $('btnSetTarget').addEventListener('click', async ()=>{ const count = parseInt(($('targetCount').value||'').trim(),10); if(!count || count<=0){ alert('Target me kitne taka?'); return; } if(!khataMeta || !khataMeta.id){ alert('Target set karne ke liye pehle Khata select karein.'); return; } // anchor at current next taka for this khata let start = takaBase; if(!start || start<=0){ try{ const tn=await fetchJSON(`?act=taka_next&khata_id=${khataMeta.id}`); start=parseInt(tn.next_taka_no||0,10)||0; }catch(_){ start=0; } } if(start<=0){ alert('Start taka resolve nahi hua. Machine/Khata select karke try karein.'); return; } const end = start + count - 1; saveTarget({khata_id:khataMeta.id,start,end,total:count,created_at:Date.now()}); $('targetCount').value=''; updateTargetUI(); }); /* ---------------- Submit + Reset ---------------- */ function setBtnLoading(btn,is){ if(!btn) return; if(is){ btn.dataset._txt=btn.textContent; btn.textContent='Submit…'; btn.disabled=true; } else { btn.textContent=btn.dataset._txt||'Submit'; btn.disabled=false; } } async function doSubmit(){ if(!khataMeta || !khataMeta.id){ alert('Please select Khata.'); return; } const machineId=parseInt($('machine').value||0); if(!machineId){ alert('Please select Machine.'); return; } const qName=$('quality_name').value.trim(); if(!qName){ alert('Quality missing.'); return; } const qId=parseInt($('quality_id').value||0); const taka=parseInt($('taka_no').value||0); const body=$('bodyRows'); if(!body.children.length){ alert('Please add at least 1 Date.'); return; } if(!employees.length){ alert('Please add at least 1 Employee column.'); return; } const lines=[]; let header_date=''; Array.from(body.children).forEach(tr=>{ const d=tr.querySelector('.date-cell')?.value||''; if(!d) return; if(!header_date) header_date=d; employees.forEach((emp,idx)=>{ const kid=parseInt(emp.id||0); if(kid>0){ const td=tr.children[idx+1]; const m=parseFloat(td.querySelector('.meter-cell')?.value||0); if(m>0) lines.push({karigar_id:kid,meter:m,date:d}); }}); }); if(lines.length===0){ alert('Please enter meters in at least one cell.'); return; } const extra=parseFloat($('extra_mtr_global').value||0)||0; const btn=$('btnSubmit'); setBtnLoading(btn,true); try{ const payload={entry:{entry_date:header_date,machine_id:machineId,khata_id:khataMeta.id,quality_id:qId,quality_name:qName,taka_no:taka||0,extra_mtr:extra,lines:lines,remark:$('remark').value.trim()}}; const resp=await fetchJSON(`?act=submit`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); $('extra_mtr_global').value='0'; recomputeTotals(); document.querySelectorAll('.meter-cell').forEach(i=>i.value=''); alert(`Saved ✅\nTaka No: ${resp.taka_no}\nQuality: ${qName}\nTotal mtr: ${Number(resp.meter_total||0).toFixed(2)}`); // refresh top stats & target progress loadToday(); updateTargetUI(); $('machine').focus(); }catch(err){ alert('save: '+err.message); } finally{ setBtnLoading(btn,false); } } $('btnSubmit').addEventListener('click',doSubmit); $('btnResetKeepDates').addEventListener('click',()=>{ document.querySelectorAll('.meter-cell').forEach(i=>i.value=''); $('extra_mtr_global').value='0'; recomputeTotals(); }); /* ENTER/ALT/CTRL FLOW — unchanged */ function _focusAndSelect(el){ if(!el) return false; el.focus(); try{ el.select && el.select(); }catch(_){ } return true; } function ensureFirstRowAndCol(){ if(!document.querySelector('#bodyRows tr')) addDateRow(); if(!employees.length){ employees.push({id:0}); renderHeader(); } } function focusFirstMeterCell(){ ensureFirstRowAndCol(); const cell=document.querySelector('#bodyRows tr:first-child td:nth-child(2) input.meter-cell'); _focusAndSelect(cell); } /* Machine Enter -> FDate */ (function(){ const mach=$('machine'), fdate=$('sb_fdate'); if(!mach || !fdate) return; mach.addEventListener('keydown',(e)=>{ if(e.key==='Enter'){ e.preventDefault(); _focusAndSelect(fdate); } }); })(); /* FDate Enter -> ensure first row, resequence, focus first meter cell */ (function(){ const fdate=$('sb_fdate'); if(!fdate) return; fdate.addEventListener('keydown',(e)=>{ if(e.key==='Enter'){ e.preventDefault(); if(!document.querySelector('#bodyRows tr')) addDateRow(); resequenceFromFirst(); focusFirstMeterCell(); } }); })(); /* Global Ctrl+Enter => Submit (ignore when Shift also held) */ document.addEventListener('keydown',(ev)=>{ if(ev.key==='Enter' && ev.ctrlKey && !ev.shiftKey){ ev.preventDefault(); doSubmit(); } }); /* Global Ctrl + (+) => Add Karigar column after current column */ document.addEventListener('keydown',(ev)=>{ const isPlus = (ev.key === '+' || ev.code === 'NumpadAdd' || (ev.key === '=' && ev.shiftKey)); if(!ev.ctrlKey || !isPlus) return; ev.preventDefault(); let insertAfterEmpIdx = employees.length - 1; const act = document.activeElement; if(act && act.classList && act.classList.contains('meter-cell')){ const td = act.closest('td'), tr = act.closest('tr'); if(td && tr){ const colIndex = Array.from(tr.children).indexOf(td); insertAfterEmpIdx = Math.max(0, colIndex - 1); } } employees.splice(insertAfterEmpIdx+1, 0, {id:0}); addColumnCells(insertAfterEmpIdx+1); renderHeader(); }); /* Meter-cell navigation & shortcuts (Enter, Shift+Enter, Ctrl+Shift+Enter, Alt+Enter) */ document.addEventListener('keydown',(ev)=>{ const el = ev.target; if(!(el instanceof HTMLInputElement)) return; if(!el.classList.contains('meter-cell')) return; if(ev.key==='Enter' && ev.altKey){ ev.preventDefault(); const td = el.closest('td'), tr = el.closest('tr'); if(!td || !tr) return; const colIndex = Array.from(tr.children).indexOf(td); const empIdx = Math.max(0, colIndex - 1); const sel = document.querySelector(`.emp-select[data-col="${empIdx}"]`); if(sel){ sel.focus(); sel.style.outline='2px solid var(--brand)'; setTimeout(()=>sel.style.outline='',1200); } return; } if(ev.key!=='Enter') return; ev.preventDefault(); const td = el.closest('td'), tr = el.closest('tr'); if(!td || !tr) return; const colIndex = Array.from(tr.children).indexOf(td); const firstMeterColIndex = 1; if(ev.ctrlKey && ev.shiftKey){ const prevRow = tr.previousElementSibling; if(prevRow){ const up = prevRow.children[colIndex]?.querySelector('input.meter-cell'); if(up){ up.focus(); try{ up.select(); }catch(_){} } } return; } if(ev.shiftKey){ let targetRow = tr; let targetColIndex = colIndex + 1; if(targetColIndex >= tr.children.length){ targetColIndex = firstMeterColIndex; targetRow = tr.nextElementSibling; if(!targetRow){ addDateRow(); targetRow = $('bodyRows').lastElementChild; } } const nextInput = targetRow.children[targetColIndex]?.querySelector('input.meter-cell'); if(nextInput){ nextInput.focus(); try{ nextInput.select(); }catch(_){ } } return; } const nextRow = tr.nextElementSibling; if(nextRow){ const n = nextRow.children[colIndex]?.querySelector('input.meter-cell'); if(n){ n.focus(); try{ n.select(); }catch(_){ } return; } } addDateRow(); const last=$('bodyRows').lastElementChild; const ni=last.children[colIndex]?.querySelector('input.meter-cell'); if(ni){ ni.focus(); try{ ni.select(); }catch(_){ } } }); /* Live resequence on F-Date */ ['sb_month','sb_fdate'].forEach(id=>{ const el=$(id); if(!el) return; el.addEventListener('input', ()=>{ const first=$('bodyRows').querySelector('tr:first-child .date-cell'); if(first){ first.value=firstDateFromMonthF(); resequenceFromFirst(); } }); }); /* boot */ loadToday(); updateTargetUI(); </script> </body> </html>