soil-analysis-bs.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. <?php
  2. /**
  3. * soil-analysis-bs.php
  4. *
  5. * Displays soil analysis ratios and key calculated values for a soil record.
  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. if ($recordId > 0) {
  20. $stmt = $pdo->prepare('SELECT * FROM soil_records WHERE id = ? AND rand = ? LIMIT 1');
  21. $stmt->execute([$recordId, $randId]);
  22. $row = $stmt->fetch();
  23. }
  24. $h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
  25. /**
  26. * Render a ratio row: element1 / element2, with a simple status bar.
  27. * Returns 3 <td> cells: Deficit | Ideal | High
  28. */
  29. function ratioBar(float $a, float $b, float $idealMin, float $idealMax): string {
  30. if ($b <= 0) return '<td colspan="3" class="text-muted text-center small">N/A</td>';
  31. $ratio = $a / $b;
  32. if ($idealMax <= 0) return '<td colspan="3" class="text-center small">' . number_format($ratio, 2) . ':1</td>';
  33. if ($ratio < $idealMin) {
  34. return '<td class="text-center"><div class="progress"><div class="progress-bar bg-danger" style="width:100%"></div></div></td>'
  35. . '<td></td><td></td>';
  36. } elseif ($ratio > $idealMax) {
  37. return '<td></td><td></td>'
  38. . '<td class="text-center"><div class="progress"><div class="progress-bar bg-warning" style="width:100%"></div></div></td>';
  39. } else {
  40. $range = $idealMax - $idealMin;
  41. $pct = $range > 0 ? min(100, max(0, ($ratio - $idealMin) / $range * 100)) : 50;
  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>Soil Analysis Ratios | 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. .lightblue { background: #cce5ff !important; color: #004085 !important; }
  69. .stripe-1 { background: #f8f9fa; }
  70. .border-left { border-left: 1px solid #dee2e6; }
  71. .border-right { border-right: 1px solid #dee2e6; }
  72. .border-bottom { border-bottom: 1px solid #dee2e6; }
  73. .border-top { border-top: 1px solid #dee2e6; }
  74. </style>
  75. </head>
  76. <body>
  77. <div class="container" id="content">
  78. <?php if (!$row): ?>
  79. <div class="alert alert-danger mt-4">Record not found or access denied.</div>
  80. <?php else: ?>
  81. <div class="row mb-3">
  82. <div class="col-md-3">
  83. <img class="img-fluid" src="/client-assets/images/crop-monitor.png" alt="Crop Monitor">
  84. </div>
  85. </div>
  86. <div class="d-print-none mb-3">
  87. <button class="btn btn-success btn-sm downloadPDF">
  88. <i class="fas fa-download me-1"></i>Download PDF
  89. </button>
  90. </div>
  91. <div class="row">
  92. <div class="col-md-12 text-center fw-bold h4">ANALYSIS RESULTS — RATIOS</div>
  93. </div>
  94. <hr class="p-1 m-1">
  95. <table class="chart">
  96. <tbody>
  97. <tr class="chart-header">
  98. <th colspan="3" class="text-center border-left border-right border-top">ELEMENT</th>
  99. <th colspan="3" class="text-center border-right border-top">STATUS</th>
  100. </tr>
  101. <tr class="chart-header-sub">
  102. <th class="text-center border-bottom border-left">Ratio</th>
  103. <th class="text-center border-bottom">Ideal Range</th>
  104. <th class="text-center border-bottom">Found</th>
  105. <th class="text-center stripe-1">Deficit</th>
  106. <th class="text-center stripe-1">Ideal</th>
  107. <th class="text-center border-right stripe-1">High</th>
  108. </tr>
  109. <tr>
  110. <td class="border-left" colspan="6"></td>
  111. </tr>
  112. <tr class="chart-header-sub">
  113. <th colspan="3" class="border-left text-center lightblue">RATIOS</th>
  114. <th class="text-center border-left stripe-1"></th>
  115. <th class="text-center border-left stripe-1"></th>
  116. <th class="text-center border-left border-right stripe-1"></th>
  117. </tr>
  118. <?php
  119. // Ca:Mg ideal 2:1 – 5:1
  120. $ca = (float)($row['ca_mehlick3'] ?? 0);
  121. $mg = (float)($row['mg_mehlick3'] ?? 0);
  122. $k = (float)($row['k_mehlick3'] ?? 0);
  123. $na = (float)($row['na_mehlick3'] ?? 0);
  124. $p = (float)($row['p_mehlick'] ?? 0);
  125. $s = (float)($row['s_morgan'] ?? 0);
  126. $zn = (float)($row['zn_dtpa'] ?? 0);
  127. $fe = (float)($row['fe_dtpa'] ?? 0);
  128. $mn = (float)($row['mn_dtpa'] ?? 0);
  129. $c = (float)($row['ocarbon'] ?? 0);
  130. $n = (float)($row['NO3_N'] ?? 0);
  131. $ratios = [
  132. ['Ca:Mg Ratio', $ca, $mg, 2.0, 5.0, 'Ca / Mg'],
  133. ['Mg:K Ratio', $mg, $k, 2.0, 6.0, 'Mg / K'],
  134. ['K:Na Ratio', $k, $na, 5.0, 20.0, 'K / Na'],
  135. ['P:S Ratio', $p, $s, 1.0, 4.0, 'P / S'],
  136. ['P:Zn Ratio', $p, $zn, 8.0, 16.0, 'P / Zn'],
  137. ['Fe:Mn Ratio', $fe, $mn, 1.5, 3.5, 'Fe / Mn'],
  138. ['C:N Ratio', $c, $n, 8.0, 15.0, 'C / N'],
  139. ];
  140. foreach ($ratios as [$label, $numerator, $denominator, $idealMin, $idealMax, $formula]):
  141. $ratio = ($denominator > 0) ? $numerator / $denominator : 0;
  142. $idealStr = number_format($idealMin, 1) . ':1 – ' . number_format($idealMax, 1) . ':1';
  143. $foundStr = ($denominator > 0) ? number_format($ratio, 2) . ':1' : '—';
  144. ?>
  145. <tr>
  146. <td class="border-left"><?= $h($label) ?></td>
  147. <td><?= $h($idealStr) ?></td>
  148. <td><?= $h($foundStr) ?></td>
  149. <?= ratioBar($numerator, $denominator, $idealMin, $idealMax) ?>
  150. </tr>
  151. <?php endforeach; ?>
  152. <tr>
  153. <td class="border-left" colspan="6"></td>
  154. </tr>
  155. <tr class="chart-header-sub">
  156. <th colspan="3" class="border-left text-center lightblue">KEY VALUES</th>
  157. <th class="text-center border-left stripe-1"></th>
  158. <th class="text-center border-left stripe-1"></th>
  159. <th class="text-center border-left border-right stripe-1"></th>
  160. </tr>
  161. <?php
  162. $keyValues = [
  163. ['Total Nitrogen (NH3-N)', (float)($row['NH3_N'] ?? 0), '%', 0.15, 0.40],
  164. ['Total Carbon (OC)', (float)($row['ocarbon'] ?? 0), '%', 1.5, 4.0],
  165. ];
  166. foreach ($keyValues as [$label, $found, $unit, $min, $max]):
  167. $pct = ($max > $min && $found > 0) ? min(100, max(0, ($found - $min) / ($max - $min) * 100)) : 0;
  168. $desired = number_format($min, 2) . '–' . number_format($max, 2);
  169. if ($found < $min) {
  170. $bar = '<td class="text-center"><div class="progress"><div class="progress-bar bg-danger" style="width:100%"></div></div></td><td></td><td></td>';
  171. } elseif ($found > $max) {
  172. $bar = '<td></td><td></td><td class="text-center"><div class="progress"><div class="progress-bar bg-warning" style="width:100%"></div></div></td>';
  173. } else {
  174. $bar = '<td></td><td class="text-center"><div class="progress"><div class="progress-bar bg-success" style="width:' . round($pct) . '%"></div></div></td><td></td>';
  175. }
  176. ?>
  177. <tr>
  178. <td class="border-left"><?= $h($label) ?> (<?= $h($unit) ?>)</td>
  179. <td><?= $h($desired) ?></td>
  180. <td><?= $found > 0 ? number_format($found, 3) : '—' ?></td>
  181. <?= $bar ?>
  182. </tr>
  183. <?php endforeach; ?>
  184. <tr>
  185. <td class="border-bottom border-left"></td>
  186. <td class="border-bottom"></td>
  187. <td class="border-bottom"></td>
  188. <td class="border-bottom"></td>
  189. <td class="border-bottom"></td>
  190. <td class="border-bottom border-right"></td>
  191. </tr>
  192. </tbody>
  193. </table>
  194. <div class="mt-4 small text-muted">
  195. <p><i class="fa fa-leaf" style="color:green"></i> Ideal ratio ranges are indicative. Consult your agronomist for crop and region-specific guidance.</p>
  196. <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>
  197. </div>
  198. <?php endif; ?>
  199. </div>
  200. <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
  201. <script>
  202. document.querySelector('.downloadPDF') && document.querySelector('.downloadPDF').addEventListener('click', function () {
  203. var opt = {
  204. margin: 3,
  205. filename: 'soil-analysis-ratios.pdf',
  206. image: { type: 'jpeg', quality: 1.0 },
  207. html2canvas: { scale: 2, letterRendering: true, windowWidth: 1024 },
  208. jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' }
  209. };
  210. html2pdf().from(document.getElementById('content')).set(opt).save();
  211. });
  212. </script>
  213. </body>
  214. </html>