water-analysis-pdf.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. <?php
  2. /**
  3. * water-analysis-pdf.php
  4. *
  5. * Displays water quality analysis results for a single water_records row.
  6. * Standalone PDF-printable page. Accessed via rid + rand URL params.
  7. */
  8. require_once __DIR__ . '/../../../config/database.php';
  9. require_once __DIR__ . '/../../../lib/auth.php';
  10. if (session_status() === PHP_SESSION_NONE) {
  11. session_start();
  12. }
  13. requireLogin();
  14. $pdo = getDBConnection();
  15. $userId = getCurrentUserId();
  16. $recordId = (int) ($_GET['rid'] ?? 0);
  17. $randId = (float) ($_GET['rand'] ?? 0);
  18. $row = null;
  19. $specs = [];
  20. if ($recordId > 0) {
  21. $stmt = $pdo->prepare('SELECT * FROM water_records WHERE id = ? AND rand = ? LIMIT 1');
  22. $stmt->execute([$recordId, $randId]);
  23. $row = $stmt->fetch();
  24. }
  25. if ($row) {
  26. $type = $row['analysis_type'] ?? '';
  27. $stmt = $pdo->prepare('SELECT * FROM water_specifications WHERE type = ? LIMIT 1');
  28. $stmt->execute([$type]);
  29. $specs = $stmt->fetch() ?: [];
  30. }
  31. $h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
  32. function waterBar(float $found, float $min, float $max): string {
  33. if ($max <= 0) return '<td colspan="3" class="text-muted text-center small">N/A</td>';
  34. $pct = ($max > $min) ? min(100, max(0, ($found - $min) / ($max - $min) * 100)) : 0;
  35. if ($found < $min) {
  36. return '<td class="text-center"><div class="progress"><div class="progress-bar bg-danger" style="width:100%"></div></div></td>'
  37. . '<td></td><td></td>';
  38. } elseif ($found > $max) {
  39. return '<td></td><td></td>'
  40. . '<td class="text-center"><div class="progress"><div class="progress-bar bg-warning" style="width:100%"></div></div></td>';
  41. } else {
  42. return '<td></td>'
  43. . '<td class="text-center"><div class="progress"><div class="progress-bar bg-success" style="width:' . round($pct) . '%"></div></div></td>'
  44. . '<td></td>';
  45. }
  46. }
  47. ?>
  48. <!doctype html>
  49. <html lang="en">
  50. <head>
  51. <meta charset="UTF-8">
  52. <meta name="viewport" content="width=device-width, initial-scale=1">
  53. <title>Water Analysis | Crop Monitor</title>
  54. <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
  55. <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.css" crossorigin="anonymous" rel="stylesheet">
  56. <link href="/client-assets/css/dashboard-2021.css" rel="stylesheet">
  57. <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" crossorigin="anonymous"></script>
  58. <style>
  59. @media print {
  60. @page { size: A4 portrait; margin: 0.5cm; }
  61. .d-print-none { display: none !important; }
  62. }
  63. .progress { border-radius: 0 !important; }
  64. table.chart { width: 100%; border-collapse: collapse; }
  65. table.chart th, table.chart td { padding: 3px 6px; font-size: 0.85rem; }
  66. .chart-header th { background: #343a40; color: white; }
  67. .chart-header-sub th { background: #6c757d; color: white; }
  68. .lightgreen { background: #d4edda !important; color: #155724 !important; }
  69. .lightred { background: #f8d7da !important; color: #721c24 !important; }
  70. .lightblue { background: #cce5ff !important; color: #004085 !important; }
  71. .lightpurple { background: #e2d9f3 !important; color: #432874 !important; }
  72. .stripe-1 { background: #f8f9fa; }
  73. .border-left { border-left: 1px solid #dee2e6; }
  74. .border-right { border-right: 1px solid #dee2e6; }
  75. .border-bottom { border-bottom: 1px solid #dee2e6; }
  76. .border-top { border-top: 1px solid #dee2e6; }
  77. </style>
  78. </head>
  79. <body>
  80. <div class="container" id="content">
  81. <?php if (!$row): ?>
  82. <div class="alert alert-danger mt-4">Record not found or access denied.</div>
  83. <?php else: ?>
  84. <div class="row mb-3">
  85. <div class="col-md-3">
  86. <img class="img-fluid" src="/client-assets/images/crop-monitor.png" alt="Crop Monitor">
  87. </div>
  88. </div>
  89. <div class="d-print-none mb-3">
  90. <button class="btn btn-success btn-sm downloadPDF">
  91. <i class="fas fa-download me-1"></i>Download PDF
  92. </button>
  93. </div>
  94. <!-- Client info -->
  95. <table class="table table-sm table-bordered mb-3" style="font-size:0.85rem;">
  96. <tbody>
  97. <tr>
  98. <th class="w-25">Client</th>
  99. <td><?= $h($row['client_name'] ?? '') ?></td>
  100. <th class="w-25">Lab No</th>
  101. <td><?= $h($row['lab_no'] ?? '') ?></td>
  102. </tr>
  103. <tr>
  104. <th>Sample ID</th>
  105. <td><?= $h($row['sample_id'] ?? '') ?></td>
  106. <th>Site ID</th>
  107. <td><?= $h($row['site_id'] ?? '') ?></td>
  108. </tr>
  109. <tr>
  110. <th>Analysis Type</th>
  111. <td><?= $h($row['analysis_type'] ?? '') ?></td>
  112. <th>Date Sampled</th>
  113. <td><?= $h($row['date_sampled'] ?? '') ?></td>
  114. </tr>
  115. </tbody>
  116. </table>
  117. <div class="row">
  118. <div class="col-md-12 text-center fw-bold h4">ANALYSIS RESULTS</div>
  119. </div>
  120. <hr class="p-1 m-1">
  121. <table class="chart">
  122. <tbody>
  123. <tr class="chart-header">
  124. <th colspan="3" class="text-center border-left border-right border-top">ELEMENT</th>
  125. <th colspan="3" class="text-center border-right border-top">STATUS</th>
  126. </tr>
  127. <tr class="chart-header-sub">
  128. <th class="text-center border-bottom border-left">Parameter</th>
  129. <th class="text-center border-bottom">Desired</th>
  130. <th class="text-center border-bottom">Found</th>
  131. <th class="text-center stripe-1">Deficit</th>
  132. <th class="text-center stripe-1">Ideal</th>
  133. <th class="text-center border-right stripe-1">High</th>
  134. </tr>
  135. <tr><td class="border-left" colspan="6"></td></tr>
  136. <tr class="chart-header-sub">
  137. <th colspan="3" class="border-left text-center lightblue">GENERAL PARAMETERS</th>
  138. <th class="text-center border-left stripe-1"></th>
  139. <th class="text-center border-left stripe-1"></th>
  140. <th class="text-center border-left border-right stripe-1"></th>
  141. </tr>
  142. <?php
  143. $generalParams = [
  144. ['ph', 'pH', ''],
  145. ['cond_dsm', 'Conductivity', 'dS/m'],
  146. ['hco3-', 'Bicarbonate (HCO₃)', 'ppm'],
  147. ];
  148. foreach ($generalParams as [$col, $label, $unit]):
  149. $found = (float)($row[$col] ?? 0);
  150. $min = (float)($specs[$col . '_min'] ?? 0);
  151. $max = (float)($specs[$col . '_max'] ?? 0);
  152. $desired = ($min > 0 || $max > 0) ? number_format($min, 2) . '–' . number_format($max, 2) : '—';
  153. $display = $unit ? $label . ' (' . $unit . ')' : $label;
  154. ?>
  155. <tr>
  156. <td class="border-left"><?= $h($display) ?></td>
  157. <td><?= $h($desired) ?></td>
  158. <td><?= $found > 0 ? number_format($found, 3) : '—' ?></td>
  159. <?= waterBar($found, $min, $max) ?>
  160. </tr>
  161. <?php endforeach; ?>
  162. <tr><td class="border-left" colspan="6"></td></tr>
  163. <tr class="chart-header-sub">
  164. <th colspan="3" class="border-left text-center lightgreen">MAJOR ELEMENTS</th>
  165. <th class="text-center border-left stripe-1"></th>
  166. <th class="text-center border-left stripe-1"></th>
  167. <th class="text-center border-left border-right stripe-1"></th>
  168. </tr>
  169. <?php
  170. $majorElements = [
  171. ['nh4', 'Ammonium Nitrogen (NH₄)', 'ppm'],
  172. ['no3', 'Nitrate Nitrogen (NO₃)', 'ppm'],
  173. ['p', 'Phosphorus', 'ppm'],
  174. ['k', 'Potassium', 'ppm'],
  175. ['s', 'Sulphur', 'ppm'],
  176. ['ca', 'Calcium', 'ppm'],
  177. ['mg', 'Magnesium', 'ppm'],
  178. ['na', 'Sodium', 'ppm'],
  179. ];
  180. foreach ($majorElements as [$col, $label, $unit]):
  181. $found = (float)($row[$col] ?? 0);
  182. $min = (float)($specs[$col . '_min'] ?? 0);
  183. $max = (float)($specs[$col . '_max'] ?? 0);
  184. $desired = ($min > 0 || $max > 0) ? number_format($min, 1) . '–' . number_format($max, 1) : '—';
  185. ?>
  186. <tr>
  187. <td class="border-left"><?= $h($label) ?> (<?= $h($unit) ?>)</td>
  188. <td><?= $h($desired) ?></td>
  189. <td><?= $found > 0 ? number_format($found, 2) : '—' ?></td>
  190. <?= waterBar($found, $min, $max) ?>
  191. </tr>
  192. <?php endforeach; ?>
  193. <tr><td class="border-left" colspan="6"></td></tr>
  194. <tr class="chart-header-sub">
  195. <th colspan="3" class="border-left text-center lightred">TRACE ELEMENTS</th>
  196. <th class="text-center border-left stripe-1"></th>
  197. <th class="text-center border-left stripe-1"></th>
  198. <th class="text-center border-left border-right stripe-1"></th>
  199. </tr>
  200. <?php
  201. $traceElements = [
  202. ['fe', 'Iron', 'ppm'],
  203. ['mn', 'Manganese', 'ppm'],
  204. ['zn', 'Zinc', 'ppm'],
  205. ['cu', 'Copper', 'ppm'],
  206. ['b', 'Boron', 'ppm'],
  207. ['m', 'Molybdenum','ppm'],
  208. ['co', 'Cobalt', 'ppm'],
  209. ];
  210. foreach ($traceElements as [$col, $label, $unit]):
  211. $found = (float)($row[$col] ?? 0);
  212. $min = (float)($specs[$col . '_min'] ?? 0);
  213. $max = (float)($specs[$col . '_max'] ?? 0);
  214. $desired = ($min > 0 || $max > 0) ? number_format($min, 2) . '–' . number_format($max, 2) : '—';
  215. ?>
  216. <tr>
  217. <td class="border-left"><?= $h($label) ?> (<?= $h($unit) ?>)</td>
  218. <td><?= $h($desired) ?></td>
  219. <td><?= $found > 0 ? number_format($found, 3) : '—' ?></td>
  220. <?= waterBar($found, $min, $max) ?>
  221. </tr>
  222. <?php endforeach; ?>
  223. <tr>
  224. <td class="border-bottom border-left"></td>
  225. <td class="border-bottom"></td>
  226. <td class="border-bottom"></td>
  227. <td class="border-bottom"></td>
  228. <td class="border-bottom"></td>
  229. <td class="border-bottom border-right"></td>
  230. </tr>
  231. </tbody>
  232. </table>
  233. <div class="mt-4 small text-muted">
  234. <p><i class="fa fa-tint" style="color:#007bff"></i> Water analysis results should be interpreted in the context of intended use (irrigation, drinking, stock water).</p>
  235. <p><i class="fa fa-tint" style="color:#007bff"></i> Talk to your qualified consultant to make a plan for correction or maintenance of the found parameter levels.</p>
  236. <p style="font-style:italic;font-size:9px;">Any recommendations provided by Cropmonitor are advice only. We are not paid consultants and are not covered to accept responsibility for any of our suggestions.</p>
  237. </div>
  238. <?php endif; ?>
  239. </div>
  240. <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
  241. <script>
  242. document.querySelector('.downloadPDF') && document.querySelector('.downloadPDF').addEventListener('click', function () {
  243. var opt = {
  244. margin: 3,
  245. filename: 'water-analysis.pdf',
  246. image: { type: 'jpeg', quality: 1.0 },
  247. html2canvas: { scale: 2, letterRendering: true, windowWidth: 1024 },
  248. jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' }
  249. };
  250. html2pdf().from(document.getElementById('content')).set(opt).save();
  251. });
  252. </script>
  253. </body>
  254. </html>