« Back to History
cutting_life_cycle_report.php
|
20260721_154032.php
Initial Bulk Import
Copy Code
<?php /* ============================================================================= File: /erp/cutting_life_cycle_report.php Purpose: Cutting Life Cycle Report with Multi-Image Support & Dynamic FY/Month Filters ============================================================================= */ require_once __DIR__ . '/modules/auth/page_acl.php'; $ctx = page_require_access('gray_lifecycle_report'); if (!function_exists('h')) { function h($value) { return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); } } $pdo = $ctx['pdo']; $company_id = (int)$ctx['company_id']; // --- USER ID RESOLUTION --- $user_id = 0; if (isset($ctx['user']['id']) && (int)$ctx['user']['id'] > 0) { $user_id = (int)$ctx['user']['id']; } elseif (isset($_SESSION['user']['id']) && (int)$_SESSION['user']['id'] > 0) { $user_id = (int)$_SESSION['user']['id']; } /* ===================== SCHEMA AUTO-PATCHING SYSTEM (Cutting Status) ===================== */ try { $pdo->exec("ALTER TABLE gray_receive ADD COLUMN IF NOT EXISTS cutting_check_status VARCHAR(20) DEFAULT 'need_check'"); } catch (Exception $e) { try { $pdo->exec("ALTER TABLE gray_receive ADD cutting_check_status VARCHAR(20) DEFAULT 'need_check'"); } catch (Exception $ex) { // Master bypass if column already exists } } /* ===================== AJAX ENDPOINT: FETCH COMMENT DETAILS ===================== */ if ($_SERVER['REQUEST_METHOD'] === 'GET' && ($_GET['action'] ?? '') === 'get_comment') { header('Content-Type: application/json'); $gray_id = trim($_GET['gray_id'] ?? ''); try { $st = $pdo->prepare("SELECT comment FROM gray_receive_comments WHERE TRIM(gray_id) = TRIM(?) AND company_id = ? AND status = 'active' ORDER BY comment_id DESC LIMIT 1"); $st->execute([$gray_id, $company_id]); $cm = $st->fetch(PDO::FETCH_ASSOC); echo json_encode(['success' => true, 'comment' => $cm ? $cm['comment'] : '']); } catch (Exception $e) { echo json_encode(['success' => false, 'message' => $e->getMessage()]); } exit; } /* ===================== AJAX ENDPOINT: SAVE / UPDATE COMMENT ===================== */ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_GET['action'] ?? '') === 'save_comment') { header('Content-Type: application/json'); $gray_id = trim($_POST['gray_id'] ?? ''); $comment_text = trim($_POST['comment'] ?? ''); if ($gray_id === '') { echo json_encode(['success' => false, 'message' => 'Invalid Gray ID']); exit; } try { $pdo->beginTransaction(); // Soft-delete older active comments for this gray_id (Delete-then-Insert pattern wrapper) $up = $pdo->prepare("UPDATE gray_receive_comments SET status = 'hidden', updated_by = ?, updated_at = NOW() WHERE TRIM(gray_id) = TRIM(?) AND company_id = ? AND status = 'active'"); $up->execute([$user_id, $gray_id, $company_id]); if ($comment_text !== '') { $ins = $pdo->prepare("INSERT INTO gray_receive_comments (company_id, gray_id, comment_type, comment, created_by, created_at, status) VALUES (?, ?, 'general', ?, ?, NOW(), 'active')"); $ins->execute([$company_id, $gray_id, $comment_text, $user_id]); } $pdo->commit(); echo json_encode(['success' => true]); } catch (Exception $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } echo json_encode(['success' => false, 'message' => $e->getMessage()]); } exit; } /* ===================== AJAX ENDPOINT: UPDATE CUTTING CHECK STATUS ===================== */ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_GET['action'] ?? '') === 'update_check_status') { header('Content-Type: application/json'); $gray_id = trim($_POST['gray_id'] ?? ''); $status = trim($_POST['check_status'] ?? 'need_check'); if ($gray_id === '' || !in_array($status, ['ok', 'need_check'])) { echo json_encode(['success' => false, 'message' => 'Invalid parameters']); exit; } try { $up = $pdo->prepare("UPDATE gray_receive SET cutting_check_status = ? WHERE gray_id = ? AND company_id = ?"); $up->execute([$status, $gray_id, $company_id]); echo json_encode(['success' => true]); } catch (Exception $e) { echo json_encode(['success' => false, 'message' => $e->getMessage()]); } exit; } // Dynamic Date Range Calculation Controllers $current_year = (int)date('Y'); $current_month = (int)date('m'); $fy_start_year = ($current_month >= 4) ? $current_year : ($current_year - 1); $default_from = date('Y-m-01'); // First day of current month $default_to = date('Y-m-d'); // Today // Request Filter States $from = $_GET['from'] ?? $default_from; $to = $_GET['to'] ?? $default_to; $check_filter = $_GET['check_filter'] ?? 'all'; // Dynamic Filtering Clause for Cutting Status Only $check_clause = ""; if ($check_filter === 'ok') { $check_clause = " AND COALESCE(g.cutting_check_status, 'need_check') = 'ok' "; } elseif ($check_filter === 'need_check') { $check_clause = " AND COALESCE(g.cutting_check_status, 'need_check') = 'need_check' "; } // --- DYNAMIC SORTING CONFIGURATION --- $allowed_sort_columns = [ 'gray_id' => 'g.gray_id', 'rec_date' => 'g.rec_date', 'cutting_date' => 'ce.max_cut_date', 'finish_mts' => 'g.finish_mts', 'shortage_pct' => '(((g.grey_mts - g.finish_mts) / NULLIF(g.grey_mts, 0)) * 100)', 'fresh' => 'COALESCE(ce.fresh, 0)', 'second_cut' => 'COALESCE(ce.second_cut, 0)', 'cutting_pct' => '(((g.finish_mts - COALESCE(ce.used_meter, 0)) / NULLIF(g.finish_mts, 0)) * 100)', 'second_pct' => '((COALESCE(ce.second_cut, 0) / NULLIF((COALESCE(ce.fresh, 0) + COALESCE(ce.second_cut, 0)), 0)) * 100)' ]; $sort_by = $_GET['sort_by'] ?? 'cutting_date'; $order = strtoupper($_GET['order'] ?? 'DESC'); if (!array_key_exists($sort_by, $allowed_sort_columns)) { $sort_by = 'cutting_date'; } if ($order !== 'ASC' && $order !== 'DESC') { $order = 'DESC'; } $orderBySql = $allowed_sort_columns[$sort_by] . " " . $order; /* ===================== MAIN QUERY WITH REMOVED COMMENT_TYPE CONSTRAINT ===================== */ $sql = " SELECT g.gray_id, g.rec_date, mm.short_name AS mill_short_name, fq.quality_name, g.finish_mts, g.shortage, g.grey_mts, g.financial_year, COALESCE(g.cutting_check_status, 'need_check') AS check_status, ce.cut_name, COALESCE(ce.fresh, 0) AS fresh, COALESCE(ce.second_cut, 0) AS second_cut, COALESCE(ce.used_meter, 0) AS used_meter, ce.max_cut_date AS cutting_date, ce.all_design_urls, cc.comment AS active_comment FROM gray_receive g LEFT JOIN fabric_quality_master fq ON fq.id = g.quality_id LEFT JOIN mill_master mm ON mm.id = g.mill_id AND mm.company_id = g.company_id INNER JOIN ( SELECT ce.gray_id, ce.company_id, MAX(ce.cutting_date) AS max_cut_date, GROUP_CONCAT(DISTINCT sct.cut_name ORDER BY sct.cut_name SEPARATOR ', ') AS cut_name, SUM(ce.fresh_saree) AS fresh, SUM(ce.second_saree) AS second_cut, SUM(ce.total_meter) AS used_meter, GROUP_CONCAT(DISTINCT ce.design_url ORDER BY ce.id SEPARATOR ',') AS all_design_urls FROM cutting_entry ce LEFT JOIN saree_cut_types sct ON sct.id = ce.cut_type_id AND sct.company_id = ce.company_id WHERE ce.company_id = :cid GROUP BY ce.gray_id ) ce ON TRIM(ce.gray_id) = TRIM(g.gray_id) AND ce.company_id = g.company_id LEFT JOIN gray_receive_comments cc ON TRIM(cc.gray_id) = TRIM(g.gray_id) AND cc.company_id = g.company_id AND cc.status = 'active' WHERE g.company_id = :cid2 AND ce.max_cut_date BETWEEN :f AND :t $check_clause ORDER BY $orderBySql "; $st = $pdo->prepare($sql); $st->execute([ ':cid' => $company_id, ':cid2' => $company_id, ':f' => $from, ':t' => $to ]); $rows = $st->fetchAll(PDO::FETCH_ASSOC); // ===== TEMP DEBUG - REMOVE AFTER TESTING ===== if (isset($_GET['debug_cmt'])) { header('Content-Type: text/plain'); echo "company_id = $company_id\n\n"; echo "-- Report rows (gray_id + active_comment) --\n"; foreach ($rows as $r) { echo "gray_id=[{$r['gray_id']}] active_comment=" . var_export($r['active_comment'], true) . "\n"; } echo "\n-- Raw gray_receive_comments table (this company) --\n"; $dbg = $pdo->prepare("SELECT gray_id, company_id, comment_type, status, comment, comment_id FROM gray_receive_comments WHERE company_id = ? ORDER BY comment_id DESC LIMIT 10"); $dbg->execute([$company_id]); foreach ($dbg->fetchAll(PDO::FETCH_ASSOC) as $row) { print_r($row); } exit; } // ===== END TEMP DEBUG ===== function format_report_pcs($value) { $value = (float)$value; return fmod($value, 1.0) == 0.0 ? (string)(int)$value : rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.'); } // Convert unique Google Drive Links safely to clean viewer source endpoints function parse_clean_drive_urls($csv_urls) { if (empty($csv_urls)) return []; $raw_arr = explode(',', $csv_urls); $clean_arr = []; foreach ($raw_arr as $url) { $url = trim($url); if ($url === '') continue; if (strpos($url, 'drive.google.com/file/d/') !== false) { $parts = explode('/file/d/', $url); if (isset($parts[1])) { $id = explode('/', $parts[1])[0]; $clean_arr[] = "https://lh3.googleusercontent.com/d/" . $id; continue; } } $clean_arr[] = $url; } return $clean_arr; } function getSortUrl($column, $current_sort, $current_order, $from, $to, $check_filter) { $next_order = ($current_sort === $column && $current_order === 'ASC') ? 'DESC' : 'ASC'; return "?from=" . h($from) . "&to=" . h($to) . "&check_filter=" . h($check_filter) . "&sort_by=" . h($column) . "&order=" . $next_order; } function getSortIndicator($column, $current_sort, $current_order) { if ($current_sort === $column) { return $current_order === 'ASC' ? ' ▲' : ' ▼'; } return ' ↕'; } function render_cutting_lifecycle_report(array $rows, string $from, string $to, string $sort_by, string $order, string $check_filter, int $fy_start_year, bool $isPdf = false): void { ?> <style> .btn-toggle-check { padding: 1px 4px; font-weight: bold; border-radius: 4px; font-size: 11px; } /* Fixed Split Scroll Sync Layout */ .lifecycle-header-wrapper { overflow: hidden; width: 100%; background-color: #212529; border: 1px solid #dee2e6; border-bottom: none; } .lifecycle-body-wrapper { max-height: calc(100vh - 320px); overflow-y: auto; overflow-x: auto; width: 100%; border: 1px solid #dee2e6; } .lifecycle-header-wrapper table, .lifecycle-body-wrapper table { min-width: 1280px; border-collapse: collapse !important; table-layout: fixed; } .col-gray-id { width: 55px; } .col-date { width: 85px; } .col-cut-date { width: 85px; } .col-mill { width: 75px; } .col-quality { width: 140px; } .col-design { width: 75px; } .col-finish { width: 85px; } .col-gr-srtg { width: 75px; } .col-cut { width: 65px; } .col-fresh { width: 65px; } .col-second { width: 65px; } .col-cutti-pct { width: 75px; } .col-second-pct { width: 75px; } .col-comment { width: 60px; } .col-status { width: 105px; } .col-action { width: 90px; } .lifecycle-header-wrapper table th, .lifecycle-body-wrapper table td { padding: 3px 4px !important; font-size: 12px; line-height: 1.3 !important; } .lifecycle-body-wrapper table td.text-center, .lifecycle-header-wrapper table th { text-align: center !important; } .sort-link { color: #ffffff !important; text-decoration: none !important; display: block; width: 100%; } .sort-link:hover { text-decoration: underline !important; background-color: #343a40; } .green-plus-btn { color: #198754 !important; font-weight: bold; text-decoration: none; font-size: 15px; } .green-plus-btn:hover { transform: scale(1.2); display: inline-block; } <?php if ($isPdf): ?> body { font-family: DejaVu Sans, sans-serif; font-size: 10px; color: #111; } .container-fluid { padding: 0; } .card { border: 0; box-shadow: none; } .lifecycle-header-wrapper { display: none !important; } .lifecycle-body-wrapper { max-height: none; overflow: visible; border: 0; } .lifecycle-body-wrapper table { min-width: 100%; table-layout: auto; border-collapse: collapse !important; } .table th, .table td { border: 1px solid #222; padding: 4px 5px; vertical-align: middle; } .table-dark th { background: #212529; color: #fff; position: static !important; } .text-center { text-align: center; } .text-end { text-align: right; } .fw-bold { font-weight: 700; } .table-hover tbody tr:nth-child(even) { background: #fafafa; } .print-header { display: block !important; text-align: center; font-weight: 700; margin-bottom: 8px; } .no-print { display: none !important; } <?php else: ?> @media print { @page { size: A4 landscape; margin: 6mm 4mm; } html, body { background: #fff !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; } .no-print { display: none !important; } .card { border: 0 !important; box-shadow: none !important; } .lifecycle-header-wrapper { display: none !important; } .lifecycle-body-wrapper { overflow: visible !important; max-height: none !important; border: 0 !important; } .lifecycle-body-wrapper table { min-width: 100% !important; table-layout: auto !important; border-collapse: collapse !important; } thead { display: table-header-group !important; } .table th, .table td { padding: 4px 5px !important; vertical-align: middle !important; font-size: 11px !important; } .print-header { display: block !important; text-align: center; font-weight: 700; margin-bottom: 6px !important; } } <?php endif; ?> </style> <div class="container-fluid <?= ($isPdf) ? '' : 'py-4' ?>"> <div class="print-header" style="<?= $isPdf ? '' : 'display:none;' ?>"> Cutting Life Cycle Report<br> <small>From <?= date('d-m-Y', strtotime($from)) ?> To <?= date('d-m-Y', strtotime($to)) ?> | Filter: <?= h(strtoupper($check_filter)) ?></small> </div> <div class="card shadow-sm mb-4 no-print border-0"> <div class="card-body"> <div class="d-flex justify-content-between align-items-center flex-wrap gap-3"> <h4 class="mb-0">Cutting Life Cycle Report</h4> <div class="d-flex align-items-center gap-2"> <a href="?from=<?= h($from) ?>&to=<?= h($to) ?>&sort_by=<?= h($sort_by) ?>&order=ASC&check_filter=<?= h($check_filter) ?>" class="btn btn-sm btn-outline-primary">ASC</a> <a href="?from=<?= h($from) ?>&to=<?= h($to) ?>&sort_by=<?= h($sort_by) ?>&order=DESC&check_filter=<?= h($check_filter) ?>" class="btn btn-sm btn-outline-primary">DESC</a> <a href="?from=<?= h($from) ?>&to=<?= h($to) ?>&sort_by=<?= h($sort_by) ?>&order=<?= h($order) ?>&check_filter=<?= h($check_filter) ?>&export=pdf" class="btn btn-danger btn-sm">Export PDF</a> <button onclick="window.print()" class="btn btn-dark btn-sm">Print</button> </div> </div> <form method="get" class="row g-3 align-items-end mt-1"> <div class="col-md-2"> <label class="form-label small fw-bold text-muted">From Date</label> <input type="date" name="from" class="form-control" value="<?= h($from) ?>"> </div> <div class="col-md-2"> <label class="form-label small fw-bold text-muted">To Date</label> <input type="date" name="to" class="form-control" value="<?= h($to) ?>"> </div> <div class="col-md-2"> <label class="form-label small fw-bold text-muted">Verification Check</label> <select name="check_filter" class="form-select bg-light fw-bold border-primary"> <option value="all" <?= $check_filter === 'all' ? 'selected' : '' ?>>All Rows</option> <option value="ok" <?= $check_filter === 'ok' ? 'selected' : '' ?>>Checked OK Only</option> <option value="need_check" <?= $check_filter === 'need_check' ? 'selected' : '' ?>>Need Check Only</option> </select> </div> <input type="hidden" name="sort_by" value="<?= h($sort_by) ?>"> <input type="hidden" name="order" value="<?= h($order) ?>"> <div class="col-md-6 d-flex gap-2"> <button type="submit" class="btn btn-primary px-3">Show Data</button> <a href="?from=<?= h(date('Y-m-01')) ?>&to=<?= h(date('Y-m-d')) ?>&check_filter=<?= h($check_filter) ?>" class="btn btn-outline-secondary px-3">This Month</a> <a href="?from=<?= h($fy_start_year . '-04-01') ?>&to=<?= h(date('Y-m-d')) ?>&check_filter=<?= h($check_filter) ?>" class="btn btn-outline-info text-dark fw-bold px-3">This FY</a> <a href="?" class="btn btn-outline-dark">Reset</a> </div> </form> <?php if (empty($rows)): ?> <div class="alert alert-warning mb-0 mt-3"> Selected properties/date range mein data nahi mila. Options badal kar dekhein. </div> <?php endif; ?> </div> </div> <div class="card border-0 shadow-sm"> <div class="lifecycle-header-wrapper"> <table class="table table-bordered align-middle mb-0 text-white"> <colgroup> <col class="col-gray-id"> <col class="col-date"> <col class="col-cut-date"> <col class="col-mill"> <col class="col-quality"> <col class="col-design"> <col class="col-finish"> <col class="col-gr-srtg"> <col class="col-cut"> <col class="col-fresh"> <col class="col-second"> <col class="col-cutti-pct"> <col class="col-second-pct"> <col class="col-comment"> <col class="col-status"> <col class="col-action"> </colgroup> <thead class="table-dark text-nowrap"> <tr> <th> <a class="sort-link" href="<?= getSortUrl('gray_id', $sort_by, $order, $from, $to, $check_filter) ?>"> Grey ID<?= getSortIndicator('gray_id', $sort_by, $order) ?> </a> </th> <th> <a class="sort-link" href="<?= getSortUrl('rec_date', $sort_by, $order, $from, $to, $check_filter) ?>"> Rec Date<?= getSortIndicator('rec_date', $sort_by, $order) ?> </a> </th> <th> <a class="sort-link" href="<?= getSortUrl('cutting_date', $sort_by, $order, $from, $to, $check_filter) ?>"> Cutting Date<?= getSortIndicator('cutting_date', $sort_by, $order) ?> </a> </th> <th>Mill</th> <th>Quality</th> <th>Design</th> <th> <a class="sort-link" href="<?= getSortUrl('finish_mts', $sort_by, $order, $from, $to, $check_filter) ?>"> Finish<?= getSortIndicator('finish_mts', $sort_by, $order) ?> </a> </th> <th> <a class="sort-link" href="<?= getSortUrl('shortage_pct', $sort_by, $order, $from, $to, $check_filter) ?>"> GR SRTG<?= getSortIndicator('shortage_pct', $sort_by, $order) ?> </a> </th> <th>Cut</th> <th> <a class="sort-link" href="<?= getSortUrl('fresh', $sort_by, $order, $from, $to, $check_filter) ?>"> Fresh<?= getSortIndicator('fresh', $sort_by, $order) ?> </a> </th> <th> <a class="sort-link" href="<?= getSortUrl('second_cut', $sort_by, $order, $from, $to, $check_filter) ?>"> Second<?= getSortIndicator('second_cut', $sort_by, $order) ?> </a> </th> <th> <a class="sort-link" href="<?= getSortUrl('cutting_pct', $sort_by, $order, $from, $to, $check_filter) ?>"> Cutti. %<?= getSortIndicator('cutting_pct', $sort_by, $order) ?> </a> </th> <th> <a class="sort-link" href="<?= getSortUrl('second_pct', $sort_by, $order, $from, $to, $check_filter) ?>"> Second %<?= getSortIndicator('second_pct', $sort_by, $order) ?> </a> </th> <th class="text-center">Cmt</th> <th class="status-cell text-center">Status</th> <th class="no-print text-center">Action</th> </tr> </thead> </table> </div> <div class="lifecycle-body-wrapper"> <table class="table table-bordered table-hover align-middle mb-0"> <colgroup> <col class="col-gray-id"> <col class="col-date"> <col class="col-cut-date"> <col class="col-mill"> <col class="col-quality"> <col class="col-design"> <col class="col-finish"> <col class="col-gr-srtg"> <col class="col-cut"> <col class="col-fresh"> <col class="col-second"> <col class="col-cutti-pct"> <col class="col-second-pct"> <col class="col-comment"> <col class="col-status"> <col class="col-action"> </colgroup> <thead class="table-dark text-nowrap d-none d-print-table-header-group"> <tr> <th>Grey ID</th> <th>Rec Date</th> <th>Cutting Date</th> <th>Mill</th> <th>Quality</th> <th>Design</th> <th>Finish</th> <th>GR SRTG</th> <th>Cut</th> <th>Fresh</th> <th>Second</th> <th>Cutti. %</th> <th>Second %</th> <th>Cmt</th> <th>Status</th> </tr> </thead> <tbody> <?php foreach($rows as $r): $total_cut = (float)($r['fresh'] ?? 0) + (float)($r['second_cut'] ?? 0); $second_pct = $total_cut > 0 ? round(($r['second_cut'] / $total_cut) * 100, 2) : 0; $shortage_pct = ((float)$r['grey_mts'] > 0) ? round((((float)$r['grey_mts'] - (float)$r['finish_mts']) / (float)$r['grey_mts']) * 100, 2) : 0; $cutting_pct = (float)$r['finish_mts'] > 0 ? round((((float)$r['finish_mts'] - (float)$r['used_meter']) / (float)$r['finish_mts']) * 100, 2) : 0; $is_not_cut = ($r['fresh'] == 0 && $r['second_cut'] == 0); $row_status_class = ($r['check_status'] === 'ok') ? 'table-success-subtle' : ''; $clean_images_list = parse_clean_drive_urls($r['all_design_urls'] ?? ''); $images_json_attribute = h(json_encode($clean_images_list)); $total_images_count = count($clean_images_list); ?> <tr id="tr_<?= h($r['gray_id']) ?>" class="<?= $row_status_class ?>"> <td class="text-center fw-bold"><?= h($r['gray_id']) ?></td> <td class="text-center text-nowrap"><?= date('d-m-Y', strtotime($r['rec_date'])) ?></td> <td class="text-center text-nowrap"> <?= (!empty($r['cutting_date']) && $r['cutting_date'] !== '0000-00-00') ? date('d-m-Y', strtotime($r['cutting_date'])) : '-' ?> </td> <td><?= h($r['mill_short_name'] ?: '-') ?></td> <td><?= h($r['quality_name']) ?></td> <td class="text-center"> <?php if ($total_images_count > 0): ?> <button type="button" class="btn btn-sm btn-outline-dark px-2 py-0 no-print" onclick="openMultiDesignModal(<?= $images_json_attribute ?>, '<?= h($r['gray_id']) ?>')" style="font-size: 11px;"> View (<?= $total_images_count ?>) </button> <span class="d-none d-print-inline" style="font-size: 9px;">Qty: <?= $total_images_count ?></span> <?php else: ?> <span class="text-muted font-monospace" style="font-size: 11px;">-</span> <?php endif; ?> </td> <td class="fw-bold text-end"><?= number_format((float)$r['finish_mts'], 2) ?></td> <td class="text-danger fw-bold text-end"><?= h($shortage_pct) ?>%</td> <?php if($is_not_cut): ?> <td colspan="5" class="text-center text-muted py-2 bg-light font-monospace small"> Pending for Cutting (Cutting Entry Pending) </td> <?php else: ?> <td class="text-center fw-bold text-primary"><?= h($r['cut_name'] ?: '-') ?></td> <td class="text-success fw-bold text-center"><?= h(format_report_pcs($r['fresh'])) ?></td> <td class="text-danger text-center"><?= h(format_report_pcs($r['second_cut'])) ?></td> <td class="text-warning fw-bold text-end"><?= h($cutting_pct) ?>%</td> <td class="text-info fw-bold text-end"><?= h($second_pct) ?>%</td> <?php endif; ?> <!-- COMMENT CELL CONTROLLER WITH FIXED DIRECT DETECTION --> <td class="text-center"> <?php if (!empty($r['active_comment']) && trim((string)$r['active_comment']) !== ''): ?> <button type="button" class="btn btn-link p-0 text-decoration-none no-print" onclick="openCommentModal('<?= h($r['gray_id']) ?>')" title="<?= h($r['active_comment']) ?>" style="font-size: 14px;"> 📝 </button> <span class="d-none d-print-inline" style="font-size: 10px;">Yes</span> <?php else: ?> <button type="button" class="btn btn-link p-0 green-plus-btn no-print" onclick="openCommentModal('<?= h($r['gray_id']) ?>')" title="Add Comment"> ➕ </button> <span class="d-none d-print-inline" style="font-size: 10px;">-</span> <?php endif; ?> </td> <td class="text-center fw-bold status-cell"> <?php if($is_not_cut): ?> <span class="badge bg-danger">Cutting Pending</span> <?php else: ?> <span class="badge bg-success">Cut Completed</span> <?php endif; ?> </td> <td class="no-print text-center"> <div class="btn-group btn-group-sm" role="group"> <button type="button" onclick="changeCheckStatus('<?= h($r['gray_id']) ?>', 'ok')" id="btn_ok_<?= h($r['gray_id']) ?>" class="btn <?= $r['check_status'] === 'ok' ? 'btn-success' : 'btn-outline-success' ?> btn-toggle-check"> OK </button> <button type="button" onclick="changeCheckStatus('<?= h($r['gray_id']) ?>', 'need_check')" id="btn_need_<?= h($r['gray_id']) ?>" class="btn <?= $r['check_status'] === 'need_check' ? 'btn-danger' : 'btn-outline-danger' ?> btn-toggle-check"> NOT OK </button> </div> </td> </tr> <?php endforeach; ?> <?php if (empty($rows)): ?> <tr> <td colspan="16" class="text-center text-muted py-4">No lifecycle data found for specified filter states.</td> </tr> <?php endif; ?> </tbody> </table> </div> </div> </div> <!-- --- DYNAMIC DEDICATED COMMENT POPUP MODAL --- --> <div class="modal fade no-print" id="lifecycleCommentModal" tabindex="-1" aria-labelledby="lifecycleCommentModalLabel" aria-hidden="true"> <div class="modal-dialog modal-dialog-centered"> <div class="modal-content shadow-lg border-0"> <div class="modal-header bg-primary text-white py-2"> <h6 class="modal-title" id="lifecycleCommentModalLabel">Manage Comment (Grey ID: <span id="comment_modal_gray_id"></span>)</h6> <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body p-3"> <input type="hidden" id="comment_target_gray_id"> <div class="mb-3"> <label for="comment_textarea" class="form-label small fw-bold text-secondary">Write your solution comment:</label> <textarea id="comment_textarea" class="form-control text-dark fw-bold border-secondary" rows="4" placeholder="Enter notes or updates here..."></textarea> </div> <div id="comment_error_log" class="text-danger small fw-bold mb-0 d-none"></div> </div> <div class="modal-footer py-1 bg-light"> <button type="button" class="btn btn-secondary btn-sm px-3" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-primary btn-sm px-3 fw-bold" onclick="submitLifecycleComment()">Save Comment</button> </div> </div> </div> </div> <div class="modal fade no-print" id="designImageModal" tabindex="-1" aria-labelledby="designImageModalLabel" aria-hidden="true"> <div class="modal-dialog modal-dialog-centered modal-lg"> <div class="modal-content shadow-lg border-0"> <div class="modal-header bg-dark text-white py-2"> <h5 class="modal-title" id="designImageModalLabel">Design Assets (Grey ID: <span id="modal_gray_id_span"></span>)</h5> <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body text-center bg-light p-3 position-relative"> <div id="modal_spinner" class="spinner-border text-primary my-4 d-none" role="status"> <span class="visually-hidden">Loading...</span> </div> <div class="d-flex align-items-center justify-content-center" style="min-height: 40vh;"> <img id="modal_target_img" src="" alt="Design Asset Preview" class="img-fluid rounded shadow-sm border" style="max-height: 65vh; object-fit: contain;"> </div> <div id="modal_nav_controls" class="d-flex justify-content-between align-items-center mt-3 px-2"> <button type="button" class="btn btn-dark btn-sm px-3" id="btn_modal_prev" onclick="navigateModalImage(-1)"> « Prev </button> <span class="fw-bold text-muted small" id="modal_image_counter_text">Image 1 of 1</span> <button type="button" class="btn btn-dark btn-sm px-3" id="btn_modal_next" onclick="navigateModalImage(1)"> Next » </button> </div> </div> <div class="modal-footer py-1 bg-light"> <button type="button" class="btn btn-secondary btn-sm px-3" data-bs-dismiss="modal">Close</button> </div> </div> </div> </div> <script> /* Horizontal Scroll Lock Sync Engine */ document.addEventListener('DOMContentLoaded', function() { const header = document.querySelector('.lifecycle-header-wrapper'); const body = document.querySelector('.lifecycle-body-wrapper'); if (header && body) { body.addEventListener('scroll', function() { header.scrollLeft = body.scrollLeft; }); } }); /* Multi-Image Navigation Handler Engine */ let activeImagesArray = []; let activeImageIndex = 0; function openMultiDesignModal(imagesList, grayId) { if (!Array.isArray(imagesList) || imagesList.length === 0) return; activeImagesArray = imagesList; activeImageIndex = 0; document.getElementById('modal_gray_id_span').innerText = grayId; renderModalImageState(); const myModal = new bootstrap.Modal(document.getElementById('designImageModal')); myModal.show(); } function renderModalImageState() { const imgEl = document.getElementById('modal_target_img'); const spinner = document.getElementById('modal_spinner'); const counterText = document.getElementById('modal_image_counter_text'); const btnPrev = document.getElementById('btn_modal_prev'); const btnNext = document.getElementById('btn_modal_next'); imgEl.style.display = 'none'; spinner.classList.remove('d-none'); counterText.innerText = `Image ${activeImageIndex + 1} of ${activeImagesArray.length}`; btnPrev.disabled = (activeImageIndex === 0); btnNext.disabled = (activeImageIndex === activeImagesArray.length - 1); imgEl.src = activeImagesArray[activeImageIndex]; imgEl.onload = function() { spinner.classList.add('d-none'); imgEl.style.display = 'inline-block'; }; imgEl.onerror = function() { spinner.classList.add('d-none'); imgEl.style.display = 'inline-block'; alert('This asset chunk could not be populated dynamically.'); }; } function navigateModalImage(direction) { let targetIndex = activeImageIndex + direction; if (targetIndex >= 0 && targetIndex < activeImagesArray.length) { activeImageIndex = targetIndex; renderModalImageState(); } } function changeCheckStatus(grayId, targetStatus) { const formData = new FormData(); formData.append('gray_id', grayId); formData.append('check_status', targetStatus); fetch('?action=update_check_status', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if(data.success) { window.location.reload(); } else { alert('Status update failed: ' + (data.message || 'Unknown Context')); } }) .catch(error => { console.error('Error:', error); alert('Communication breakdown during async status dispatch.'); }); } /* Comment Modal CRUD Dispatch Engines */ let commentBsModal = null; function openCommentModal(grayId) { document.getElementById('comment_modal_gray_id').innerText = grayId; document.getElementById('comment_target_gray_id').value = grayId; document.getElementById('comment_textarea').value = ''; document.getElementById('comment_error_log').classList.add('d-none'); fetch('?action=get_comment&gray_id=' + encodeURIComponent(grayId)) .then(res => res.json()) .then(data => { if (data.success) { document.getElementById('comment_textarea').value = data.comment; commentBsModal = new bootstrap.Modal(document.getElementById('lifecycleCommentModal')); commentBsModal.show(); } else { alert('Could not fetch existing comment logs.'); } }) .catch(err => console.error(err)); } function submitLifecycleComment() { const grayId = document.getElementById('comment_target_gray_id').value; const commentVal = document.getElementById('comment_textarea').value.trim(); const errLog = document.getElementById('comment_error_log'); const formData = new FormData(); formData.append('gray_id', grayId); formData.append('comment', commentVal); fetch('?action=save_comment', { method: 'POST', body: formData }) .then(res => res.json()) .then(data => { if (data.success) { if (commentBsModal) { commentBsModal.hide(); } window.location.reload(); } else { errLog.innerText = data.message || 'Error occurred while saving comment.'; errLog.classList.remove('d-none'); } }) .catch(err => { console.error(err); errLog.innerText = 'Server processing failure.'; errLog.classList.remove('d-none'); }); } </script> <?php } if (($_GET['export'] ?? '') === 'pdf') { $autoloadCandidates = [ __DIR__ . '/lib/dompdf/vendor/autoload.php', __DIR__ . '/vendor/autoload.php', __DIR__ . '/dirname(__DIR__) . \'/vendor/autoload.php\'', ]; $autoloadPath = null; foreach ($autoloadCandidates as $candidate) { if (is_file($candidate)) { $autoloadPath = $candidate; break; } } if ($autoloadPath === null) { throw new RuntimeException('PDF library not found.'); } require_once $autoloadPath; $dompdf = new \Dompdf\Dompdf([ 'isHtml5ParserEnabled' => true, 'isRemoteEnabled' => true, ]); ob_start(); ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Cutting Life Cycle Report</title> </head> <body> <?php render_cutting_lifecycle_report($rows, $from, $to, $sort_by, $order, $check_filter, $fy_start_year, true); ?> </body> </html> <?php $html = ob_get_clean(); $dompdf->loadHtml($html, 'UTF-8'); $dompdf->setPaper('A4', 'landscape'); $dompdf->render(); $dompdf->stream('cutting-lifecycle-report-' . $from . '-to-' . $to . '.pdf', ['Attachment' => true]); exit; } require_once __DIR__ . '/partials/header.php'; render_cutting_lifecycle_report($rows, $from, $to, $sort_by, $order, $check_filter, $fy_start_year, false); require_once __DIR__ . '/partials/footer.php'; ?>