« Back to History
saree_report_monthly.php
|
20260723_000646.php
Initial Domain Snapshot
Copy Code
<?php /* ============================================================================= File: /erp/saree_report_monthly.php Title: Total Saree Report by month Notes: - Uses project bootstrap: modules/auth/page_acl.php -> $ctx - Header/footer kept view-only (partials/header.php, partials/footer.php) - Behavior: * Click Load to load existing entries. * If entries exist -> show all dates for month (prefilled). * If no entries -> show ONLY first date row. Press Enter to add next date. - Ensure table total_saree_monthly exists (see earlier SQL). ============================================================================= */ require_once __DIR__ . '/modules/auth/page_acl.php'; $ctx = page_require_access('saree_report_monthly'); $pdo = $ctx['pdo'] ?? null; $company_id = (int)($ctx['company_id'] ?? 0); $user = $ctx['user'] ?? null; /* Header (view-only) */ require_once __DIR__ . '/partials/header.php'; /* CSRF/session */ if (session_status() === PHP_SESSION_NONE) session_start(); if (empty($_SESSION['csrf_token'])) $_SESSION['csrf_token'] = bin2hex(random_bytes(16)); $csrf_token = $_SESSION['csrf_token']; function esc($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } /* AJAX endpoints (same as before) */ $action = $_REQUEST['action'] ?? ''; if ($action === 'load_entries') { header('Content-Type: application/json; charset=utf-8'); $month = $_GET['month'] ?? ''; $quality_id = (int)($_GET['quality_id'] ?? 0); if (!($pdo instanceof PDO)) { echo json_encode(['success'=>false,'msg'=>'DB unavailable']); exit; } if (!$month || !$quality_id) { echo json_encode(['success'=>false,'msg'=>'Missing month/quality']); exit; } $start = $month . '-01'; $end = date('Y-m-t', strtotime($start)); try { $stmt = $pdo->prepare("SELECT id, entry_date, quality_id, saree FROM total_saree_monthly WHERE company_id = :cid AND quality_id = :qid AND entry_date BETWEEN :s AND :e ORDER BY entry_date"); $stmt->execute([':cid'=>$company_id, ':qid'=>$quality_id, ':s'=>$start, ':e'=>$end]); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); echo json_encode(['success'=>true,'rows'=>$rows,'start'=>$start,'end'=>$end]); } catch (Throwable $e) { http_response_code(500); echo json_encode(['success'=>false,'msg'=>$e->getMessage()]); } exit; } if ($action === 'save_entries' && $_SERVER['REQUEST_METHOD'] === 'POST') { header('Content-Type: application/json; charset=utf-8'); $payload = json_decode(file_get_contents('php://input'), true); if (!is_array($payload)) { http_response_code(400); echo json_encode(['success'=>false,'msg'=>'Invalid payload']); exit; } if (($payload['csrf'] ?? '') !== $csrf_token) { http_response_code(403); echo json_encode(['success'=>false,'msg'=>'CSRF failed']); exit; } $quality_id = (int)($payload['quality_id'] ?? 0); $rows = $payload['rows'] ?? []; if (!($pdo instanceof PDO)) { http_response_code(500); echo json_encode(['success'=>false,'msg'=>'DB unavailable']); exit; } if (!$quality_id || !is_array($rows)) { http_response_code(400); echo json_encode(['success'=>false,'msg'=>'Missing quality or rows']); exit; } try { $pdo->beginTransaction(); $st = $pdo->prepare("INSERT INTO total_saree_monthly (company_id, entry_date, quality_id, saree, created_by, created_at, updated_at) VALUES(:cid, :d, :qid, :s, :uid, NOW(), NOW()) ON DUPLICATE KEY UPDATE saree = VALUES(saree), updated_at = VALUES(updated_at)"); foreach ($rows as $r) { if (empty($r['date'])) continue; $st->execute([ ':cid' => $company_id, ':d' => date('Y-m-d', strtotime($r['date'])), ':qid' => $quality_id, ':s' => (int)($r['saree'] ?? 0), ':uid' => (int)($user['id'] ?? 0), ]); } $pdo->commit(); echo json_encode(['success'=>true]); } catch (Throwable $e) { if ($pdo && $pdo->inTransaction()) $pdo->rollBack(); http_response_code(500); echo json_encode(['success'=>false,'msg'=>$e->getMessage()]); } exit; } /* ---------- Render page ---------- */ $default_month = date('Y-m'); // YYYY-MM $qualities = []; if ($pdo instanceof PDO) { try { $q = $pdo->prepare("SELECT id, quality_name, code FROM qualities WHERE (company_id = :cid OR company_id IS NULL) ORDER BY quality_name"); $q->execute([':cid' => $company_id]); $qualities = $q->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) { // ignore $qualities = []; } } ?> <div class="page-title"><h3>Total Saree Report by month</h3></div> <div class="card"> <div class="card-body"> <?php if (!($pdo instanceof PDO)): ?> <div class="notice notice--warning">Database connection not available. Page will be read-only until DB is reachable.</div> <?php endif; ?> <form id="sareeForm" onsubmit="return false;"> <input type="hidden" id="csrf" value="<?php echo esc($csrf_token); ?>"> <div class="form-row"> <div class="form-group col-md-3"> <label for="month">Month</label> <input type="month" id="month" name="month" class="form-control" value="<?php echo esc($default_month); ?>"> </div> <div class="form-group col-md-4"> <label for="quality">Quality</label> <select id="quality" name="quality" class="form-control"> <option value="">-- select quality --</option> <?php foreach ($qualities as $q): ?> <option value="<?php echo (int)$q['id']; ?>"><?php echo esc($q['quality_name']); ?><?php echo !empty($q['code']) ? ' ('.esc($q['code']).')' : ''; ?></option> <?php endforeach; ?> </select> </div> <div class="form-group col-md-5" style="display:flex;align-items:flex-end;"> <button id="loadBtn" class="btn btn-success mr-2">Load</button> <button id="saveBtn" class="btn btn-outline-secondary">Save</button> </div> </div> </form> <hr/> <div id="rowsWrap" class="card card--subtle p-2 text-muted"> Select month & quality and click <strong>Load</strong>. </div> </div> </div> <script> (function(){ const monthEl = document.getElementById('month'); const qualityEl = document.getElementById('quality'); const rowsWrap = document.getElementById('rowsWrap'); const loadBtn = document.getElementById('loadBtn'); const saveBtn = document.getElementById('saveBtn'); const csrf = document.getElementById('csrf').value; // Helpers function clearRows(){ rowsWrap.innerHTML = ''; } function isoDateStr(date){ return date.toISOString().slice(0,10); } function formatDisplay(dateStr){ const dt = new Date(dateStr + 'T00:00:00'); const opts = { day: '2-digit', month: 'short', year: 'numeric' }; return dt.toLocaleDateString(undefined, opts).replace(',', ''); // e.g. 01 Sep 2025 } function buildRowHTML(dateStr, sareeVal = ''){ // one row: date (readonly), saree input, edit button return `<div class="grid grid-3 gap-1 row-item" data-date="${dateStr}"> <div><input type="text" class="form-control date-display" value="${formatDisplay(dateStr)}" readonly></div> <div><input type="number" class="form-control saree-input" min="0" value="${sareeVal}"></div> <div style="display:flex;align-items:center;"> <button type="button" class="btn btn--link edit">Edit</button> </div> </div>`; } function addRow(dateStr, sareeVal = ''){ rowsWrap.insertAdjacentHTML('beforeend', buildRowHTML(dateStr, sareeVal)); const last = rowsWrap.querySelector('.row-item:last-child'); const inp = last.querySelector('.saree-input'); // start with read-only so Edit toggles inp.readOnly = false; // allow immediate editing for UX inp.addEventListener('keydown', onSareeKeyDown); last.querySelector('.edit').addEventListener('click', function(){ inp.readOnly = !inp.readOnly; if (!inp.readOnly) inp.focus(); }); return last; } function populateMonthWithOnlyFirst(month){ clearRows(); const first = new Date(month + '-01T00:00:00'); const firstISO = isoDateStr(first); addRow(firstISO, ''); } function populateMonthFullWithMap(month, map){ clearRows(); const first = new Date(month + '-01T00:00:00'); const last = new Date(month + '-01T00:00:00'); last.setMonth(last.getMonth()+1); last.setDate(0); for (let d = first.getDate(); d <= last.getDate(); d++){ const dt = new Date(first.getFullYear(), first.getMonth(), d); const ds = isoDateStr(dt); addRow(ds, map[ds] ? map[ds].saree : ''); } } // handle Enter on saree input function onSareeKeyDown(e){ if (e.key !== 'Enter') return; e.preventDefault(); const currentRow = e.target.closest('.row-item'); // find next sibling const next = currentRow.nextElementSibling; const month = monthEl.value; if (!month) return; const lastDay = new Date(month + '-01T00:00:00'); lastDay.setMonth(lastDay.getMonth()+1); lastDay.setDate(0); const curDate = new Date(currentRow.dataset.date + 'T00:00:00'); // if next exists, focus its input: if (next){ const nextInp = next.querySelector('.saree-input'); nextInp.readOnly = false; nextInp.focus(); return; } // next doesn't exist -> compute next date and add if within month const nextDate = new Date(curDate); nextDate.setDate(nextDate.getDate() + 1); if (nextDate <= lastDay){ addRow(isoDateStr(nextDate), ''); const newLastInp = rowsWrap.querySelector('.row-item:last-child .saree-input'); newLastInp.focus(); } else { // end of month -> trigger save saveEntries(); } } // Load entries (AJAX) async function loadEntries(){ clearRows(); const month = monthEl.value; const qid = qualityEl.value; if (!month || !qid){ // If user hasn't selected quality, show guidance or only first date? rowsWrap.innerHTML = '<div class="text-muted">Please select month and quality, then click Load.</div>'; return; } loadBtn.disabled = true; rowsWrap.innerHTML = '<div class="muted">Loading...</div>'; try { const url = window.location.pathname + '?action=load_entries&month=' + encodeURIComponent(month) + '&quality_id=' + encodeURIComponent(qid); const res = await fetch(url, { credentials: 'same-origin' }); const raw = await res.text(); if (!res.ok) { rowsWrap.innerHTML = '<div class="text-danger">Server error: HTTP ' + res.status + '</div>'; console.error('Load HTTP error', res.status, raw); return; } let j; try { j = JSON.parse(raw); } catch (e){ rowsWrap.innerHTML = '<div class="text-danger">Invalid server response (not JSON)</div>'; console.error('Invalid JSON', raw); return; } if (!j.success){ rowsWrap.innerHTML = '<div class="text-danger">Error: ' + (j.msg || 'Unknown') + '</div>'; return; } // If rows exist -> render full month with data if (Array.isArray(j.rows) && j.rows.length > 0){ const map = {}; j.rows.forEach(r => { map[r.entry_date] = r; }); populateMonthFullWithMap(month, map); } else { // no existing entries -> show only first date row populateMonthWithOnlyFirst(month); } } catch (err){ rowsWrap.innerHTML = '<div class="text-danger">Network error</div>'; console.error(err); } finally { loadBtn.disabled = false; } } // Save entries (AJAX) async function saveEntries(){ const month = monthEl.value; const qid = qualityEl.value; if (!month || !qid){ alert('Select month and quality before saving'); return; } const rows = Array.from(rowsWrap.querySelectorAll('.row-item')).map(div => ({ date: div.dataset.date, saree: parseInt(div.querySelector('.saree-input').value || '0', 10) })); saveBtn.disabled = true; try { const res = await fetch(window.location.pathname + '?action=save_entries', { method: 'POST', credentials: 'same-origin', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ csrf: csrf, quality_id: qid, rows }) }); const raw = await res.text(); if (!res.ok){ alert('Server returned HTTP ' + res.status); console.error('Save HTTP error', raw); return; } let j; try { j = JSON.parse(raw); } catch (e){ alert('Invalid server response on save'); console.error('Invalid JSON save', raw); return; } if (!j.success){ alert('Save failed: ' + (j.msg || 'Unknown')); return; } alert('Saved successfully'); // After save, keep current display. Optionally reload full month: await loadEntries(); } catch (err){ alert('Save network error'); console.error(err); } finally { saveBtn.disabled = false; } } // Events loadBtn.addEventListener('click', function(e){ e.preventDefault(); loadEntries(); }); saveBtn.addEventListener('click', function(e){ e.preventDefault(); saveEntries(); }); // DO NOT auto-load on quality change — user must click Load (this prevents sudden UI hide) // qualityEl.addEventListener('change', function(){ if (this.value) loadEntries(); }); // initial state: show guidance or first-date? we'll show guidance rowsWrap.innerHTML = '<div class="text-muted">Select month & quality and click <strong>Load</strong>.</div>'; })(); </script> <?php /* Footer (view-only) */ require_once __DIR__ . '/partials/footer.php';