coverpage.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. <?php
  2. /**
  3. * dashboard/crop-analysis/coverpage.php
  4. *
  5. * Printable cover page for any analysis report type.
  6. *
  7. * Query params:
  8. * type — soil | plant | water | animal | compost
  9. * rid — record id
  10. * rand — auth token
  11. * cid — client_records.id (optional)
  12. */
  13. require_once __DIR__ . '/../../config/database.php';
  14. require_once __DIR__ . '/../../lib/auth.php';
  15. require_once __DIR__ . '/../../lib/print_auth.php';
  16. if (session_status() === PHP_SESSION_NONE) session_start();
  17. $type = trim($_GET['type'] ?? 'soil');
  18. $recordId = (int) ($_GET['rid'] ?? 0);
  19. $randId = trim( $_GET['rand'] ?? '');
  20. $clientId = (int) ($_GET['cid'] ?? 0);
  21. $printMode = isset($_GET['print']);
  22. $allowedTypes = ['soil', 'plant', 'water', 'animal', 'compost'];
  23. if (!in_array($type, $allowedTypes, true)) {
  24. http_response_code(400);
  25. die('Invalid type');
  26. }
  27. if (!$recordId || $randId === '') {
  28. http_response_code(400);
  29. die('Invalid request parameters');
  30. }
  31. if ($printMode) {
  32. authenticatePrintPage($recordId, $randId);
  33. } else {
  34. requireLogin();
  35. }
  36. // ── Fetch record from the appropriate table ─────────────────────────────────
  37. $tableMap = [
  38. 'soil' => 'soil_records',
  39. 'plant' => 'plant_records',
  40. 'water' => 'water_records',
  41. 'animal' => 'animal_records',
  42. 'compost' => 'soil_records', // compost uses soil_records with analysis_type
  43. ];
  44. try {
  45. $pdo = getDBConnection();
  46. $table = $tableMap[$type];
  47. $stmt = $pdo->prepare("SELECT * FROM {$table} WHERE id = ? AND rand = ?");
  48. $stmt->execute([$recordId, $randId]);
  49. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  50. if (!$row) {
  51. http_response_code(404);
  52. die('Record not found');
  53. }
  54. } catch (PDOException $e) {
  55. error_log('DB error in coverpage.php: ' . $e->getMessage());
  56. die('Database error');
  57. }
  58. $h = fn($v) => htmlspecialchars((string)($v ?? ''), ENT_QUOTES, 'UTF-8');
  59. // ── Common fields (shared column names across all tables) ───────────────────
  60. $clientName = $h($row['client_name'] ?? '');
  61. $siteAddress = $h($row['site_address'] ?? '');
  62. $statePost = $h($row['state_postcode'] ?? '');
  63. $email = $h($row['email'] ?? '');
  64. $labNo = $h($row['lab_no'] ?? '');
  65. $batchNo = $h($row['batch_no'] ?? '');
  66. $sampleId = $h($row['sample_id'] ?? '');
  67. $siteId = $h($row['site_id'] ?? '');
  68. $dateSampled = $h($row['date_sampled'] ?? '');
  69. $analysisType = $h($row['analysis_type'] ?? '');
  70. // Type-specific fields
  71. $subjectLine = '';
  72. if ($type === 'soil' || $type === 'compost') {
  73. $subjectLine = $h($row['crop_type'] ?? $row['soil_type'] ?? '');
  74. } elseif ($type === 'plant') {
  75. $subjectLine = $h($row['crop_type'] ?? '');
  76. } elseif ($type === 'water') {
  77. $subjectLine = $h($row['water_source'] ?? $row['analysis_type'] ?? '');
  78. } elseif ($type === 'animal') {
  79. $subjectLine = $h($row['animal'] ?? $row['analysis_type'] ?? '');
  80. }
  81. // Report type label and accent colour
  82. $typeConfig = [
  83. 'soil' => ['label' => 'Soil Analysis', 'accent' => '#4a7c4e', 'bg' => 'Vineyard-Soil.jpg'],
  84. 'plant' => ['label' => 'Plant Tissue Analysis', 'accent' => '#2e6b3e', 'bg' => 'grass.jpg'],
  85. 'water' => ['label' => 'Water Analysis', 'accent' => '#1a5f8a', 'bg' => 'irrigation-water.jpg'],
  86. 'animal' => ['label' => 'Animal Dietary Balance', 'accent' => '#7a5230', 'bg' => 'grass.jpg'],
  87. 'compost' => ['label' => 'Compost Analysis', 'accent' => '#5a4a1e', 'bg' => 'Vineyard-Soil.jpg'],
  88. ];
  89. $cfg = $typeConfig[$type];
  90. $label = $cfg['label'];
  91. $accent = $cfg['accent'];
  92. $bgImg = '/client-assets/images/' . $cfg['bg'];
  93. $today = date('jS F Y');
  94. ?>
  95. <!doctype html>
  96. <html lang="en">
  97. <head>
  98. <meta charset="UTF-8">
  99. <meta name="viewport" content="width=device-width, initial-scale=1">
  100. <title><?= $label ?><?= $clientName !== '' ? ' — ' . $clientName : '' ?></title>
  101. <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
  102. <style>
  103. * { margin: 0; padding: 0; box-sizing: border-box; }
  104. html, body {
  105. width: 100%;
  106. min-width: 1030px;
  107. height: 100%;
  108. font-family: 'Segoe UI', Arial, sans-serif;
  109. background: #fff;
  110. color: #1a1a1a;
  111. }
  112. /* ── Full-page cover layout ── */
  113. .cover {
  114. position: relative;
  115. width: 100%;
  116. min-width: 1030px;
  117. /* A4 aspect ratio at the 1030px combined viewport: 1030 * (297/210) = 1457px */
  118. height: calc(100vw * 297 / 210);
  119. min-height: 1457px;
  120. display: flex;
  121. flex-direction: column;
  122. }
  123. /* ── Top photo banner ── */
  124. .cover-photo {
  125. flex: 1;
  126. background: url('<?= $bgImg ?>') center center / cover no-repeat;
  127. position: relative;
  128. }
  129. .cover-photo::after {
  130. content: '';
  131. position: absolute;
  132. inset: 0;
  133. background: linear-gradient(
  134. to bottom,
  135. rgba(0,0,0,0.08) 0%,
  136. rgba(0,0,0,0.35) 100%
  137. );
  138. }
  139. /* ── Accent stripe ── */
  140. .accent-stripe {
  141. height: 8px;
  142. background: <?= $accent ?>;
  143. }
  144. /* ── Bottom info panel ── */
  145. .cover-footer {
  146. background: #fff;
  147. padding: 28px 36px 32px;
  148. display: flex;
  149. align-items: flex-end;
  150. justify-content: space-between;
  151. gap: 24px;
  152. min-height: 148mm;
  153. }
  154. /* ── Logo + report type (left) ── */
  155. .cover-left {
  156. display: flex;
  157. flex-direction: column;
  158. justify-content: space-between;
  159. height: 100%;
  160. flex: 1;
  161. }
  162. .cover-logo {
  163. height: auto;
  164. width: 50%;
  165. }
  166. .cover-type-label {
  167. font-size: 11px;
  168. font-weight: 600;
  169. letter-spacing: 0.18em;
  170. text-transform: uppercase;
  171. color: <?= $accent ?>;
  172. margin-bottom: 6px;
  173. }
  174. .cover-report-title {
  175. font-size: 28px;
  176. font-weight: 700;
  177. line-height: 1.15;
  178. color: #1a1a1a;
  179. border-left: 4px solid <?= $accent ?>;
  180. padding-left: 14px;
  181. }
  182. .cover-date {
  183. font-size: 11px;
  184. color: #888;
  185. margin-top: 10px;
  186. padding-left: 18px;
  187. }
  188. /* ── Client details (right) ── */
  189. .cover-client {
  190. text-align: right;
  191. min-width: 200px;
  192. max-width: 240px;
  193. flex-shrink: 0;
  194. }
  195. .cover-client-name {
  196. font-size: 17px;
  197. font-weight: 700;
  198. color: #1a1a1a;
  199. margin-bottom: 10px;
  200. line-height: 1.2;
  201. }
  202. .detail-row {
  203. display: flex;
  204. justify-content: flex-end;
  205. align-items: baseline;
  206. gap: 8px;
  207. margin-bottom: 4px;
  208. font-size: 11px;
  209. }
  210. .detail-label {
  211. color: #999;
  212. font-weight: 600;
  213. letter-spacing: 0.06em;
  214. text-transform: uppercase;
  215. white-space: nowrap;
  216. flex-shrink: 0;
  217. }
  218. .detail-value {
  219. color: #2a2a2a;
  220. font-weight: 500;
  221. }
  222. .detail-divider {
  223. border: none;
  224. border-top: 1px solid #e8e8e8;
  225. margin: 10px 0;
  226. }
  227. @media print {
  228. @page { size: A4 portrait; margin: 0; }
  229. }
  230. </style>
  231. </head>
  232. <body>
  233. <div class="cover">
  234. <!-- ── Photo banner ── -->
  235. <div class="cover-photo"></div>
  236. <!-- ── Accent stripe ── -->
  237. <div class="accent-stripe"></div>
  238. <!-- ── Footer info panel ── -->
  239. <div class="cover-footer">
  240. <!-- Left: logo + report type -->
  241. <div class="cover-left">
  242. <img class="cover-logo" src="/client-assets/images/crop-monitor.png" alt="Crop Monitor">
  243. <div style="margin-top: auto;">
  244. <div class="cover-type-label">Report Type</div>
  245. <div class="cover-report-title"><?= $label ?></div>
  246. <div class="cover-date">Prepared: <?= $today ?></div>
  247. </div>
  248. </div>
  249. <!-- Right: client details -->
  250. <div class="cover-client">
  251. <?php if ($clientName !== ''): ?>
  252. <div class="cover-client-name"><?= $clientName ?></div>
  253. <?php endif; ?>
  254. <?php if ($siteAddress !== '' || $statePost !== ''): ?>
  255. <div class="detail-row">
  256. <span class="detail-label">Site</span>
  257. <span class="detail-value"><?= $siteAddress ?><?= ($siteAddress && $statePost) ? ', ' : '' ?><?= $statePost ?></span>
  258. </div>
  259. <?php endif; ?>
  260. <?php if ($dateSampled !== ''): ?>
  261. <div class="detail-row">
  262. <span class="detail-label">Sampled</span>
  263. <span class="detail-value"><?= $dateSampled ?></span>
  264. </div>
  265. <?php endif; ?>
  266. <?php if ($sampleId !== ''): ?>
  267. <div class="detail-row">
  268. <span class="detail-label">Sample ID</span>
  269. <span class="detail-value"><?= $sampleId ?></span>
  270. </div>
  271. <?php endif; ?>
  272. <?php if ($siteId !== ''): ?>
  273. <div class="detail-row">
  274. <span class="detail-label">Site ID</span>
  275. <span class="detail-value"><?= $siteId ?></span>
  276. </div>
  277. <?php endif; ?>
  278. <?php if ($subjectLine !== ''): ?>
  279. <hr class="detail-divider">
  280. <div class="detail-row">
  281. <span class="detail-label"><?= $type === 'water' ? 'Source' : ($type === 'animal' ? 'Animal' : 'Crop / Type') ?></span>
  282. <span class="detail-value"><?= $subjectLine ?></span>
  283. </div>
  284. <?php endif; ?>
  285. <?php if ($analysisType !== ''): ?>
  286. <div class="detail-row">
  287. <span class="detail-label">Analysis</span>
  288. <span class="detail-value"><?= $analysisType ?></span>
  289. </div>
  290. <?php endif; ?>
  291. <?php if ($labNo !== ''): ?>
  292. <hr class="detail-divider">
  293. <div class="detail-row">
  294. <span class="detail-label">Lab No</span>
  295. <span class="detail-value"><?= $labNo ?></span>
  296. </div>
  297. <?php endif; ?>
  298. <?php if ($batchNo !== ''): ?>
  299. <div class="detail-row">
  300. <span class="detail-label">Batch</span>
  301. <span class="detail-value"><?= $batchNo ?></span>
  302. </div>
  303. <?php endif; ?>
  304. <?php if ($email !== ''): ?>
  305. <div class="detail-row" style="margin-top: 8px;">
  306. <span class="detail-label">Email</span>
  307. <span class="detail-value" style="font-size:10px;"><?= $email ?></span>
  308. </div>
  309. <?php endif; ?>
  310. </div>
  311. </div><!-- /.cover-footer -->
  312. </div><!-- /.cover -->
  313. </body>
  314. </html>