plant-analysis.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <?php
  2. /**
  3. * Plant analysis results display page.
  4. * Loads a single plant_records row by rid + rand params.
  5. */
  6. require_once __DIR__ . '/../../../config/database.php';
  7. require_once __DIR__ . '/../../../lib/auth.php';
  8. if (session_status() === PHP_SESSION_NONE) {
  9. session_start();
  10. }
  11. requireLogin();
  12. $pdo = getDBConnection();
  13. $userId = getCurrentUserId();
  14. $recordId = (int) ($_GET['rid'] ?? 0);
  15. $randId = (float) ($_GET['rand'] ?? 0);
  16. $row = null;
  17. $specs = [];
  18. if ($recordId > 0) {
  19. $stmt = $pdo->prepare('SELECT * FROM plant_records WHERE id = ? AND rand = ? LIMIT 1');
  20. $stmt->execute([$recordId, $randId]);
  21. $row = $stmt->fetch();
  22. }
  23. if ($row) {
  24. $crop = $row['crop'] ?? '';
  25. $stmt = $pdo->prepare('SELECT * FROM plant_specifications WHERE crop = ? LIMIT 1');
  26. $stmt->execute([$crop]);
  27. $specs = $stmt->fetch() ?: [];
  28. }
  29. $h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
  30. function statusBar(float $found, float $min, float $max): string {
  31. if ($max <= 0) return '<td colspan="3" class="text-muted text-center small">N/A</td>';
  32. $pct = ($max > $min) ? min(100, max(0, ($found - $min) / ($max - $min) * 100)) : 0;
  33. if ($found < $min) {
  34. $bar = '<td class="text-center"><div class="progress"><div class="progress-bar bg-danger" style="width:100%"></div></div></td>';
  35. $bar .= '<td></td><td></td>';
  36. } elseif ($found > $max) {
  37. $bar = '<td></td><td></td>';
  38. $bar .= '<td class="text-center"><div class="progress"><div class="progress-bar bg-warning" style="width:100%"></div></div></td>';
  39. } else {
  40. $bar = '<td></td>';
  41. $bar .= '<td class="text-center"><div class="progress"><div class="progress-bar bg-success" style="width:' . round($pct) . '%"></div></div></td>';
  42. $bar .= '<td></td>';
  43. }
  44. return $bar;
  45. }
  46. ?>
  47. <!doctype html>
  48. <html lang="en">
  49. <head>
  50. <meta charset="UTF-8">
  51. <meta name="viewport" content="width=device-width, initial-scale=1">
  52. <title>Plant Analysis | Crop Monitor</title>
  53. <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
  54. <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.css" crossorigin="anonymous" rel="stylesheet">
  55. <link href="/client-assets/css/dashboard.css" rel="stylesheet">
  56. <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" crossorigin="anonymous"></script>
  57. <style>
  58. @media print {
  59. @page { size: A4 portrait; margin: 0.5cm; }
  60. .d-print-none { display: none !important; }
  61. }
  62. .progress { border-radius: 0 !important; }
  63. table.chart { width: 100%; border-collapse: collapse; }
  64. table.chart th, table.chart td { padding: 3px 6px; font-size: 0.85rem; }
  65. .chart-header th { background: #343a40; color: white; }
  66. .chart-header-sub th { background: #6c757d; color: white; }
  67. .lightgreen { background: #d4edda !important; color: #155724 !important; }
  68. .lightred { background: #f8d7da !important; color: #721c24 !important; }
  69. .lightpurple { background: #e2d9f3 !important; color: #432874 !important; }
  70. .stripe-1 { background: #f8f9fa; }
  71. .border-left { border-left: 1px solid #dee2e6; }
  72. .border-right { border-right: 1px solid #dee2e6; }
  73. .border-bottom { border-bottom: 1px solid #dee2e6; }
  74. .border-top { border-top: 1px solid #dee2e6; }
  75. </style>
  76. </head>
  77. <body>
  78. <div class="container" id="content">
  79. <?php if (!$row): ?>
  80. <div class="alert alert-danger mt-4">Record not found or access denied.</div>
  81. <?php else: ?>
  82. <div class="row mb-3">
  83. <div class="col-md-3">
  84. <img class="img-fluid" src="/client-assets/images/crop-monitor.png" alt="Crop Monitor">
  85. </div>
  86. </div>
  87. <div class="d-print-none mb-3">
  88. <button class="btn btn-success btn-sm downloadPDF">
  89. <i class="fas fa-download me-1"></i>Download PDF
  90. </button>
  91. </div>
  92. <div class="row">
  93. <div class="col-md-12 text-center fw-bold h4">ANALYSIS RESULTS</div>
  94. </div>
  95. <hr class="p-1 m-1">
  96. <table class="chart">
  97. <tbody>
  98. <tr class="chart-header">
  99. <th colspan="3" class="text-center border-left border-right border-top">ELEMENT</th>
  100. <th colspan="3" class="text-center border-right border-top">STATUS</th>
  101. </tr>
  102. <tr class="chart-header-sub">
  103. <th class="text-center border-bottom border-left">Elements</th>
  104. <th class="text-center border-bottom">Desired</th>
  105. <th class="text-center border-bottom">Found</th>
  106. <th class="text-center stripe-1">Deficient</th>
  107. <th class="text-center stripe-1">Ideal</th>
  108. <th class="text-center border-right stripe-1">High</th>
  109. </tr>
  110. <tr>
  111. <td class="border-left" colspan="6"></td>
  112. </tr>
  113. <tr class="chart-header-sub">
  114. <th colspan="3" class="border-left text-center lightgreen">MAJOR ELEMENTS</th>
  115. <th class="text-center border-left stripe-1"></th>
  116. <th class="text-center border-left stripe-1"></th>
  117. <th class="text-center border-left border-right stripe-1"></th>
  118. </tr>
  119. <?php
  120. $majorElements = [
  121. ['n', 'Nitrogen', '%'],
  122. ['p', 'Phosphorus', '%'],
  123. ['k', 'Potassium', '%'],
  124. ['mg', 'Magnesium', '%'],
  125. ['ca', 'Calcium', '%'],
  126. ['na', 'Sodium', '%'],
  127. ];
  128. foreach ($majorElements as [$el, $nutrient, $unit]):
  129. $found = (float) ($row[$el] ?? 0);
  130. $min = (float) ($specs['min_' . $el] ?? 0);
  131. $max = (float) ($specs['max_' . $el] ?? 0);
  132. $desired = ($min > 0 || $max > 0) ? number_format($min, 2) . '–' . number_format($max, 2) : '—';
  133. ?>
  134. <tr>
  135. <td class="border-left"><?= $h($nutrient) ?> (<?= $h($unit) ?>)</td>
  136. <td><?= $h($desired) ?></td>
  137. <td><?= $found > 0 ? number_format($found, 3) : '—' ?></td>
  138. <?= statusBar($found, $min, $max) ?>
  139. </tr>
  140. <?php endforeach; ?>
  141. <tr><td colspan="6" class="border-left"></td></tr>
  142. <tr class="chart-header-sub">
  143. <th colspan="3" class="border-left text-center lightred">TRACE ELEMENTS</th>
  144. <th class="text-center border-left stripe-1"></th>
  145. <th class="text-center border-left stripe-1"></th>
  146. <th class="text-center border-left border-right stripe-1"></th>
  147. </tr>
  148. <tr><td colspan="6" class="border-left"></td></tr>
  149. <?php
  150. $traceElements = [
  151. ['fe', 'Iron', 'ppm'],
  152. ['mn', 'Manganese', 'ppm'],
  153. ['zn', 'Zinc', 'ppm'],
  154. ['cu', 'Copper', 'ppm'],
  155. ['b', 'Boron', 'ppm'],
  156. ];
  157. foreach ($traceElements as [$el, $nutrient, $unit]):
  158. $found = (float) ($row[$el] ?? 0);
  159. $min = (float) ($specs['min_' . $el] ?? 0);
  160. $max = (float) ($specs['max_' . $el] ?? 0);
  161. $desired = ($min > 0 || $max > 0) ? number_format($min, 1) . '–' . number_format($max, 1) : '—';
  162. ?>
  163. <tr>
  164. <td class="border-left"><?= $h($nutrient) ?> (<?= $h($unit) ?>)</td>
  165. <td><?= $h($desired) ?></td>
  166. <td><?= $found > 0 ? number_format($found, 2) : '—' ?></td>
  167. <?= statusBar($found, $min, $max) ?>
  168. </tr>
  169. <?php endforeach; ?>
  170. <tr><td colspan="6" class="border-left"></td></tr>
  171. <tr class="chart-header-sub">
  172. <th colspan="3" class="border-left text-center lightpurple">MACRO ELEMENTS</th>
  173. <th class="text-center border-left stripe-1"></th>
  174. <th class="text-center border-left stripe-1"></th>
  175. <th class="text-center border-left border-right stripe-1"></th>
  176. </tr>
  177. <?php
  178. $macroElements = [
  179. ['m', 'Molybdenum', 'ppm'],
  180. ['co', 'Cobalt', 'ppm'],
  181. ['se', 'Selenium', 'ppm'],
  182. ['cl', 'Chloride', 'ppm'],
  183. ['c', 'Carbon', 'ppm'],
  184. ];
  185. foreach ($macroElements as [$el, $nutrient, $unit]):
  186. $found = (float) ($row[$el] ?? 0);
  187. $min = (float) ($specs['min_' . $el] ?? 0);
  188. $max = (float) ($specs['max_' . $el] ?? 0);
  189. $desired = ($min > 0 || $max > 0) ? number_format($min, 2) . '–' . number_format($max, 2) : '—';
  190. ?>
  191. <tr>
  192. <td class="border-left"><?= $h($nutrient) ?> (<?= $h($unit) ?>)</td>
  193. <td><?= $h($desired) ?></td>
  194. <td><?= $found > 0 ? number_format($found, 3) : '—' ?></td>
  195. <?= statusBar($found, $min, $max) ?>
  196. </tr>
  197. <?php endforeach; ?>
  198. <tr>
  199. <td class="border-bottom border-left"></td>
  200. <td class="border-bottom"></td>
  201. <td class="border-bottom"></td>
  202. <td class="border-bottom"></td>
  203. <td class="border-bottom"></td>
  204. <td class="border-bottom border-right"></td>
  205. </tr>
  206. </tbody>
  207. </table>
  208. <div class="mt-4 small text-muted">
  209. <p><i class="fa fa-leaf" style="color:green"></i> It is always an advantage to assess tissue analysis results along with a corresponding soil analysis for more accurate diagnosis of plant nutrient status.</p>
  210. <p><i class="fa fa-leaf" style="color:green"></i> Trace element levels — manganese, copper and zinc — can all be affected by fungicide spray residues, giving misleading results.</p>
  211. <p><i class="fa fa-leaf" style="color:green"></i> Talk to your qualified consultant to make a plan for correction or maintenance of the found nutrient levels.</p>
  212. <p style="font-style:italic;font-size:9px;">Desired ranges indexed from: CSIRO Plant Analysis Handbook 2nd Ed. Hill Laboratories consultants guide. PIRSA Soil and Plant Analysis.</p>
  213. <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>
  214. </div>
  215. <?php endif; ?>
  216. </div>
  217. <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
  218. <script>
  219. document.querySelector('.downloadPDF') && document.querySelector('.downloadPDF').addEventListener('click', function () {
  220. var opt = {
  221. margin: 3,
  222. filename: 'plant-analysis.pdf',
  223. image: { type: 'jpeg', quality: 1.0 },
  224. html2canvas: { scale: 2, letterRendering: true, windowWidth: 1024 },
  225. jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' }
  226. };
  227. html2pdf().from(document.getElementById('content')).set(opt).save();
  228. });
  229. </script>
  230. </body>
  231. </html>