plant-report.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. <?php
  2. /**
  3. * plant-report.php
  4. *
  5. * Displays the plant analysis consultant notes / PDF report.
  6. * Accessed via rid + rand URL params.
  7. * Notes auto-save to the reports table via plant-report-save.php.
  8. */
  9. require_once __DIR__ . '/../../../config/database.php';
  10. require_once __DIR__ . '/../../../lib/auth.php';
  11. if (session_status() === PHP_SESSION_NONE) {
  12. session_start();
  13. }
  14. requireLogin();
  15. $pdo = getDBConnection();
  16. $userId = getCurrentUserId();
  17. $recordId = (int) ($_GET['rid'] ?? 0);
  18. $randId = (float) ($_GET['rand'] ?? 0);
  19. $row = null;
  20. $notes = ['general_details' => '', 'recommended_details' => '', 'foliar_details' => ''];
  21. if ($recordId > 0) {
  22. $stmt = $pdo->prepare('SELECT * FROM plant_records WHERE id = ? AND rand = ? LIMIT 1');
  23. $stmt->execute([$recordId, $randId]);
  24. $row = $stmt->fetch();
  25. }
  26. if ($row) {
  27. $rpt = $pdo->prepare(
  28. 'SELECT comment FROM reports WHERE record_id = ? AND modx_user_id = ? ORDER BY id DESC LIMIT 1'
  29. );
  30. $rpt->execute([$recordId, $userId]);
  31. $saved = $rpt->fetchColumn();
  32. if ($saved) {
  33. $decoded = json_decode($saved, true);
  34. if (is_array($decoded)) {
  35. $notes = array_merge($notes, $decoded);
  36. }
  37. }
  38. }
  39. $h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
  40. ?>
  41. <!doctype html>
  42. <html lang="en">
  43. <head>
  44. <meta charset="UTF-8">
  45. <meta name="viewport" content="width=device-width, initial-scale=1">
  46. <title>Plant Analysis Report | Crop Monitor</title>
  47. <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
  48. <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.css" crossorigin="anonymous" rel="stylesheet">
  49. <link href="/client-assets/css/dashboard-2021.css" rel="stylesheet">
  50. <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" crossorigin="anonymous"></script>
  51. <style>
  52. @media print {
  53. @page { size: A4 portrait; margin: 0.5cm; }
  54. .d-print-none { display: none !important; }
  55. }
  56. .chart-header { background: #343a40; color: white; text-align: center; padding: 6px; }
  57. .chart-header-sub { background: #6c757d; color: white; text-align: center; padding: 4px; }
  58. textarea { width: 100%; min-height: 120px; border: 1px solid #dee2e6; padding: 8px; font-size: 0.9rem; }
  59. .save-status { font-size: 0.75rem; color: #6c757d; }
  60. </style>
  61. </head>
  62. <body>
  63. <div class="container" id="content">
  64. <?php if (!$row): ?>
  65. <div class="alert alert-danger mt-4">Record not found or access denied.</div>
  66. <?php else: ?>
  67. <div class="row mb-3">
  68. <div class="col-md-3">
  69. <img class="img-fluid" src="/client-assets/images/crop-monitor.png" alt="Crop Monitor">
  70. </div>
  71. </div>
  72. <div class="d-print-none mb-3 d-flex align-items-center gap-2">
  73. <button class="btn btn-success btn-sm downloadPDF">
  74. <i class="fas fa-download me-1"></i>Download PDF
  75. </button>
  76. <span class="save-status" id="saveStatus"></span>
  77. </div>
  78. <!-- Client info -->
  79. <table class="table table-sm table-bordered mb-3" style="font-size:0.85rem;">
  80. <tbody>
  81. <tr>
  82. <th class="w-25">Client</th>
  83. <td><?= $h($row['client_name'] ?? '') ?></td>
  84. <th class="w-25">Lab No</th>
  85. <td><?= $h($row['lab_no'] ?? '') ?></td>
  86. </tr>
  87. <tr>
  88. <th>Sample ID</th>
  89. <td><?= $h($row['sample_id'] ?? '') ?></td>
  90. <th>Site ID</th>
  91. <td><?= $h($row['site_id'] ?? '') ?></td>
  92. </tr>
  93. <tr>
  94. <th>Crop</th>
  95. <td><?= $h($row['crop_type'] ?? '') ?></td>
  96. <th>Date Sampled</th>
  97. <td><?= $h($row['date_sampled'] ?? '') ?></td>
  98. </tr>
  99. </tbody>
  100. </table>
  101. <div class="text-center fw-bold h5 mb-3">PLANT ANALYSIS SUMMARY</div>
  102. <form class="report-form">
  103. <!-- General Comment -->
  104. <div class="chart-header mb-1">General Comment</div>
  105. <div class="mb-3">
  106. <textarea id="general_details" name="general_details"><?= $h($notes['general_details']) ?></textarea>
  107. </div>
  108. <!-- Recommended Program -->
  109. <div class="chart-header mb-1">Recommended Remedial Program</div>
  110. <div class="mb-3">
  111. <textarea id="recommended_details" name="recommended_details"><?= $h($notes['recommended_details']) ?></textarea>
  112. </div>
  113. <!-- Foliar Program -->
  114. <div class="chart-header mb-1">Foliar Program</div>
  115. <div class="mb-3">
  116. <textarea id="foliar_details" name="foliar_details"><?= $h($notes['foliar_details']) ?></textarea>
  117. </div>
  118. </form>
  119. <div class="mt-4 small text-muted">
  120. <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>
  121. <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>
  122. <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>
  123. <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>
  124. <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>
  125. </div>
  126. <?php endif; ?>
  127. </div>
  128. <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
  129. <script>
  130. (function () {
  131. var timeoutId;
  132. var form = document.querySelector('.report-form');
  133. if (!form) return;
  134. form.querySelectorAll('textarea').forEach(function (el) {
  135. el.addEventListener('input', function () {
  136. clearTimeout(timeoutId);
  137. timeoutId = setTimeout(saveToDB, 1200);
  138. });
  139. });
  140. function saveToDB() {
  141. var data = new FormData(form);
  142. var params = new URLSearchParams();
  143. data.forEach(function (v, k) { params.append(k, v); });
  144. document.getElementById('saveStatus').textContent = 'Saving…';
  145. fetch('/dashboard/crop-analysis/plant-test-data/plant-report-save.php?rid=<?= (int)$recordId ?>&rand=<?= (float)$randId ?>', {
  146. method: 'POST',
  147. headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  148. body: params.toString()
  149. })
  150. .then(function (r) { return r.json(); })
  151. .then(function (d) {
  152. document.getElementById('saveStatus').textContent = d.success ? ('Saved ' + d.saved) : 'Save failed';
  153. })
  154. .catch(function () {
  155. document.getElementById('saveStatus').textContent = 'Save error';
  156. });
  157. }
  158. })();
  159. document.querySelector('.downloadPDF') && document.querySelector('.downloadPDF').addEventListener('click', function () {
  160. var opt = {
  161. margin: 3,
  162. filename: 'plant-report.pdf',
  163. image: { type: 'jpeg', quality: 1.0 },
  164. html2canvas: { scale: 2, letterRendering: true, windowWidth: 1024 },
  165. jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' }
  166. };
  167. html2pdf().from(document.getElementById('content')).set(opt).save();
  168. });
  169. </script>
  170. </body>
  171. </html>