plant-analysis.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. <?php
  2. /**
  3. * dashboard/crop-analysis/plant-test-data/plant-analysis.php
  4. *
  5. * Plant tissue analysis results display page.
  6. */
  7. require_once __DIR__ . '/../../../config/database.php';
  8. require_once __DIR__ . '/../../../lib/auth.php';
  9. require_once __DIR__ . '/../../../lib/print_auth.php';
  10. if (session_status() === PHP_SESSION_NONE) {
  11. session_start();
  12. }
  13. $recordId = (int) ($_GET['rid'] ?? 0);
  14. $randId = trim( $_GET['rand'] ?? '');
  15. $clientId = (int) ($_GET['cid'] ?? 0);
  16. $printMode = isset($_GET['print']);
  17. if ($printMode) {
  18. authenticatePrintPage($recordId, $randId);
  19. } else {
  20. requireLogin();
  21. }
  22. $pdo = getDBConnection();
  23. $userId = $printMode ? null : getCurrentUserId();
  24. $row = null;
  25. $specs = [];
  26. if ($recordId > 0 && $randId !== '') {
  27. $stmt = $pdo->prepare('SELECT * FROM plant_records WHERE id = ? AND rand = ? LIMIT 1');
  28. $stmt->execute([$recordId, $randId]);
  29. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  30. }
  31. if ($row) {
  32. // Column is plant_type, not crop
  33. $plantType = $row['crop_type'] ?? '';
  34. if ($plantType !== '') {
  35. $stmtSpec = $pdo->prepare('SELECT * FROM plant_specifications WHERE plant_type = ? LIMIT 1');
  36. $stmtSpec->execute([$plantType]);
  37. $specs = $stmtSpec->fetch(PDO::FETCH_ASSOC) ?: [];
  38. }
  39. }
  40. // ── Spec column name map ──────────────────────────────────────────────────────
  41. // The plant_specifications table has inconsistent column casing — map each
  42. // element key to its actual min/max column names in the DB.
  43. $specCols = [
  44. 'n' => ['n_min', 'n_max'],
  45. 'p' => ['P_Min', 'P_Max'],
  46. 'k' => ['K_Min', 'K_Max'],
  47. 's' => ['S_Min', 'S_Max'],
  48. 'mg' => ['Mg_Min', 'Mg_Max'],
  49. 'ca' => ['Ca_Min', 'Ca_Max'],
  50. 'na' => ['Na_Min', 'Na_Max'],
  51. 'fe' => ['Fe_Min', 'Fe_Max'],
  52. 'mn' => ['Mn_Min', 'Mn_Max'],
  53. 'zn' => ['Zn_Min', 'Zn_Max'],
  54. 'cu' => ['Cu_Min', 'Cu_Max'],
  55. 'b' => ['B_Min', 'B_Max'],
  56. 'm' => ['M_Min', 'M_Max'],
  57. 'co' => ['Co_min', 'Co_max'],
  58. 'se' => ['se_min', 'se_max'],
  59. 'cl' => ['cl_min', 'cl_max'],
  60. 'c' => ['c_min', 'c_max'],
  61. ];
  62. $h = fn($v) => htmlspecialchars((string)($v ?? ''), ENT_QUOTES, 'UTF-8');
  63. function statusBar(float $found, float $min, float $max): string {
  64. if ($max <= 0) return '<td colspan="3" class="text-muted text-center small">N/A</td>';
  65. if ($found < $min) {
  66. return '<td class="text-center"><div class="progress"><div class="progress-bar bg-danger" style="width:100%"></div></div></td><td></td><td></td>';
  67. }
  68. if ($found > $max) {
  69. return '<td></td><td></td><td class="text-center"><div class="progress"><div class="progress-bar bg-warning" style="width:100%"></div></div></td>';
  70. }
  71. $pct = ($max > $min) ? round(min(100, ($found - $min) / ($max - $min) * 100)) : 50;
  72. return '<td></td><td class="text-center"><div class="progress"><div class="progress-bar bg-success" style="width:' . $pct . '%"></div></div></td><td></td>';
  73. }
  74. $today = date('jS F Y');
  75. $pageTitle = 'Plant Analysis' . (!empty($row['client_name']) ? ' — ' . $row['client_name'] : '');
  76. $siteName = 'Crop Monitor';
  77. if (!$printMode) {
  78. include __DIR__ . '/../../../layouts/header.php';
  79. include __DIR__ . '/../../../layouts/navbar.php';
  80. }
  81. ?>
  82. <?php if (!$printMode): ?>
  83. <link rel="stylesheet" href="/client-assets/home/css/graphPrint.css" media="print">
  84. <style>
  85. .progress { border-radius: 0 !important; }
  86. table.chart { width: 100%; border-collapse: collapse; }
  87. table.chart th, table.chart td { padding: 3px 6px; font-size: 0.85rem; }
  88. .chart-header th { background: #343a40; color: white; }
  89. .chart-header-sub th { background: #6c757d; color: white; }
  90. .lightgreen { background: #d4edda !important; color: #155724 !important; }
  91. .lightred { background: #f8d7da !important; color: #721c24 !important; }
  92. .lightpurple { background: #e2d9f3 !important; color: #432874 !important; }
  93. .stripe-1 { background: #f8f9fa; }
  94. .border-left { border-left: 1px solid #dee2e6; }
  95. .border-right { border-right: 1px solid #dee2e6; }
  96. .border-bottom { border-bottom: 1px solid #dee2e6; }
  97. .border-top { border-top: 1px solid #dee2e6; }
  98. </style>
  99. <div id="layoutSidenav">
  100. <div id="layoutSidenav_nav">
  101. <?php include __DIR__ . '/../../../layouts/sidebar.php'; ?>
  102. </div>
  103. <div id="layoutSidenav_content">
  104. <main>
  105. <div class="container-fluid px-4">
  106. <?php endif; ?>
  107. <div class="container" id="content">
  108. <?php if (!$row): ?>
  109. <div class="alert alert-danger mt-4">Record not found or access denied.</div>
  110. <?php else: ?>
  111. <!-- ── Header ──────────────────────────────────────────────────────────── -->
  112. <div class="row mb-2 mt-3">
  113. <div class="col-md-3">
  114. <img class="img-fluid" src="/client-assets/images/crop-monitor.png" alt="Crop Monitor" style="max-height:55px;">
  115. </div>
  116. </div>
  117. <table class="title w-100 mb-3 small">
  118. <tbody>
  119. <tr>
  120. <td class="text-end fw-bold text-nowrap">DATE:</td>
  121. <td><?= $h($today) ?></td>
  122. <td></td>
  123. <td class="text-end fw-bold text-nowrap">SAMPLE ID:</td>
  124. <td><?= $h($row['site_id']) ?></td>
  125. </tr>
  126. <tr>
  127. <td class="text-end fw-bold text-nowrap">CLIENT:</td>
  128. <td><?= $h($row['client_name']) ?></td>
  129. <td></td>
  130. <td class="text-end fw-bold text-nowrap">DATE SAMPLED:</td>
  131. <td><?= $h($row['date_sampled']) ?></td>
  132. </tr>
  133. <tr>
  134. <td class="text-end fw-bold text-nowrap">ADDRESS:</td>
  135. <td><?= $h($row['site_address']) ?></td>
  136. <td></td>
  137. <td class="text-end fw-bold text-nowrap">LAB NUMBER:</td>
  138. <td><?= $h($row['lab_no']) ?></td>
  139. </tr>
  140. <tr>
  141. <td></td>
  142. <td><?= $h($row['state_postcode']) ?></td>
  143. <td></td>
  144. <td class="text-end fw-bold text-nowrap">CROP TYPE:</td>
  145. <td><?= $h($row['crop_type']) ?></td>
  146. </tr>
  147. </tbody>
  148. </table>
  149. <!-- ── Action buttons ──────────────────────────────────────────────────── -->
  150. <?php if (!$printMode): ?>
  151. <div class="d-print-none mb-3 d-flex gap-2 flex-wrap">
  152. <a href="/dashboard/crop-analysis/plant-test-data/plant-report.php?rid=<?= $recordId ?>&rand=<?= urlencode($randId) ?>&cid=<?= $clientId ?>"
  153. class="btn btn-outline-primary btn-sm">
  154. <i class="fas fa-file-alt me-1"></i>View Report
  155. </a>
  156. <a href="/pdf-files/headlessChrome_pdf.php?type=plant-analysis&rid=<?= $recordId ?>&rand=<?= urlencode($randId) ?>&cid=<?= $clientId ?>"
  157. class="btn btn-outline-secondary btn-sm">
  158. <i class="fas fa-file-pdf me-1"></i>PDF — Analysis
  159. </a>
  160. <a href="/pdf-files/headlessChrome_pdf.php?type=plant&rid=<?= $recordId ?>&rand=<?= urlencode($randId) ?>&cid=<?= $clientId ?>"
  161. class="btn btn-success btn-sm">
  162. <i class="fas fa-file-pdf me-1"></i>PDF — Analysis &amp; Report
  163. </a>
  164. </div>
  165. <?php endif; ?>
  166. <div class="row">
  167. <div class="col-md-12 text-center fw-bold h4">Plant Tissue Analysis Results</div>
  168. </div>
  169. <hr class="p-1 m-1">
  170. <table class="chart">
  171. <tbody>
  172. <tr class="chart-header">
  173. <th colspan="3" class="text-center border-left border-right border-top">ELEMENT</th>
  174. <th colspan="3" class="text-center border-right border-top">STATUS</th>
  175. </tr>
  176. <tr class="chart-header-sub">
  177. <th class="text-center border-bottom border-left">Element</th>
  178. <th class="text-center border-bottom">Desired</th>
  179. <th class="text-center border-bottom">Found</th>
  180. <th class="text-center stripe-1">Deficient</th>
  181. <th class="text-center stripe-1">Ideal</th>
  182. <th class="text-center border-right stripe-1">High</th>
  183. </tr>
  184. <tr><td class="border-left" colspan="6"></td></tr>
  185. <!-- Major Elements -->
  186. <tr class="chart-header-sub">
  187. <th colspan="6" class="border-left text-center lightgreen">MAJOR ELEMENTS (%)</th>
  188. </tr>
  189. <?php
  190. $majorElements = [
  191. ['n', 'Nitrogen', '%', 3],
  192. ['p', 'Phosphorus', '%', 3],
  193. ['k', 'Potassium', '%', 3],
  194. ['s', 'Sulfur', '%', 3],
  195. ['mg', 'Magnesium', '%', 3],
  196. ['ca', 'Calcium', '%', 3],
  197. ['na', 'Sodium', '%', 3],
  198. ];
  199. foreach ($majorElements as [$el, $nutrient, $unit, $dp]):
  200. $found = (float)($row[$el] ?? 0);
  201. [$minCol, $maxCol] = $specCols[$el];
  202. $min = (float)($specs[$minCol] ?? 0);
  203. $max = (float)($specs[$maxCol] ?? 0);
  204. $desired = ($min > 0 || $max > 0) ? number_format($min, $dp) . '–' . number_format($max, $dp) : '—';
  205. ?>
  206. <tr>
  207. <td class="border-left"><?= $h($nutrient) ?> (<?= $h($unit) ?>)</td>
  208. <td><?= $h($desired) ?></td>
  209. <td><?= $found > 0 ? number_format($found, $dp) : '—' ?></td>
  210. <?= statusBar($found, $min, $max) ?>
  211. </tr>
  212. <?php endforeach; ?>
  213. <tr><td colspan="6" class="border-left"></td></tr>
  214. <!-- Trace Elements -->
  215. <tr class="chart-header-sub">
  216. <th colspan="6" class="border-left text-center lightred">TRACE ELEMENTS (ppm)</th>
  217. </tr>
  218. <?php
  219. $traceElements = [
  220. ['fe', 'Iron', 'ppm', 1],
  221. ['mn', 'Manganese', 'ppm', 1],
  222. ['zn', 'Zinc', 'ppm', 1],
  223. ['cu', 'Copper', 'ppm', 1],
  224. ['b', 'Boron', 'ppm', 1],
  225. ];
  226. foreach ($traceElements as [$el, $nutrient, $unit, $dp]):
  227. $found = (float)($row[$el] ?? 0);
  228. [$minCol, $maxCol] = $specCols[$el];
  229. $min = (float)($specs[$minCol] ?? 0);
  230. $max = (float)($specs[$maxCol] ?? 0);
  231. $desired = ($min > 0 || $max > 0) ? number_format($min, $dp) . '–' . number_format($max, $dp) : '—';
  232. ?>
  233. <tr>
  234. <td class="border-left"><?= $h($nutrient) ?> (<?= $h($unit) ?>)</td>
  235. <td><?= $h($desired) ?></td>
  236. <td><?= $found > 0 ? number_format($found, $dp) : '—' ?></td>
  237. <?= statusBar($found, $min, $max) ?>
  238. </tr>
  239. <?php endforeach; ?>
  240. <tr><td colspan="6" class="border-left"></td></tr>
  241. <!-- Other Elements -->
  242. <tr class="chart-header-sub">
  243. <th colspan="6" class="border-left text-center lightpurple">OTHER ELEMENTS (ppm)</th>
  244. </tr>
  245. <?php
  246. $otherElements = [
  247. ['m', 'Molybdenum', 'ppm', 2],
  248. ['co', 'Cobalt', 'ppm', 2],
  249. ['se', 'Selenium', 'ppm', 2],
  250. ['cl', 'Chloride', 'ppm', 2],
  251. ['c', 'Carbon', '%', 2],
  252. ];
  253. foreach ($otherElements as [$el, $nutrient, $unit, $dp]):
  254. $found = (float)($row[$el] ?? 0);
  255. [$minCol, $maxCol] = $specCols[$el];
  256. $min = (float)($specs[$minCol] ?? 0);
  257. $max = (float)($specs[$maxCol] ?? 0);
  258. $desired = ($min > 0 || $max > 0) ? number_format($min, $dp) . '–' . number_format($max, $dp) : '—';
  259. ?>
  260. <tr>
  261. <td class="border-left"><?= $h($nutrient) ?> (<?= $h($unit) ?>)</td>
  262. <td><?= $h($desired) ?></td>
  263. <td><?= $found > 0 ? number_format($found, $dp) : '—' ?></td>
  264. <?= statusBar($found, $min, $max) ?>
  265. </tr>
  266. <?php endforeach; ?>
  267. <tr>
  268. <td class="border-bottom border-left"></td>
  269. <td class="border-bottom"></td>
  270. <td class="border-bottom"></td>
  271. <td class="border-bottom"></td>
  272. <td class="border-bottom"></td>
  273. <td class="border-bottom border-right"></td>
  274. </tr>
  275. </tbody>
  276. </table>
  277. <div class="mt-4 small text-muted">
  278. <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>
  279. <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>
  280. <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>
  281. <p class="fst-italic" style="font-size:9px;">Desired ranges indexed from: CSIRO Plant Analysis Handbook 2nd Ed. Hill Laboratories consultants guide. PIRSA Soil and Plant Analysis.</p>
  282. <p class="fst-italic" style="font-size:9px;">Any recommendations provided by Cropmonitor are advice only. We are not paid consultants and accept no responsibility for any of our suggestions.</p>
  283. </div>
  284. <?php endif; ?>
  285. </div><!-- /.container -->
  286. <?php if (!$printMode): ?>
  287. </div><!-- /.container-fluid -->
  288. </main>
  289. <?php include __DIR__ . '/../../../layouts/footer.php'; ?>
  290. </div><!-- /#layoutSidenav_content -->
  291. </div><!-- /#layoutSidenav -->
  292. <?php endif; ?>