Benjamin Harris před 2 měsíci
rodič
revize
8ed4058123
41 změnil soubory, kde provedl 4432 přidání a 6388 odebrání
  1. 300 356
      albrecht-soil-analysis.php
  2. 12 58
      client-assets/table/Tablepage.php
  3. 95 0
      controllers/animalTestSubmit.php
  4. 33 0
      controllers/compostTestSubmit.php
  5. 60 0
      controllers/newProductSubmit.php
  6. 83 0
      controllers/plantTestSubmit.php
  7. 71 0
      controllers/waterTestSubmit.php
  8. 210 426
      dashboard/client-settings/index.php
  9. 138 335
      dashboard/client-settings/product-list.php
  10. 192 249
      dashboard/client-settings/soil-recommendations.php
  11. 233 480
      dashboard/crop-analysis/animal-dietary-balance/amdb.php
  12. 109 197
      dashboard/crop-analysis/animal-dietary-balance/animal-dietary-balance.php
  13. 16 135
      dashboard/crop-analysis/animal-dietary-balance/animal-submit.php
  14. 55 157
      dashboard/crop-analysis/compost-test-data/compost-test-data.php
  15. 15 148
      dashboard/crop-analysis/plant-test-data/generating-plant-analysis.php
  16. 3 0
      dashboard/crop-analysis/plant-test-data/index.php
  17. 237 231
      dashboard/crop-analysis/plant-test-data/plant-analysis.php
  18. 88 102
      dashboard/crop-analysis/plant-test-data/plant-rec-update.php
  19. 81 265
      dashboard/crop-analysis/plant-test-data/plant-recommendations.php
  20. 65 0
      dashboard/crop-analysis/plant-test-data/plant-report-save.php
  21. 181 153
      dashboard/crop-analysis/plant-test-data/plant-report.php
  22. 229 0
      dashboard/crop-analysis/plant-test-data/plant-test-data.php
  23. 14 111
      dashboard/crop-analysis/plant-test-data/plant-test-report.php
  24. 226 165
      dashboard/crop-analysis/soil-test-data/soil-analysis-bs.php
  25. 27 120
      dashboard/crop-analysis/soil-test-data/soil-analysis-full-report.php
  26. 13 243
      dashboard/crop-analysis/soil-test-data/soil-recommendations.php
  27. 220 406
      dashboard/crop-analysis/soil-test-data/soil-report-pdf.php
  28. 6 3
      dashboard/crop-analysis/soil-test-data/soil-report.php
  29. 5 17
      dashboard/crop-analysis/soil-test-data/soil-test-data.php
  30. 56 148
      dashboard/crop-analysis/updatecomment.php
  31. 150 202
      dashboard/crop-analysis/uploadsubmit.php
  32. 3 0
      dashboard/crop-analysis/water-test-data/index.php
  33. 259 271
      dashboard/crop-analysis/water-test-data/water-analysis-pdf.php
  34. 229 474
      dashboard/crop-analysis/water-test-data/water-test-data.php
  35. 148 201
      dashboard/crop-analysis/water-test-data/water-uploadsubmit.php
  36. 240 487
      dashboard/inbox.php
  37. 128 150
      dashboard/inbox_email.php
  38. 132 0
      dashboard/pesticide.php
  39. 37 7
      dashboard/planning-calendar.php
  40. 17 80
      login/edit-personal-details.php
  41. 16 11
      login/thanks-for-registering.php

+ 300 - 356
albrecht-soil-analysis.php

@@ -1,409 +1,353 @@
+<?php
+/**
+ * albrecht-soil-analysis.php
+ *
+ * Public marketing/landing page for Albrecht Soil Analysis service.
+ * No authentication required.
+ */
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+$pageTitle   = 'Albrecht Soil Analysis';
+$pageIntro   = 'Crop Monitor uses the Albrecht Method to balance your soil for maximum production.';
+$pageDesc    = 'Comprehensive soil analysis reports for Australian conditions.';
+$siteName    = 'Crop Monitor';
+
+$h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
+
+// Handle newsletter subscription (stub — configure MailChimp API separately)
+$newsletterSuccess = false;
+$newsletterError   = '';
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['newsletter-submit'])) {
+    $nEmail = filter_var(trim($_POST['email'] ?? ''), FILTER_VALIDATE_EMAIL);
+    $nName  = trim($_POST['name'] ?? '');
+    if ($nEmail && $nName) {
+        // TODO: integrate MailChimp API or SMTP
+        $newsletterSuccess = true;
+    } else {
+        $newsletterError = 'Please provide a valid name and email address.';
+    }
+}
+
+// Handle contact form (stub — configure SMTP separately)
+$contactSuccess = false;
+$contactError   = '';
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['enquiry-submit'])) {
+    $cEmail = filter_var(trim($_POST['email'] ?? ''), FILTER_VALIDATE_EMAIL);
+    $cName  = trim($_POST['name'] ?? '');
+    $cText  = trim($_POST['text'] ?? '');
+    if ($cEmail && $cName && $cText) {
+        // TODO: send via PHPMailer/SMTP to enquiries@cropmonitor.info
+        $contactSuccess = true;
+    } else {
+        $contactError = 'Please fill in all fields with a valid email address.';
+    }
+}
+?>
 <!doctype html>
 <html lang="en">
-    <head>
-        <title>[[*longtitle]] | [[++site_name]]</title>
-        <base href="[[!++site_url]]" >
-        <meta charset="[[++modx_charset]]" >
-        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" >
-        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
-        <meta name="keywords" content="[[*introtext]]" >
-        <meta name="description" content="[[*description]]" >
-        
-        <link rel="icon" href="client-assets/images/favicon.ico?v=2" type="image/x-icon" >
-        [[$dash-header]]
-        <!-- Custom styles for this template -->
-        <link rel="stylesheet" href="client-assets/css/greyscale.css" >
-    
-    </header>
-    
-    <body>
-        
-        <style>
-            .carousel-item {
-                height: 65vh;
-                min-height: 250px;
-                background: no-repeat center center scroll;
-                -webkit-background-size: cover;
-                -moz-background-size: cover;
-                -o-background-size: cover;
-                background-size: cover;
-            }
-            .nav-link {
-                /* font-size: 2em; */
-                text-align: center;
-                font-weight: bold;
-            }
-        </style>
-        
-        <!-- Navigation -->
-        <nav class="navbar navbar-expand-lg navbar-light bg-light fixed-top" id="mainNav">
-        <div class="container">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <meta name="keywords" content="<?= $h($pageIntro) ?>">
+    <meta name="description" content="<?= $h($pageDesc) ?>">
+    <title><?= $h($pageTitle) ?> | <?= $h($siteName) ?></title>
+    <link rel="icon" href="/client-assets/images/favicon.ico?v=2" type="image/x-icon">
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
+    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.css" crossorigin="anonymous" rel="stylesheet">
+    <link rel="stylesheet" href="/client-assets/css/greyscale.css">
+    <style>
+        .carousel-item {
+            height: 65vh;
+            min-height: 250px;
+            background: no-repeat center center scroll;
+            background-size: cover;
+        }
+        .nav-link { text-align: center; font-weight: bold; }
+        .slideOne   { background-image: linear-gradient(rgba(0,0,0,.25),rgba(0,0,0,.25)), url('/client-assets/FredTemplate/images/g6.jpg'); background-size: cover; }
+        .slideTwo   { background-image: linear-gradient(rgba(0,0,0,.25),rgba(0,0,0,.25)), url('/client-assets/FredTemplate/images/g7.jpg'); background-size: cover; }
+        .slideThree { background-image: linear-gradient(rgba(0,0,0,.25),rgba(0,0,0,.25)), url('/client-assets/FredTemplate/images/g3.jpg'); background-size: cover; }
+    </style>
+</head>
+<body>
 
-            <a class="navbar-brand js-scroll-trigger" href="#">
-                <img src="client-assets/images/favicon.ico" width="30" height="30" class="d-inline-block align-top" alt="">
-                Crop monitor
-            </a>
-            
-            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
-                <span class="navbar-toggler-icon"></span>
-            </button>
-            
-            <div class="collapse navbar-collapse" id="navbarResponsive">
-                
-                <form class="form-inline mx-auto justify-content-center d-none d-md-block">
-                    <button class="btn btn-sm btn-outline-success" type="button">Try a [[*longtitle]] Free</button>
-                </form>
-                
-                <ul class="navbar-nav ml-auto">
-                    <li class="nav-item">
-                        <a class="nav-link js-scroll-trigger" href="[[~18]]">About</a>
-                    </li>
-                    <li class="nav-item">
-                        <a class="nav-link js-scroll-trigger" href="[[~71]]">Blog</a>
-                    </li>
-                    <li class="nav-item">
-                        <a class="nav-link js-scroll-trigger" href="[[~17]]">Contact Us</a>
-                    </li>
-                    <li class="nav-item">
-                        <a class="nav-link js-scroll-trigger" href="[[~4]]">Login</a>
-                    </li>
-                    <div class="row">
-                        <li class="col pr-0 nav-item"><a class="nav-link text-dark" href="#"><i class="fab fa-facebook-f"></i></a></li>
-                        <li class="col px-0 nav-item"><a class="nav-link text-dark" href="#"><i class="fab fa-twitter"></i></a></li>
-                        <li class="col pl-0 nav-item"><a class="nav-link text-dark" href="#"><i class="fab fa-instagram"></i></a></li>
-                    </div>
-                </ul>
-            </div>
+<!-- Navigation -->
+<nav class="navbar navbar-expand-lg navbar-light bg-light fixed-top" id="mainNav">
+    <div class="container">
+        <a class="navbar-brand js-scroll-trigger" href="#">
+            <img src="/client-assets/images/favicon.ico" width="30" height="30" class="d-inline-block align-top" alt="">
+            Crop Monitor
+        </a>
+
+        <button class="navbar-toggler" type="button"
+                data-bs-toggle="collapse" data-bs-target="#navbarResponsive"
+                aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
+            <span class="navbar-toggler-icon"></span>
+        </button>
+
+        <div class="collapse navbar-collapse" id="navbarResponsive">
+            <form class="d-none d-md-block mx-auto">
+                <button class="btn btn-sm btn-outline-success" type="button">
+                    Try <?= $h($pageTitle) ?> Free
+                </button>
+            </form>
+            <ul class="navbar-nav ms-auto">
+                <li class="nav-item"><a class="nav-link" href="#about">About</a></li>
+                <li class="nav-item"><a class="nav-link" href="#blog">Blog</a></li>
+                <li class="nav-item"><a class="nav-link" href="#contact">Contact Us</a></li>
+                <li class="nav-item"><a class="nav-link" href="/login/login.php">Login</a></li>
+                <li class="nav-item"><a class="nav-link text-dark" href="#"><i class="fab fa-facebook-f"></i></a></li>
+                <li class="nav-item"><a class="nav-link text-dark" href="#"><i class="fab fa-twitter"></i></a></li>
+                <li class="nav-item"><a class="nav-link text-dark" href="#"><i class="fab fa-instagram"></i></a></li>
+            </ul>
         </div>
-        </nav>
+    </div>
+</nav>
 
-        <header>
-          <div id="carouselLandingPage" class="carousel slide" data-ride="carousel">
-            <ol class="carousel-indicators">
-              <li data-target="#carouselLandingPage" data-slide-to="0" class="active"></li>
-              <li data-target="#carouselLandingPage" data-slide-to="1"></li>
-              <li data-target="#carouselLandingPage" data-slide-to="2"></li>
-            </ol>
-            <div class="carousel-inner" role="listbox">
-                
-                [[!Gallery? 
-                    &album=`Albrech Landing Page`
-                    &thumbTpl=`slideshow_landing_page`
-                ]]
-                
-                <style>
-                    .slideOne {
-                        background-image: linear-gradient(rgba(0, 0, 0, 0.25), rgba(0, 0, 0, 0.25) ), url('client-assets/FredTemplate/images/work.png'), url('client-assets/FredTemplate/images/g6.jpg');
-                        background-size: cover, auto 300px;
-                        background-position: center, center right 25%;
-                    }
-                    .slideTwo {
-                        background-image: linear-gradient(rgba(0, 0, 0, 0.25), rgba(0, 0, 0, 0.25) ), url('client-assets/FredTemplate/images/work.png'), url('client-assets/FredTemplate/images/g7.jpg');
-                        background-size: cover, auto 300px;
-                        background-position: center, center right 25%;
-                    }
-                    .slideThree {
-                        background-image: linear-gradient(rgba(0, 0, 0, 0.25), rgba(0, 0, 0, 0.25) ), url('client-assets/FredTemplate/images/work.png'), url('client-assets/FredTemplate/images/g3.jpg');
-                        background-size: cover, auto 300px;
-                        background-position: center, center right 25%;
-                    }
-                </style>
-                
-                <!-- Slide One - Set the background image for this slide in the line below -->
-                <div class="carousel-item active slideOne" style=" ">
-                    <div class="carousel-caption text-left">
-                        <div class="row">
-                            <div class="col-md-9">
-                                <h2 class="text-bold">[[*longtitle]]</h2>
-                                <p class="lead col-12 col-md-6 ">[[*introtext]]</p>
-                                <p class="col-12 col-md-6 font-italic d-none d-md-block">[[*description]]</p>
-                                <a href="#" class="btn btn-outline-success">Get a Free Soil Report now!</a>
-                            </div>
-                            <div class="col-md-3 d-none d-md-block align-middle">
-                                <!-- <img src="client-assets/FredTemplate/images/work.png" class="img-fluid mx-auto" alt="Responsive image"> -->
-                            </div>
+<!-- Hero Carousel -->
+<header>
+    <div id="carouselLandingPage" class="carousel slide" data-bs-ride="carousel">
+        <div class="carousel-indicators">
+            <button type="button" data-bs-target="#carouselLandingPage" data-bs-slide-to="0" class="active" aria-current="true"></button>
+            <button type="button" data-bs-target="#carouselLandingPage" data-bs-slide-to="1"></button>
+            <button type="button" data-bs-target="#carouselLandingPage" data-bs-slide-to="2"></button>
+        </div>
+        <div class="carousel-inner">
+            <div class="carousel-item active slideOne">
+                <div class="carousel-caption text-start">
+                    <div class="row">
+                        <div class="col-md-9">
+                            <h2 class="fw-bold"><?= $h($pageTitle) ?></h2>
+                            <p class="lead col-12 col-md-6"><?= $h($pageIntro) ?></p>
+                            <p class="col-12 col-md-6 fst-italic d-none d-md-block"><?= $h($pageDesc) ?></p>
+                            <a href="#signup" class="btn btn-outline-success">Get a Free Soil Report now!</a>
                         </div>
                     </div>
                 </div>
-              
-                <!-- Slide Two - Set the background image for this slide in the line below -->
-                <div class="carousel-item slideTwo" style="">
-                    <div class="carousel-caption text-left">
-                        <div class="row">
-                            <div class="col-md-8">
-                              <h2 class="text-bold">Second Slide</h2>
-                              <p class="lead col-12 col-md-6 ">This is a description for the second slide.</p>
-                              <p class="col-12 col-md-6 font-italic d-none d-md-block"></p>
-                              <a href="#" class="btn btn-outline-success">Get a Free Soil Report now!</a>
-                            </div>
-                            <div class="col-md-4 d-none d-md-block">
-                                <!-- <img src="client-assets/FredTemplate/images/work.png" class="img-fluid mx-auto align-middle" alt="Responsive image"> -->
-                            </div>
+            </div>
+            <div class="carousel-item slideTwo">
+                <div class="carousel-caption text-start">
+                    <div class="row">
+                        <div class="col-md-8">
+                            <h2 class="fw-bold">Balanced Soil, Better Yields</h2>
+                            <p class="lead col-12 col-md-6">Correct cation balance improves soil chemistry and physical structure.</p>
+                            <a href="#signup" class="btn btn-outline-success">Get a Free Soil Report now!</a>
                         </div>
                     </div>
                 </div>
-              
-              <!-- Slide Three - Set the background image for this slide in the line below -->
-              <div class="carousel-item slideThree" style="">
-                <div class="carousel-caption text-left">
+            </div>
+            <div class="carousel-item slideThree">
+                <div class="carousel-caption text-start">
                     <div class="row">
                         <div class="col-md-8">
-                          <h2 class="text-bold">Third Slide</h2>
-                          <p class="lead col-12 col-md-6 ">This is a description for the third slide.</p>
-                          <p class="col-12 col-md-6 font-italic d-none d-md-block"></p>
-                          <a href="#" class="btn btn-outline-success">Get a Free Soil Report now!</a>
-                        </div>
-                        <div class="col-md-4 d-none d-md-block">
-                            <!-- <img src="client-assets/FredTemplate/images/work.png" class="img-fluid mx-auto align-middle" alt="Responsive image"> -->
+                            <h2 class="fw-bold">Feed the Soil, Feed the Plant</h2>
+                            <p class="lead col-12 col-md-6">Real-time monitoring for Australian conditions.</p>
+                            <a href="#signup" class="btn btn-outline-success">Get a Free Soil Report now!</a>
                         </div>
                     </div>
                 </div>
-              </div>
             </div>
-            
-            <a class="carousel-control-prev" href="#carouselLandingPage" role="button" data-slide="prev">
-                  <span class="carousel-control-prev-icon" aria-hidden="true"></span>
-                  <span class="sr-only">Previous</span>
-                </a>
-            <a class="carousel-control-next" href="#carouselLandingPage" role="button" data-slide="next">
-                  <span class="carousel-control-next-icon" aria-hidden="true"></span>
-                  <span class="sr-only">Next</span>
-                </a>
-          </div>
-        </header>
+        </div>
+        <button class="carousel-control-prev" type="button" data-bs-target="#carouselLandingPage" data-bs-slide="prev">
+            <span class="carousel-control-prev-icon" aria-hidden="true"></span>
+            <span class="visually-hidden">Previous</span>
+        </button>
+        <button class="carousel-control-next" type="button" data-bs-target="#carouselLandingPage" data-bs-slide="next">
+            <span class="carousel-control-next-icon" aria-hidden="true"></span>
+            <span class="visually-hidden">Next</span>
+        </button>
+    </div>
+</header>
 
-        <!-- Page Content --> 
-<section id="about" class="py-5"> 
-  <div class="container"> 
-    <h2 class="text-success">Albrecht Soil Analysis</h2> 
-     
-    <hr>
-    
-    <blockquote class="blockquote text-center"> 
-        <p class="mb-0">The soil is the ‘<i>creative material</i>’ of most of the basic needs of life. Creation starts with a handful of dust.</p> 
-        <footer class="blockquote-footer">Dr. William A. Abrecht. <cite title="Source Title">University of Missouri</cite></footer> 
-    </blockquote> 
-    
-    <hr>
-    
-    <div class="row card-deck"> 
-        <div class="card border-success text-center mb-3"> 
-            <div class="card-body">
-                <h2 class="card-title text-center">Physical</h2> 
-                <i class="fas fa-atom fa-3x"></i> 
-                <p class="card-text">When you correct cation balance, you have addressed soil chemistry, which improves the physical structure of the soil.</p> 
+<!-- About Section -->
+<section id="about" class="py-5">
+    <div class="container">
+        <h2 class="text-success">Albrecht Soil Analysis</h2>
+        <hr>
+        <blockquote class="blockquote text-center">
+            <p class="mb-0">The soil is the '<i>creative material</i>' of most of the basic needs of life. Creation starts with a handful of dust.</p>
+            <footer class="blockquote-footer">Dr. William A. Albrecht. <cite>University of Missouri</cite></footer>
+        </blockquote>
+        <hr>
+        <div class="row row-cols-1 row-cols-md-3 g-4">
+            <div class="col">
+                <div class="card border-success text-center h-100">
+                    <div class="card-body">
+                        <h2 class="card-title">Physical</h2>
+                        <i class="fas fa-atom fa-3x mb-2"></i>
+                        <p class="card-text">When you correct cation balance, you have addressed soil chemistry, which improves the physical structure of the soil.</p>
+                    </div>
+                </div>
             </div>
-        </div> 
-         
-        <div class="card border-success text-center mb-3"> 
-            <div class="card-body">
-                <h2 class="card-title text-center">Chemical</h2> 
-                <i class="fas fa-flask fa-3x"></i> 
-                <p class="card-text">The correction of cation balance and the provision of minimum levels of micronutrients tkatesk care of the chemical part of soil productivity and health.</p>
+            <div class="col">
+                <div class="card border-success text-center h-100">
+                    <div class="card-body">
+                        <h2 class="card-title">Chemical</h2>
+                        <i class="fas fa-flask fa-3x mb-2"></i>
+                        <p class="card-text">The correction of cation balance and the provision of minimum levels of micronutrients takes care of the chemical part of soil productivity and health.</p>
+                    </div>
+                </div>
             </div>
-        </div> 
-         
-        <div class="card border-success text-center mb-3"> 
-            <div class="card-body">
-                <h2 class="card-title text-center">Biological</h2> 
-                <i class="fas fa-bug fa-3x"></i> 
-                <p class="card-text">The essential understanding involves a recognition that the purpose of cation balancing is to stimulate soil biology, and much of the beneficial response relates to firing up this workforce</p> 
+            <div class="col">
+                <div class="card border-success text-center h-100">
+                    <div class="card-body">
+                        <h2 class="card-title">Biological</h2>
+                        <i class="fas fa-bug fa-3x mb-2"></i>
+                        <p class="card-text">The essential understanding involves a recognition that the purpose of cation balancing is to stimulate soil biology, and much of the beneficial response relates to firing up this workforce.</p>
+                    </div>
+                </div>
             </div>
-        </div> 
-    </div> 
-     
-    <hr> 
-
-  </div> 
-</section> 
+        </div>
+        <hr>
+    </div>
+</section>
 
-<!-- Projects Section -->
+<!-- Services Section -->
 <section id="services" class="projects-section bg-light pt-2">
-    <div class="container">
-         [[!getResources? 
-           &parents=`[[*id]]`
-           &level=`0`
-           &includeTVs=`1` 
-           &processTVs=`1`
-           &includeContent=`1`
-           &tplFirst=`projectsSectionFIRST`
-           &tpl=`projectsSectionEVEN`
-           &tplOdd=`projectsSectionODD`
-           &sortby=`FIELD(modResource.id, 95 )`
-           &sortdir=`ASC`
-           &limit=`0`
-         ]]
+    <div class="container py-4">
+        <h3 class="text-center mb-4">Our Services</h3>
+        <div class="row row-cols-1 row-cols-md-3 g-4">
+            <div class="col">
+                <div class="card h-100 border-success">
+                    <div class="card-body">
+                        <h5 class="card-title">Soil Analysis</h5>
+                        <p class="card-text">Comprehensive Albrecht Method soil testing with detailed nutrient balance reports.</p>
+                    </div>
+                </div>
+            </div>
+            <div class="col">
+                <div class="card h-100 border-success">
+                    <div class="card-body">
+                        <h5 class="card-title">Plant Tissue Analysis</h5>
+                        <p class="card-text">Identify nutrient deficiencies in your crops with laboratory-accurate tissue testing.</p>
+                    </div>
+                </div>
+            </div>
+            <div class="col">
+                <div class="card h-100 border-success">
+                    <div class="card-body">
+                        <h5 class="card-title">Water Quality Analysis</h5>
+                        <p class="card-text">Test irrigation and drinking water for optimal farm management.</p>
+                    </div>
+                </div>
+            </div>
+        </div>
     </div>
 </section>
-          
-<section class="bg-dark text-light py-5" id="blog" > 
-    <div class="container"> 
-        <p class="lead">
-            
-        </p> 
-    </div> 
-</section> 
-
-<section class="py-5" id="contact" > 
-    <div class="container"> 
-        <p class="lead">
-            Dr. Albrecht saw a direct link between soil quality and food quality, drawing direct connection between poor quality forage crops, and ill health in livestock.
-        </p> 
-    </div> 
-</section> 
 
-<section class="bg-dark text-light py-5" id="login" > 
-    <div class="container"> 
-        <p class="lead">
-            Feed the soil to feed the plant is another vital concept of the Albrecht Model of soil building.
-        </p> 
-    </div> 
-</section> 
+<section class="bg-dark text-light py-5" id="blog">
+    <div class="container">
+        <p class="lead">Dr. Albrecht saw a direct link between soil quality and food quality, drawing a direct connection between poor quality forage crops and ill health in livestock.</p>
+    </div>
+</section>
 
-<section class="py-5" id="" > 
-    <div class="container"> 
-        <p class="lead">
-            Feed the soil to feed the plant is another vital concept of the Albrecht Model of soil building.
-        </p> 
-    </div> 
+<section class="py-5" id="contact-info">
+    <div class="container">
+        <p class="lead">Feed the soil to feed the plant is a vital concept of the Albrecht Model of soil building.</p>
+    </div>
 </section>
-        
 
-<!-- Signup Section -->
+<!-- Newsletter Signup Section -->
 <section id="signup" class="signup-section">
     <div class="container">
-      <div class="row">
-        <div class="col-md-10 col-lg-8 mx-auto text-center">
-
-          <i class="far fa-paper-plane fa-2x mb-2 text-white"></i>
-          <h2 class="text-white mb-5">Subscribe to receive updates!</h2>
-                [[!FormIt?
-                    &hooks=`MailChimpSubscribe`
-                    &validate=`email:email:required,name:required`
-                    &validationErrorMessage=`true`
-                    &clearFieldsOnSuccess=`1`
-                    &submitVar=`newsletter-submit`
-                    &mailchimpListId=`04e221f3bc`
-                    &mailchimpFields=`name=FNAME,email=EMAIL`
-                    &mailchimpSubscribeField=`newsletter`
-                    &mailchimpSubscribeFieldValue=`1`
-                    &successMessage=`Thankyou for subscribing`
-                ]]
-          <span>[[!+fi.successMessage:notempty=`<h2 class="text-white mb-5">[[!+fi.successMessage]]</h2>`]]
-          [[!+fi.validation_error_message:notempty=`<h2 class="text-white mb-5">[[+errors]] [[!+fi.validation_error_message]]</h2>`]]</span>
-          <form action="[[~[[*id]]]]#signup" method="post" class="form-inline d-flex">
-            <input type="hidden" name="nospam" value="" />
-            <input class="form-control flex-fill mr-0 mr-sm-2 mb-3 mb-sm-0" type="email" name="email" id="email" value="[[!+fi.email]]" placeholder="Email Address" >
-            <input class="form-control flex-fill mr-0 mr-sm-2 mb-3 mb-sm-0" type="text" name="name" id="name" value="[[!+fi.name]]" placeholder="Full Name">
-            <input type="submit" name="newsletter-submit" class="btn btn-success mx-auto" value="Subscribe" />
-          </form>
-          
+        <div class="row">
+            <div class="col-md-10 col-lg-8 mx-auto text-center">
+                <i class="far fa-paper-plane fa-2x mb-2 text-white"></i>
+                <h2 class="text-white mb-5">Subscribe to receive updates!</h2>
+                <?php if ($newsletterSuccess): ?>
+                    <h2 class="text-white mb-5">Thank you for subscribing!</h2>
+                <?php elseif ($newsletterError): ?>
+                    <div class="alert alert-warning"><?= $h($newsletterError) ?></div>
+                <?php endif; ?>
+                <form action="#signup" method="post" class="d-flex flex-wrap gap-2 justify-content-center">
+                    <input type="hidden" name="nospam" value="">
+                    <input class="form-control flex-fill" type="email" name="email" placeholder="Email Address"
+                           value="<?= $h($_POST['email'] ?? '') ?>">
+                    <input class="form-control flex-fill" type="text" name="name" placeholder="Full Name"
+                           value="<?= $h($_POST['name'] ?? '') ?>">
+                    <button type="submit" name="newsletter-submit" class="btn btn-success">Subscribe</button>
+                </form>
+            </div>
         </div>
-      </div>
     </div>
 </section>
 
-
 <!-- Contact Section -->
-<section id="contact"  class="contact-section bg-black">
-    <div class="container">
-        
-        <div class="row">
+<section id="contact" class="contact-section bg-black">
+    <div class="container py-5">
+        <div class="row mb-4">
             <div class="col-md-4 mb-3 mb-md-0">
-              <div class="card py-4 h-100">
-                <div class="card-body text-center">
-                  <i class="fas fa-map-marked-alt text-primary mb-2"></i>
-                  <h4 class="text-uppercase m-0">Address</h4>
-                  <hr class="my-4">
-                  <div class="small text-black-50">34 Coplestone Street,<br> Scottsdale, Tasmania 7260</div>
+                <div class="card py-4 h-100">
+                    <div class="card-body text-center">
+                        <i class="fas fa-map-marked-alt text-primary mb-2"></i>
+                        <h4 class="text-uppercase m-0">Address</h4>
+                        <hr class="my-4">
+                        <div class="small text-muted">34 Coplestone Street,<br>Scottsdale, Tasmania 7260</div>
+                    </div>
                 </div>
-              </div>
             </div>
-    
             <div class="col-md-4 mb-3 mb-md-0">
-              <div class="card py-4 h-100">
-                <div class="card-body text-center">
-                  <i class="fas fa-envelope text-primary mb-2"></i>
-                  <h4 class="text-uppercase m-0">Email</h4>
-                  <hr class="my-4">
-                  <div class="small text-black-50">
-                    <a href="#">enquiry@cropmonitor.info</a>
-                  </div>
+                <div class="card py-4 h-100">
+                    <div class="card-body text-center">
+                        <i class="fas fa-envelope text-primary mb-2"></i>
+                        <h4 class="text-uppercase m-0">Email</h4>
+                        <hr class="my-4">
+                        <div class="small text-muted">
+                            <a href="mailto:enquiry@cropmonitor.info">enquiry@cropmonitor.info</a>
+                        </div>
+                    </div>
                 </div>
-              </div>
             </div>
-    
             <div class="col-md-4 mb-3 mb-md-0">
-              <div class="card py-4 h-100">
-                <div class="card-body text-center">
-                  <i class="fas fa-mobile-alt text-primary mb-2"></i>
-                  <h4 class="text-uppercase m-0">Phone</h4>
-                  <hr class="my-4">
-                  <div class="small text-black-50">0417 728 061</div>
+                <div class="card py-4 h-100">
+                    <div class="card-body text-center">
+                        <i class="fas fa-mobile-alt text-primary mb-2"></i>
+                        <h4 class="text-uppercase m-0">Phone</h4>
+                        <hr class="my-4">
+                        <div class="small text-muted">0417 728 061</div>
+                    </div>
                 </div>
-              </div>
             </div>
         </div>
-    
+
         <div class="row">
-            <div class="col mb-3 mt-3 mb-md-0">
-              <div class="card py-4 h-100">
-                <div class="card-body text-center">
-                  <i class="fas fa-mobile-alt text-primary mb-2"></i>
-                  <h4 class="text-uppercase m-0">Contact Us</h4>
-                  <hr class="my-4">
-                        [[!FormIt?
-                            &hooks=`spam,email`
-                            &submitVar=`enquiry-submit`
-                            &emailTpl=`CMenquiryEmailTpl`
-                            &emailTo=`enquiries@cropmonitor.info`
-                            &successMessage=`Thankyou for contacting us, we will be in touch soon.`
-                            &validationErrorMessage=`true`
-                            &validate=`nospam:blank,
-                                name:required,
-                                email:email:required,
-                                text:required:stripTags`
-                        ]]  
-                    <span>
-                        [[!+fi.successMessage:notempty=`<div class="small text-black-50">[[!+fi.successMessage]]</div>`]]
-                        [[!+fi.validation_error_message:notempty=`<div class="small text-black-50">[[+errors]] [[!+fi.validation_error_message]]</div>`]]
-                    </span>
-                    <form action="[[~[[*id]]]]#contact" method="post" class="form-inline d-flex">
-                        <input type="hidden" name="nospam" value="" />
-                        <input class="form-control form-control-sm flex-fill mr-0 mr-sm-2 mb-3 mb-sm-0" type="email" name="email" id="email" value="[[!+fi.email]]" placeholder="Email Address" >
-                        <input class="form-control form-control-sm flex-fill mr-0 mr-sm-2 mb-3 mb-sm-0" type="text" name="name" id="name" value="[[!+fi.name]]" placeholder="Full Name">
-                        <textarea class="form-control form-control-sm flex-fill mr-0 mr-sm-2 mb-3 mb-sm-0" name="text" id="text" rows="1" value="[[!+fi.text]]" placeholder="Your enquiry is about...." ></textarea>
-                        <input type="submit" name="enquiry-submit" class="btn-sm btn btn-success" value="Submit" />
-                    </form>
+            <div class="col">
+                <div class="card py-4 h-100">
+                    <div class="card-body text-center">
+                        <i class="fas fa-paper-plane text-primary mb-2"></i>
+                        <h4 class="text-uppercase m-0">Contact Us</h4>
+                        <hr class="my-4">
+                        <?php if ($contactSuccess): ?>
+                            <div class="alert alert-success">Thank you for contacting us, we will be in touch soon.</div>
+                        <?php elseif ($contactError): ?>
+                            <div class="alert alert-warning"><?= $h($contactError) ?></div>
+                        <?php endif; ?>
+                        <form action="#contact" method="post" class="d-flex flex-wrap gap-2 justify-content-center">
+                            <input type="hidden" name="nospam" value="">
+                            <input class="form-control form-control-sm flex-fill" type="email" name="email"
+                                   placeholder="Email Address" value="<?= $h($_POST['email'] ?? '') ?>">
+                            <input class="form-control form-control-sm flex-fill" type="text" name="name"
+                                   placeholder="Full Name" value="<?= $h($_POST['name'] ?? '') ?>">
+                            <textarea class="form-control form-control-sm flex-fill" name="text" rows="1"
+                                      placeholder="Your enquiry is about..."><?= $h($_POST['text'] ?? '') ?></textarea>
+                            <button type="submit" name="enquiry-submit" class="btn btn-sm btn-success">Submit</button>
+                        </form>
+                    </div>
                 </div>
-              </div>
             </div>
         </div>
-    
-        <!--
-        <div class="social d-flex justify-content-center">
-            <a href="#" class="mx-2">
-              <i class="fab fa-youtube"></i>
-            </a>
-            <a href="#" class="mx-2">
-              <i class="fab fa-facebook-f"></i>
-            </a>
-            <a href="#" class="mx-2">
-              <i class="fab fa-linkedin"></i>
-            </a>
-        </div>
-        -->
     </div>
-</section>        
-        <!-- Footer -->
-        <footer class="footer bg-dark py-2 text-center text-white-50">
-            <div class="container">
-              [[SimpleCopyright? &startYear=`2005`]]. All Rights Reserved<a href="[[~1]]"></a>
-            </div>
-        </footer>
-        
-    </body>
+</section>
 
-    [[$dash-footer]]
+<!-- Footer -->
+<footer class="footer bg-dark py-2 text-center text-white-50">
+    <div class="container">
+        &copy; <?= date('Y') ?> Crop Monitor. All Rights Reserved.
+    </div>
+</footer>
 
-</html>
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
+</body>
+</html>

+ 12 - 58
client-assets/table/Tablepage.php

@@ -1,60 +1,14 @@
 <?php
+/**
+ * Tablepage.php — DISABLED
+ *
+ * This was a raw database table-browser dev tool using hardcoded root credentials.
+ * It has been disabled as it exposed all database tables without authentication.
+ *
+ * The authorised DataTable AJAX endpoint is /client-assets/table/gettable.php
+ * which uses PDO, session authentication, and a table whitelist.
+ */
 
-    //Connect to mysql server
-    $link = mysqli_connect("localhost", "root", "R3M0T31") or die(mysqli_error());
-    if(!$link) {
-           die('Failed to connect to server: ' . mysqli_error());
-    }
-    //Select database
-    $db = mysqli_select_db($link, "cropmonitor");
-    if(!$db) {
-           die("Unable to select database");
-    }
-    $query = "SHOW TABLES;";
-    $result_tables = mysqli_query($link, $query);
-?>
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-    <link rel="shortcut icon" href="https://raw.githubusercontent.com/josephworks/Table-Viewer/master/assets/images/favicon.ico" >
-    <title>Table Viewer</title>
-    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
-    <script type="text/javascript" src="jquery.paginate.js"></script>
-    <script type="text/javascript" src="tableupdater.js"></script>
-    <link rel="stylesheet" type="text/css" href="style.css" media="screen">
-    <link rel="stylesheet" type="text/css" href="chosen.css" media="screen">
-    <link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,400italic&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
-</head>
-<body>
-<div id=wrapper>
-    <h1>Databases</h1>
-    <div id="formcontroles">
-		<div class="tablecontrols">
-			<select data-placeholder="Select table…" class="chzn-select" id="select-table" onchange="changetable()">
-				<option value=""></option> 
-        		<?php
-        			while ($row = mysqli_fetch_array($result_tables)){
-            			echo "<option>".$row[0]."</option>";
-        			}
-        		?>
-			</select>
-        </div>
-        <div class="tablecontrols">
-        	<select id="num-rows" onchange="changetable()" class="chzn-select" tabindex="2">
-        		<option>10 Rows</option>
-        		<option>20 Rows</option>
-        		<option>30 Rows</option>
-        		<option>50 Rows</option>
-        		<option>100 Rows</option>
-        	</select>
-        </div>
-	</div>
-    <div id="table-show"></div>
-    <div id="pagination"></div>
-</div>
-<script src="chosen.jquery.min.js" type="text/javascript"></script>
-<script type="text/javascript"> $(".chzn-select").chosen(); $(".chzn-select-deselect").chosen({allow_single_deselect:true}); </script>
-</body>
-</html>
+http_response_code(410);
+echo '<p>This tool has been disabled. Access the application via the dashboard.</p>';
+exit;

+ 95 - 0
controllers/animalTestSubmit.php

@@ -0,0 +1,95 @@
+<?php
+/**
+ * controllers/animalTestSubmit.php
+ *
+ * Handles POST submission of animal dietary balance test data.
+ * Replaces dashboard/crop-analysis/animal-dietary-balance/animal-submit.php
+ */
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+require_once __DIR__ . '/../config/database.php';
+require_once __DIR__ . '/../lib/auth.php';
+require_once __DIR__ . '/../lib/csrf.php';
+
+requireLogin();
+
+if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+    header('Location: /dashboard/crop-analysis/animal-dietary-balance/');
+    exit;
+}
+
+if (!verifyCsrfToken($_POST['csrf_token'] ?? '')) {
+    http_response_code(403);
+    exit('Invalid security token.');
+}
+
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+$date   = date('Y-m-d');
+$rand   = mt_rand(10000, 99999);
+
+$str = fn($key) => trim($_POST[$key] ?? '');
+$num = fn($key) => is_numeric($_POST[$key] ?? '') ? (float) $_POST[$key] : null;
+
+// Collect POST fields
+$email          = $str('email');
+$clientName     = $str('name');
+$siteAddress    = $str('site_address');
+$statePostcode  = $str('state_postcode');
+$analysisType   = $str('analysis_type');
+$labNo          = $str('lab_no');
+$dateSampled    = $str('date_sampled') ?: null;
+$sampleId       = $str('sample_id');
+$cropType       = $str('crop_type');
+
+$n  = $str('n');  $p  = $str('p');  $k  = $str('k');
+$s  = $str('s');  $mg = $str('mg'); $ca = $str('ca');
+$na = $str('na'); $fe = $str('fe'); $mn = $str('mn');
+$zn = $str('zn'); $cu = $str('cu'); $b  = $str('b');
+$mo = $str('mo'); $co = $str('co'); $se = $str('se');
+$cl = $str('cl');
+
+// meq/100g calculations
+$kNum  = (float) $k;
+$sNum  = (float) $s;
+$caNum = (float) $ca;
+$mgNum = (float) $mg;
+$naNum = (float) $na;
+$clNum = (float) $cl;
+
+$kMeq  = $kNum  / 390;
+$sMeq  = $sNum  / 100000 * 33333 * 1.11;
+$caMeq = $caNum / 200;
+$mgMeq = $mgNum / 120;
+$naMeq = $naNum / 230;
+$clMeq = $clNum / 100000 * 448.34 * 1.11;
+
+$stmt = $pdo->prepare('
+    INSERT INTO animal_records
+        (client_records_id, modx_user_id, date, email, client_name, site_address,
+         state_postcode, analysis_type, lab_no, date_sampled, sample_id, crop_type,
+         n, p, k, s, mg, ca, na, fe, mn, zn, cu, b, mo, co, se, cl,
+         k_meq, s_meq, ca_meq, mg_meq, na_meq, cl_meq, rand)
+    VALUES
+        (0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
+         ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
+         ?, ?, ?, ?, ?, ?, ?)
+');
+
+$stmt->execute([
+    $userId, $date, $email, $clientName, $siteAddress,
+    $statePostcode, $analysisType, $labNo, $dateSampled, $sampleId, $cropType,
+    $n, $p, $k, $s, $mg, $ca, $na, $fe, $mn, $zn, $cu, $b, $mo, $co, $se, $cl,
+    $kMeq, $sMeq, $caMeq, $mgMeq, $naMeq, $clMeq, $rand,
+]);
+
+$insertId = (int) $pdo->lastInsertId();
+
+header('Location: /dashboard/crop-analysis/animal-dietary-balance/?rand=' . $rand
+    . '&cid=' . urlencode($sampleId)
+    . '&rid=' . $insertId
+    . '&stid=' . urlencode($cropType));
+exit;

+ 33 - 0
controllers/compostTestSubmit.php

@@ -0,0 +1,33 @@
+<?php
+/**
+ * controllers/compostTestSubmit.php
+ *
+ * POST handler for compost test entry.
+ * NOTE: Compost form fields are pending migration — this is a stub.
+ */
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+require_once __DIR__ . '/../config/database.php';
+require_once __DIR__ . '/../lib/auth.php';
+require_once __DIR__ . '/../lib/csrf.php';
+
+requireLogin();
+
+if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+    header('Location: /dashboard/crop-analysis/compost-test-data/compost-test-data.php');
+    exit;
+}
+
+if (!verifyCsrfToken($_POST['csrf_token'] ?? '')) {
+    $_SESSION['flash_error'] = 'Invalid CSRF token. Please try again.';
+    header('Location: /dashboard/crop-analysis/compost-test-data/compost-test-data.php');
+    exit;
+}
+
+// Compost form fields not yet defined — redirect with info message
+$_SESSION['flash_error'] = 'Compost test submission is pending full migration. Please contact your administrator.';
+header('Location: /dashboard/crop-analysis/compost-test-data/compost-test-data.php');
+exit;

+ 60 - 0
controllers/newProductSubmit.php

@@ -0,0 +1,60 @@
+<?php
+/**
+ * controllers/newProductSubmit.php
+ *
+ * POST handler: inserts a new product into fertiliser_specifications.
+ */
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+require_once __DIR__ . '/../config/database.php';
+require_once __DIR__ . '/../lib/auth.php';
+require_once __DIR__ . '/../lib/csrf.php';
+
+requireLogin();
+
+if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+    header('Location: /dashboard/client-settings/product-list.php');
+    exit;
+}
+
+if (!verifyCsrfToken($_POST['csrf_token'] ?? '')) {
+    $_SESSION['flash_error'] = 'Invalid CSRF token. Please try again.';
+    header('Location: /dashboard/client-settings/product-list.php');
+    exit;
+}
+
+$name     = trim($_POST['name']     ?? '');
+$chemical = trim($_POST['chemical'] ?? '');
+
+if ($name === '') {
+    $_SESSION['flash_error'] = 'Product name is required.';
+    header('Location: /dashboard/client-settings/product-list.php');
+    exit;
+}
+
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+
+$nutrients    = ['N', 'P', 'K', 'Na', 'Ca', 'Mg', 'B', 'Zn', 'Cu', 'Mn', 'Fe', 'Co', 'Mo'];
+$colList      = 'modx_user_id, name, chemical, ' . implode(', ', array_map(fn($c) => "`$c`", $nutrients));
+$placeholders = implode(', ', array_fill(0, count($nutrients) + 3, '?'));
+
+$values = [$userId, $name, $chemical];
+foreach ($nutrients as $col) {
+    $val = trim((string) ($_POST[$col] ?? '0'));
+    $values[] = is_numeric($val) ? $val : '0';
+}
+
+try {
+    $stmt = $pdo->prepare("INSERT INTO fertiliser_specifications ($colList) VALUES ($placeholders)");
+    $stmt->execute($values);
+    $_SESSION['flash_success'] = 'Product "' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '" added successfully.';
+} catch (\PDOException $e) {
+    $_SESSION['flash_error'] = 'Failed to add product. Please try again.';
+}
+
+header('Location: /dashboard/client-settings/product-list.php');
+exit;

+ 83 - 0
controllers/plantTestSubmit.php

@@ -0,0 +1,83 @@
+<?php
+/**
+ * controllers/plantTestSubmit.php
+ *
+ * Handles POST submission of plant tissue analysis test data.
+ * Replaces dashboard/crop-analysis/plant-test-data/generating-plant-analysis.php
+ */
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+require_once __DIR__ . '/../config/database.php';
+require_once __DIR__ . '/../lib/auth.php';
+require_once __DIR__ . '/../lib/csrf.php';
+
+requireLogin();
+
+if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+    header('Location: /dashboard/crop-analysis/plant-test-data/');
+    exit;
+}
+
+if (!verifyCsrfToken($_POST['csrf_token'] ?? '')) {
+    http_response_code(403);
+    exit('Invalid security token.');
+}
+
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+$date   = date('Y-m-d');
+$rand   = mt_rand(10000, 99999);
+
+$str = fn($key) => trim($_POST[$key] ?? '');
+
+$email         = $str('email');
+$clientName    = $str('name');
+$siteAddress   = $str('site_address');
+$statePostcode = $str('state_postcode');
+$analysisType  = $str('analysis_type');
+$labNo         = $str('lab_no');
+$batchNo       = $str('batch_no');
+$dateSampled   = $str('date_sampled') ?: null;
+$sampleId      = $str('sample_id');
+$siteId        = $str('site_id');
+$cropType      = $str('crop_type');
+
+$n  = $str('n');  $p  = $str('p');  $k  = $str('k');
+$s  = $str('s');  $mg = $str('mg'); $ca = $str('ca');
+$na = $str('na'); $fe = $str('fe'); $mn = $str('mn');
+$zn = $str('zn'); $cu = $str('cu'); $b  = $str('b');
+$m  = $str('m') ?: null;
+$co = $str('co') ?: null;
+$se = $str('se') ?: null;
+$cl = $str('cl') ?: null;
+
+$stmt = $pdo->prepare('
+    INSERT INTO plant_records
+        (client_records_id, modx_user_id, date, email, client_name, site_address,
+         state_postcode, analysis_type, lab_no, batch_no, date_sampled, sample_id,
+         site_id, crop_type, n, p, k, s, mg, ca, na, fe, mn, zn, cu, b,
+         m, co, se, cl, rand)
+    VALUES
+        (0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
+         ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
+         ?, ?, ?, ?, ?)
+');
+
+$stmt->execute([
+    $userId, $date, $email, $clientName, $siteAddress,
+    $statePostcode, $analysisType, $labNo, $batchNo, $dateSampled, $sampleId,
+    $siteId, $cropType,
+    $n, $p, $k, $s, $mg, $ca, $na, $fe, $mn, $zn, $cu, $b,
+    $m, $co, $se, $cl, $rand,
+]);
+
+$insertId = (int) $pdo->lastInsertId();
+
+header('Location: /dashboard/crop-analysis/plant-test-data/plant-analysis.php?rand=' . $rand
+    . '&cid=' . urlencode($sampleId)
+    . '&rid=' . $insertId
+    . '&stid=' . urlencode($cropType));
+exit;

+ 71 - 0
controllers/waterTestSubmit.php

@@ -0,0 +1,71 @@
+<?php
+/**
+ * controllers/waterTestSubmit.php
+ *
+ * POST handler for water test analysis entry.
+ * Inserts a new record into water_records.
+ */
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+require_once __DIR__ . '/../config/database.php';
+require_once __DIR__ . '/../lib/auth.php';
+require_once __DIR__ . '/../lib/csrf.php';
+
+requireLogin();
+
+if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+    header('Location: /dashboard/crop-analysis/water-test-data/water-test-data.php');
+    exit;
+}
+
+if (!verifyCsrfToken($_POST['csrf_token'] ?? '')) {
+    $_SESSION['flash_error'] = 'Invalid CSRF token. Please try again.';
+    header('Location: /dashboard/crop-analysis/water-test-data/water-test-data.php');
+    exit;
+}
+
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+
+$clientId = (int) ($_POST['client_id'] ?? 0);
+if ($clientId > 0) {
+    $stmt = $pdo->prepare('SELECT id FROM client_records WHERE id = ? AND modx_user_id = ?');
+    $stmt->execute([$clientId, $userId]);
+    if (!$stmt->fetch()) {
+        $clientId = 0;
+    }
+}
+
+$rand = mt_rand(10000, 99999);
+
+$fields = [
+    'lab_no', 'batch_no', 'date_sampled', 'sample_id', 'site_id',
+    'ph', 'cond_dsm', 'hco3',
+    'n', 'p', 'k', 's', 'mg', 'ca',
+    'na', 'fe', 'mn', 'zn', 'cu', 'b',
+    'm', 'co', 'se', 'ch',
+];
+
+$colList = 'modx_user_id, client_records_id, rand, ' . implode(', ', array_map(fn($c) => "`$c`", $fields));
+$placeholders = implode(', ', array_fill(0, count($fields) + 3, '?'));
+
+$values = [$userId, $clientId ?: null, $rand];
+foreach ($fields as $field) {
+    $val = trim((string) ($_POST[$field] ?? ''));
+    $values[] = ($val === '') ? null : $val;
+}
+
+try {
+    $stmt = $pdo->prepare("INSERT INTO water_records ($colList) VALUES ($placeholders)");
+    $stmt->execute($values);
+    $newId  = (int) $pdo->lastInsertId();
+    $_SESSION['flash_success'] = 'Water test record saved successfully.';
+    header("Location: /dashboard/inbox.php");
+} catch (\PDOException $e) {
+    $_SESSION['flash_error'] = 'Failed to save record. Please try again.';
+    header('Location: /dashboard/crop-analysis/water-test-data/water-test-data.php');
+}
+exit;

+ 210 - 426
dashboard/client-settings/index.php

@@ -1,432 +1,216 @@
-<!doctype html>
-<html lang="en">
-
-<head>
-	<title>[[*longtitle]] | [[++site_name]]</title>
-	<base href="[[!++site_url]]">
-	<meta charset="[[++modx_charset]]">
-	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
-	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-	<meta name="keywords" content="[[*introtext]]">
-	<meta name="description" content="[[*description]]">
-	<link rel="icon" href="client-assets/images/favicon.ico?v=2" type="image/x-icon"> [[!Profile]]
-	
-	<script type="text/javascript">
-    	window.dataLayer = window.dataLayer || [];
-    
-    	function gtag() {
-    		dataLayer.push(arguments);
-    	}
-    	gtag('js', new Date());
-    	gtag('set', {
-    		'user_id': '[[+modx.user.id]]'
-    	}); // Set the user ID using signed-in user_id.
-    	gtag('config', 'UA-133963301-1');
-	</script>
-	
-	<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" />
-	<link href="client-assets/css/dashboard-2021.css" rel="stylesheet" />
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js" crossorigin="anonymous"></script>
-	<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
-	<script src="client-assets/js/skycons.js"></script>
-	<style>
-    	.btn-append {
-    		color: #495057;
-    		background-color: #e9ecef;
-    		border: 1px solid #ced4da;
-    	}
-    	.footer {
-    		position: absolute;
-    		bottom: 0;
-    		width: 100%;
-    		height: 60px;
-    		line-height: 60px;
-    	}
-	</style>
-</head>
-
-<body class="sb-nav-fixed" id="page-top"> 
-    [[!Personalize?
-        &yesChunk=`navHeaderLogged`
-        &noChunk=`navHeader`
-        &allowedGroups=`basicClients,bacicAdmin,companyClients,companyManagers`
-    ]]
-    
-	<div id="layoutSidenav">
-	    <div id="layoutSidenav_nav">
-    		<!-- Sidebar -->
-    		[[Wayfinder?
-    		    &startId=`2` 
-        		&displayStart=`0` 
-        		&startitemTpl=`startitemTpl` 
-        		&selfClass=`show` 
-        		&level=`2`
-        		&outerTpl=`outer`
-        		    &outerClass=`sb-sidenav accordion sb-sidenav-dark`
-        		&rowTpl=`row`
-        		    &rowClass=`nav-link`
-        		&parentRowTpl=`parentRow`
-        		    &parentClass=`nav-link collapsed`
-        		&innerTpl=`inner`
-        		    &innerClass=`collapse`
-        		&innerRowTpl=`secondInner`
-        		    &innerRowClass=`nav-link`
-    		]]
-        </div>
-		
-		<div id="layoutSidenav_content">
-			<main>
-
-				<div class="container-fluid px-4">
-				    <h1 class="mt-4">[[*pagetitle]]</h1>
-
-					<ol class="breadcrumb mb-4"> [[$dash-breadcrumbs]] </ol>
-					
-					<div class="row">
-					    <div class="container-fluid">
-	
-
-	[[!Profile]]
-	<div class="row">
-		<div class="col-sm-3">
-			<h2 class="text-center">[[+fullname]]</h2>
-		</div>
-		<div class="col-sm-9">
-		    <!-- <a href="/users" class="pull-right">
-		        <img title="profile image" class="img-circle img-responsive" src="">
-		    </a> -->
-		</div>
-	</div>
-
-	
-	<div class="row">
-		
-		<div class="col-sm-3">
-			<!--left col-->
-			<div class="text-center">
-				<img src="[[+photo]]" class="avatar img-circle img-fluid img-thumbnail" alt="avatar"> <!-- https://ssl.gstatic.com/accounts/ui/avatar_2x.png -->
-				    <h6> </h6>
-				<!-- <input type="file" class="text-center center-block file-upload"> -->
-				
-				<span class="error">[[+error.photo]]</span>
-				[[!UpdateProfile? &useExtended=`1` &submitVar=`file-upload` &preHooks=`user_profile_image`]]
-				<form class="form" enctype="multipart/form-data" action="[[~[[*id]]]]" method="post">
-				    [[+login.update_success:if=`[[+login.update_success]]`:is=`1`:then=`[[%login.profile_updated? &namespace=`login` &topic=`updateprofile`]]`]]
-				    <input type="hidden" name="nospam:blank" value="" />
-
-				    <div class="input-group mb-3">
-                      <div class="custom-file">
-                        <input type="file" class="custom-file-input" name="photo" id="photo" value="[[+photo]]" >
-                        <label class="custom-file-label text-left" for="photo">Choose file</label>
-                      </div>
-                      <div class="input-group-append">
-                        <button class="btn btn-outline-secondary" id="file-upload" name="file-upload" type="button">Upload</button>
-                      </div>
+<?php
+require_once __DIR__ . '/../../config/database.php';
+require_once __DIR__ . '/../../lib/auth.php';
+require_once __DIR__ . '/../../lib/csrf.php';
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+requireLogin();
+
+$pageTitle = 'Account Settings';
+$siteName  = 'Crop Monitor';
+
+$pdo     = getDBConnection();
+$userId  = getCurrentUserId();
+$user    = getCurrentUser();
+
+$errors  = [];
+$success = false;
+
+// Handle profile update POST
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['form-save'])) {
+    if (!verifyCsrfToken($_POST['csrf_token'] ?? '')) {
+        $errors[] = 'Invalid CSRF token.';
+    } else {
+        $fields = ['fullname', 'company', 'phone', 'mobilephone', 'email', 'address', 'city', 'state', 'zip', 'country', 'industry', 'role'];
+        $set    = [];
+        $values = [];
+        foreach ($fields as $f) {
+            $set[]    = "`$f` = ?";
+            $values[] = trim($_POST[$f] ?? '');
+        }
+        $values[] = $userId;
+
+        try {
+            $pdo->prepare('UPDATE users SET ' . implode(', ', $set) . ' WHERE id = ?')->execute($values);
+            $success = true;
+            // Refresh session name
+            $_SESSION['user_name'] = trim($_POST['fullname'] ?? '');
+        } catch (\PDOException $e) {
+            $errors[] = 'Failed to update profile.';
+        }
+    }
+}
+
+// Load current profile data from users table
+$profile = [];
+$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ? LIMIT 1');
+$stmt->execute([$userId]);
+$profile = $stmt->fetch() ?: [];
+
+$h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
+
+include __DIR__ . '/../../layouts/header.php';
+include __DIR__ . '/../../layouts/navbar.php';
+?>
+
+<div id="layoutSidenav">
+    <div id="layoutSidenav_nav">
+        <?php include __DIR__ . '/../../layouts/sidebar.php'; ?>
+    </div>
+    <div id="layoutSidenav_content">
+        <main>
+            <div class="container-fluid px-4">
+                <h1 class="mt-4"><?= $h($pageTitle) ?></h1>
+                <ol class="breadcrumb mb-4">
+                    <li class="breadcrumb-item"><a href="/dashboard/dashboard.php">Dashboard</a></li>
+                    <li class="breadcrumb-item active">Account Settings</li>
+                </ol>
+
+                <?php if ($success): ?>
+                    <div class="alert alert-success">Profile updated successfully.</div>
+                <?php endif; ?>
+                <?php foreach ($errors as $err): ?>
+                    <div class="alert alert-danger"><?= $h($err) ?></div>
+                <?php endforeach; ?>
+
+                <div class="row">
+                    <div class="col-sm-3">
+                        <div class="text-center">
+                            <img src="/client-assets/images/avatar-placeholder.png"
+                                 class="img-circle img-fluid img-thumbnail mb-2" alt="avatar"
+                                 style="max-width:150px">
+                        </div>
+                        <div class="alert alert-success mt-2" role="alert">
+                            Your Account level is: <b>FREE</b>
+                            <a href="#" class="alert-link">Upgrade Account</a>
+                        </div>
                     </div>
 
-
-				</form>
-			</div>
-			</hr>
-			<br>
-			
-			<div class="alert alert-success" role="alert">Your Account level is: <b>FREE</b> <a href="#" class="alert-link">Upgrade Account</a></div>
-			<div class="alert" role="alert"></div>
-			
-			<!--
-    			<div class="panel panel-default">
-    				<div class="panel-heading">Website <i class="fa fa-link fa-1x"></i></div>
-    				<div class="panel-body"><a href="#"></a></div>
-    			</div>
-    			
-    			<ul class="list-group">
-    				<li class="list-group-item text-muted">Activity <i class="fa fa-dashboard fa-1x"></i></li>
-    				<li class="list-group-item text-right"><span class="pull-left"><strong>Shares</strong></span></li>
-    				<li class="list-group-item text-right"><span class="pull-left"><strong>Likes</strong></span></li>
-    				<li class="list-group-item text-right"><span class="pull-left"><strong>Posts</strong></span></li>
-    				<li class="list-group-item text-right"><span class="pull-left"><strong>Followers</strong></span></li>
-    			</ul>
-			
-    			<div class="panel panel-default">
-    				<div class="panel-heading">Social Media</div>
-    				<div class="panel-body">
-    					<i class="fa fa-facebook fa-2x"></i> <i class="fa fa-github fa-2x"></i> <i class="fa fa-twitter fa-2x"></i> <i class="fa fa-pinterest fa-2x"></i> <i class="fa fa-google-plus fa-2x"></i>
-    				</div>
-    			</div>
-			-->
-			
-		</div>
-		<!--/col-3-->
-		
-		<div class="col-sm-9">
-
-			<ul class="nav nav-tabs">
-				<li class="nav-item">
-				    <a class="nav-link" data-toggle="tab" href="#contact-details">Your Details</a>
-				</li>
-				<li>
-				    <a class="nav-link" data-toggle="tab" href="#change-password">Change Password</a>
-				</li>
-				<li>
-				    <a class="nav-link" data-toggle="tab" href="#messages">Personal Messages</a>
-				</li>
-			</ul>
-			
-			<div class="tab-content">
-				<div class="tab-pane" id="contact-details">
-				<br>
-					
-					[[!UpdateProfile? 
-					&submitVar=`form-save`
-					&validate=`
-					    fullname:required,
-					    mobilephone:required,
-					    email:required,
-					    address:required,
-					    city:required,
-					    state:required,
-					    zip:required`]]
-					
-					<div class="updprof-error">[[+error.message]]</div>
-                    [[+login.update_success:is=`1`:then=`[[%login.profile_updated? &namespace=`login` &topic=`updateprofile`]]`]]
-                    
-                    
-					<form class="form" action="[[~[[*id]]]]" method="post" id="registrationForm">
-						
-						<div class="form-row">
-							<div class="col-md-6 ">
-								<label for="fullname">Name</label>
-								<input type="text" class="form-control" name="fullname" id="fullname" value="[[+fullname]]" />
-								<small class="error">[[+error.fullname]]</small>
-							</div>
-
-							<div class="col-md-6 ">
-								<label for="phone">Company</label>
-								<input type="text" class="form-control" name="company" id="company" value="[[+fax]]" />
-								<small class="error">[[+error.fax]]</small>
-							</div>
-						</div>
-						
-						<div class="form-row">
-							<div class="col-md-6 ">
-								<label for="phone">Phone</label>
-								<input type="text" class="form-control" name="phone" id="phone" value="[[+phone]]" />
-								<small class="error">[[+error.phone]]</small>
-							</div>
-
-							<div class="col-md-6 ">
-								<label for="mobilephone">Mobile</label>
-								<input type="text" class="form-control" name="mobilephone:required" id="mobilephone" value="[[+mobilephone]]" />
-								<small class="error">[[+error.mobilephone]]</small>
-							</div>
-						</div>
-						
-						<div class="form-row">
-							<div class="col-md-12">
-								<label for="email">Email</label>
-								<input type="email" class="form-control" name="email:required" id="email" value="[[+email]]" />
-								<small class="error">[[+error.email]]</small>
-							</div>
-						</div>
-						
-						<div class="form-row">
-							<div class="col-md-12">
-								<label for="address">Address</label>
-								<input type="text" class="form-control" name="address:required" id="address" value="[[+address]]" />
-								<small class="error">[[+error.address]]</small>
-							</div>
-						</div>
-						
-						<div class="form-row">
-							<div class="col-md-3 ">
-								<label for="city">City</label>
-								<input type="text" class="form-control" name="city:required" id="city" value="[[+city]]" />
-								<small class="error">[[+error.city]]</small>
-							</div>
-
-							<div class="col-md-3 ">
-								<label for="state">State</label>
-								<input type="text" class="form-control" name="state:required" id="state" value="[[+state]]" />
-								<small class="error">[[+error.state]]</small>
-							</div>
-
-							<div class="col-md-3 ">
-								<label for="zip">Postcode</label>
-								<input type="text" class="form-control" name="zip:required" id="zip" value="[[+zip]]" />
-								<small class="error">[[+error.zip]]</small>
-							</div>
-							
-							<div class="col-md-3 ">
-								<label for="state">Country</label>
-								<input type="text" class="form-control" name="country:required" id="country" value="[[+country]]" />
-								<small class="error">[[+error.country]]</small>
-							</div>
-						</div>
-						
-						<hr>
-						<h4>Industry Details</h4>
-						
-						<div class="form-row">
-							<div class="col-md-6 ">
-								<label for="fullname">Industry</label>
-								<select class="form-control" name="industry" name="industry" id="industry" value="[[+website]]">
-									<option selected>[[+website]]</option>
-									<option name="broadacre">Broadacre</option>
-									<option name="viticulture">Viticulture</option>
-									<option name="horticulture">Horticulture</option>
-									<option name="permaculture">Permaculture</option>
-									<option name="dairy">Dairy</option>
-								</select>
-								<small class="error">[[+error.website]]</small>
-							</div>
-
-							<div class="col-md-6 ">
-								<label for="phone">Role</label>
-								<select class="form-control" name="role" name="role" id="role" value="[[+phone]]">
-									<option selected>[[+phone]]</option>
-									<option name="manager">Manager</option>
-									<option name="viticulturist">Viticulturist</option>
-									<option name="horticulturist">Horticulturist</option>
-									<option name="permaculturist">Permaculturist</option>
-									<option name="irrigation-manager">Irrigation Manager</option>
-								</select>
-								<small class="error">[[+error.phone]]</small>
-							</div>
-						</div>
-
-						<div class="form-group">
-							<div class="col-md-12">
-								<br>
-								<button class="btn btn-lg btn-success" id="form-save" name="form-save" type="submit"><i class="glyphicon glyphicon-ok-sign"></i> Save</button>
-								<button class="btn btn-lg btn-warning" type="reset"><i class="glyphicon glyphicon-repeat"></i> Reset</button>
-							</div>
-						</div>
-					
-					</form>
-					<hr>
-				</div>
-				<!--/tab-pane-->
-				
-				
-				<div class="tab-pane" id="change-password">
-					
-					[[!ChangePassword?
-                        &submitVar=`change-password`
-                        &placeholderPrefix=`cp.`
-                        &validateOldPassword=`1`
-                        &validate=`nospam:blank`
-                        &successMessage=`Your password is changed`
-                    ]]
-                    
-                    <form action="[[~[[*id]]]]" method="post">
-                        <input type="hidden" name="nospam" value="" />
-                      <div class="form-group">
-                        <label for="password_old">Old Password <span style="color: red;">[[!+cp.error.password_old]]</span></label>
-                        <input type="password" class="form-control" name="password_old" id="password_old" value="[[+cp.password_old]]" aria-describedby="password_old" placeholder="Old Password">
-                        <small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
-                      </div>
-                      <div class="form-group">
-                        <label for="exampleInputPassword1">New Password <span style="color: red;">[[!+cp.error.password_new]]</span></label>
-                        <input type="password" class="form-control" name="password_new" id="password_new" value="[[+cp.password_new]]" placeholder="New Password">
-                      </div>
-                      <div class="form-group">
-                        <label for="exampleInputPassword1">Confirm New Password <span style="color: red;">[[!+cp.error.password_new_confirm]]</span></label>
-                        <input type="password" class="form-control" name="password_new_confirm" id="password_new_confirm" value="[[+cp.password_new_confirm]]" placeholder="Confirm New Password">
-                      </div>
-                      <input type="submit" class="btn btn-primary" name="change-password" value="Change Password" >
-                    </form>
-					
-				</div>
-				<!--/tab-pane-->
-				
-				<div class="tab-pane" id="messages">
-					
-				</div>
-				
-			</div>
-			<!--/tab-pane-->
-		</div>
-		<!--/tab-content-->
-	</div>
-	<!--/col-9-->
-</div>
-<!--/row-->
-</div>
-
-
-<script>
-	$(document).ready(function() {
-    	var readURL = function(input) {
-    	    if (input.files && input.files[0]) {
-    	        var reader = new FileReader();
-    	
-    	        reader.onload = function (e) {
-    	            $('.avatar').attr('src', e.target.result);
-    	        }
-    	
-    	        reader.readAsDataURL(input.files[0]);
-    	    }
-    	}
-    
-    	$(".file-upload").on('change', function(){
-    	    readURL(this);
-    	});
-	});
-</script>
-					</div>
-
-				</div>
-
-			</main>
-			
-			<footer class="py-4 bg-light mt-auto">
-                <div class="container-fluid px-4">
-                    <div class="d-flex align-items-center justify-content-between small">
-                        <div class="text-muted">[[SimpleCopyright? &startYear=`2003`]]. All Rights Reserved <a href="[[~1]]"></a></div>
-                        <div>
-                            <a href="[[~39~]]">Privacy Policy</a>
-                            &middot;
-                            <a href="[[~39~]]">Terms &amp; Conditions</a>
+                    <div class="col-sm-9">
+                        <ul class="nav nav-tabs" id="settingsTabs">
+                            <li class="nav-item">
+                                <a class="nav-link active" data-bs-toggle="tab" href="#contact-details">Your Details</a>
+                            </li>
+                            <li class="nav-item">
+                                <a class="nav-link" data-bs-toggle="tab" href="#change-password">Change Password</a>
+                            </li>
+                        </ul>
+
+                        <div class="tab-content mt-3">
+                            <!-- Contact Details Tab -->
+                            <div class="tab-pane fade show active" id="contact-details">
+                                <form class="form" action="" method="post" id="registrationForm">
+                                    <input type="hidden" name="csrf_token" value="<?= $h(generateCsrfToken()) ?>">
+                                    <input type="hidden" name="form-save" value="1">
+
+                                    <div class="row mb-3">
+                                        <div class="col-md-6">
+                                            <label class="form-label">Name</label>
+                                            <input type="text" class="form-control" name="fullname"
+                                                   value="<?= $h($profile['fullname'] ?? '') ?>">
+                                        </div>
+                                        <div class="col-md-6">
+                                            <label class="form-label">Company</label>
+                                            <input type="text" class="form-control" name="company"
+                                                   value="<?= $h($profile['company'] ?? '') ?>">
+                                        </div>
+                                    </div>
+
+                                    <div class="row mb-3">
+                                        <div class="col-md-6">
+                                            <label class="form-label">Phone</label>
+                                            <input type="text" class="form-control" name="phone"
+                                                   value="<?= $h($profile['phone'] ?? '') ?>">
+                                        </div>
+                                        <div class="col-md-6">
+                                            <label class="form-label">Mobile</label>
+                                            <input type="text" class="form-control" name="mobilephone"
+                                                   value="<?= $h($profile['mobilephone'] ?? '') ?>">
+                                        </div>
+                                    </div>
+
+                                    <div class="mb-3">
+                                        <label class="form-label">Email</label>
+                                        <input type="email" class="form-control" name="email"
+                                               value="<?= $h($profile['email'] ?? $user['email'] ?? '') ?>">
+                                    </div>
+
+                                    <div class="mb-3">
+                                        <label class="form-label">Address</label>
+                                        <input type="text" class="form-control" name="address"
+                                               value="<?= $h($profile['address'] ?? '') ?>">
+                                    </div>
+
+                                    <div class="row mb-3">
+                                        <div class="col-md-3">
+                                            <label class="form-label">City</label>
+                                            <input type="text" class="form-control" name="city"
+                                                   value="<?= $h($profile['city'] ?? '') ?>">
+                                        </div>
+                                        <div class="col-md-3">
+                                            <label class="form-label">State</label>
+                                            <input type="text" class="form-control" name="state"
+                                                   value="<?= $h($profile['state'] ?? '') ?>">
+                                        </div>
+                                        <div class="col-md-3">
+                                            <label class="form-label">Postcode</label>
+                                            <input type="text" class="form-control" name="zip"
+                                                   value="<?= $h($profile['zip'] ?? '') ?>">
+                                        </div>
+                                        <div class="col-md-3">
+                                            <label class="form-label">Country</label>
+                                            <input type="text" class="form-control" name="country"
+                                                   value="<?= $h($profile['country'] ?? '') ?>">
+                                        </div>
+                                    </div>
+
+                                    <hr>
+                                    <h5>Industry Details</h5>
+
+                                    <div class="row mb-3">
+                                        <div class="col-md-6">
+                                            <label class="form-label">Industry</label>
+                                            <select class="form-select" name="industry">
+                                                <?php foreach (['Broadacre','Viticulture','Horticulture','Permaculture','Dairy'] as $opt): ?>
+                                                <option value="<?= $h($opt) ?>" <?= ($profile['industry'] ?? '') === $opt ? 'selected' : '' ?>>
+                                                    <?= $h($opt) ?>
+                                                </option>
+                                                <?php endforeach; ?>
+                                            </select>
+                                        </div>
+                                        <div class="col-md-6">
+                                            <label class="form-label">Role</label>
+                                            <select class="form-select" name="role">
+                                                <?php foreach (['Manager','Viticulturist','Horticulturist','Permaculturist','Irrigation Manager'] as $opt): ?>
+                                                <option value="<?= $h($opt) ?>" <?= ($profile['role'] ?? '') === $opt ? 'selected' : '' ?>>
+                                                    <?= $h($opt) ?>
+                                                </option>
+                                                <?php endforeach; ?>
+                                            </select>
+                                        </div>
+                                    </div>
+
+                                    <button class="btn btn-success" type="submit">Save</button>
+                                    <button class="btn btn-warning ms-2" type="reset">Reset</button>
+                                </form>
+                            </div>
+
+                            <!-- Change Password Tab -->
+                            <div class="tab-pane fade" id="change-password">
+                                <p class="text-muted">
+                                    To change your password, please use the
+                                    <a href="/login/change-password.php">Change Password</a> page.
+                                </p>
+                            </div>
                         </div>
                     </div>
                 </div>
-            </footer>
-            
-		</div>
+            </div>
+        </main>
 
-	</div>
-
-	<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-	<script>
-    	/*!
-            * Start Bootstrap - SB Admin v7.0.3 (https://startbootstrap.com/template/sb-admin)
-            * Copyright 2013-2021 Start Bootstrap
-            * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-sb-admin/blob/master/LICENSE)
-            */
-            // 
-        // Scripts
-        // 
-        
-        window.addEventListener('DOMContentLoaded', event => {
-        
-            // Toggle the side navigation
-            const sidebarToggle = document.body.querySelector('#sidebarToggle');
-            if (sidebarToggle) {
-                // Uncomment Below to persist sidebar toggle between refreshes
-                // if (localStorage.getItem('sb|sidebar-toggle') === 'true') {
-                //     document.body.classList.toggle('sb-sidenav-toggled');
-                // }
-                sidebarToggle.addEventListener('click', event => {
-                    event.preventDefault();
-                    document.body.classList.toggle('sb-sidenav-toggled');
-                    localStorage.setItem('sb|sidebar-toggle', document.body.classList.contains('sb-sidenav-toggled'));
-                });
-            }
-        
-        });
-
-	</script>
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js" crossorigin="anonymous"></script>
-	<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest" crossorigin="anonymous"></script>
-</body>
-</html>
+        <?php include __DIR__ . '/../../layouts/footer.php'; ?>
+    </div>
+</div>

+ 138 - 335
dashboard/client-settings/product-list.php

@@ -1,357 +1,160 @@
-<!doctype html>
-<html lang="en">
-
-<head>
-	<title>[[*longtitle]] | [[++site_name]]</title>
-	<base href="[[!++site_url]]">
-	<meta charset="[[++modx_charset]]">
-	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
-	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-	<meta name="keywords" content="[[*introtext]]">
-	<meta name="description" content="[[*description]]">
-	<link rel="icon" href="client-assets/images/favicon.ico?v=2" type="image/x-icon"> [[!Profile]]
-	
-	<script type="text/javascript">
-    	window.dataLayer = window.dataLayer || [];
-    
-    	function gtag() {
-    		dataLayer.push(arguments);
-    	}
-    	gtag('js', new Date());
-    	gtag('set', {
-    		'user_id': '[[+modx.user.id]]'
-    	}); // Set the user ID using signed-in user_id.
-    	gtag('config', 'UA-133963301-1');
-	</script>
-	
-	<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" />
-	<link href="client-assets/css/dashboard-2021.css" rel="stylesheet" />
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js" crossorigin="anonymous"></script>
-	<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
-	<script src="client-assets/js/skycons.js"></script>
-	<style>
-    	.btn-append {
-    		color: #495057;
-    		background-color: #e9ecef;
-    		border: 1px solid #ced4da;
-    	}
-    	.footer {
-    		position: absolute;
-    		bottom: 0;
-    		width: 100%;
-    		height: 60px;
-    		line-height: 60px;
-    	}
-	</style>
-</head>
-
-<body class="sb-nav-fixed" id="page-top"> 
-    [[!Personalize?
-        &yesChunk=`navHeaderLogged`
-        &noChunk=`navHeader`
-        &allowedGroups=`basicClients,bacicAdmin,companyClients,companyManagers`
-    ]]
-    
-	<div id="layoutSidenav">
-	    <div id="layoutSidenav_nav">
-    		<!-- Sidebar -->
-    		[[Wayfinder?
-    		    &startId=`2` 
-        		&displayStart=`0` 
-        		&startitemTpl=`startitemTpl` 
-        		&selfClass=`show` 
-        		&level=`2`
-        		&outerTpl=`outer`
-        		    &outerClass=`sb-sidenav accordion sb-sidenav-dark`
-        		&rowTpl=`row`
-        		    &rowClass=`nav-link`
-        		&parentRowTpl=`parentRow`
-        		    &parentClass=`nav-link collapsed`
-        		&innerTpl=`inner`
-        		    &innerClass=`collapse`
-        		&innerRowTpl=`secondInner`
-        		    &innerRowClass=`nav-link`
-    		]]
-        </div>
-		
-		<div id="layoutSidenav_content">
-			<main>
-
-				<div class="container-fluid px-4">
-				    <h1 class="mt-4">[[*pagetitle]]</h1>
-
-					<ol class="breadcrumb mb-4"> [[$dash-breadcrumbs]] </ol>
-					
-					<div class="row">
-					    <div class="grid-form">
-    <!---->
-    <div class="grid-form1">
-        <h3>Company Product Analysis</h3>
-        <p>Products used in Soil Analysis recommendation programs.</p>
-        <div class="tab-content">
-        <!-- Company Product list here -->    
-        
-        <?php
-$result    = null;
-
-$modx_user = $modx->user->get('id');
-?>
-<table class="table table-bordered">
-<tbody>
-    <tr>
-        <th class='col-20'>ID</th>
-        <th class='col-20'>Product</th>
-        <th class='col-20'>Chemical</th>
-        <th class='col-20'>N</th>
-        <th class='col-20'>P</th>
-        <th class='col-20'>K</th>
-        <th class='col-20'>Na</th>
-        <th class='col-20'>Ca</th>
-        <th class='col-20'>Mg</th>
-        <th class='col-20'>B</th>
-        <th class='col-20'>Zn</th>
-        <th class='col-20'>Cu</th>
-        <th class='col-20'>Mn</th>
-        <th class='col-20'>Fe</th>
-        <th class='col-20'>Co</th>
-        <th class='col-20'>Mo</th>
-    </tr>
-
 <?php
-//Database connection
-//$con = mysqli_connect("localhost", "root", "R3M0T31", "cropmonitor");
-$con = mysqli_connect("localhost", "cropmonitor", "brvnCcaEYxlPCS3", "cropmonitor");
+require_once __DIR__ . '/../../config/database.php';
+require_once __DIR__ . '/../../lib/auth.php';
+require_once __DIR__ . '/../../lib/csrf.php';
 
-// Check connection
-if (mysqli_connect_errno()) {
-    echo "Failed to connect to MySQL: " . mysqli_connect_error();
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
 }
 
-// Get results from database 
-$result = mysqli_query($con, "SELECT * FROM `fertiliser_specifications` WHERE `modx_user_id` = " . $modx_user . " ");
-                                 //
+requireLogin();
 
-if ($result === FALSE) {
-    die(mysqli_error($con)); // TODO: better error handling
-    echo "User Profile incorrect";
-} else {
-    while ($row = mysqli_fetch_array($result)) {
-        
-        $id = $row['id'];
-        $modx_user = $row['modx_user_id'];
-?>
+$pageTitle = 'Product List';
+$siteName  = 'Crop Monitor';
 
-        <tr>
-        <td class='left'><?php echo $row['id']; ?> </td>
-        <td class='left'><b><?php echo $row['name']; ?></b></td>
-        <td class='left'><?php echo $row['chemical']; ?></td>
-        <td contenteditable='true' onBlur="updateDatabase(this,'n','<?php echo $id; ?>')" onClick='showEdit(this);' class='left'><?php echo $row['n']; ?></td>
-        <td contenteditable='true' onBlur="updateDatabase(this,'p','<?php echo $id; ?>')" onClick='showEdit(this);' class='left'><?php echo $row['p']; ?></td>
-        <td contenteditable='true' onBlur="updateDatabase(this,'k','<?php echo $id; ?>')" onClick='showEdit(this);' class='left'><?php echo $row['k']; ?></td>
-        <td contenteditable='true' onBlur="updateDatabase(this,'Na','<?php echo $id; ?>')" onClick='showEdit(this);' class='left'><?php echo $row['Na']; ?></td>
-        <td contenteditable='true' onBlur="updateDatabase(this,'Ca','<?php echo $id; ?>')" onClick='showEdit(this);' class='left'><?php echo $row['Ca']; ?></td>
-        <td contenteditable='true' onBlur="updateDatabase(this,'Mg','<?php echo $id; ?>')" onClick='showEdit(this);' class='left'><?php echo $row['Mg']; ?></td>
-        <td contenteditable='true' onBlur="updateDatabase(this,'B','<?php echo $id; ?>')" onClick='showEdit(this);' class='left'><?php echo $row['B']; ?></td>
-        <td contenteditable='true' onBlur="updateDatabase(this,'Zn','<?php echo $id; ?>')" onClick='showEdit(this);' class='left'><?php echo $row['Zn']; ?></td>
-        <td contenteditable='true' onBlur="updateDatabase(this,'Cu','<?php echo $id; ?>')" onClick='showEdit(this);' class='left'><?php echo $row['Cu']; ?></td>
-        <td contenteditable='true' onBlur="updateDatabase(this,'Mn','<?php echo $id; ?>')" onClick='showEdit(this);' class='left'><?php echo $row['Mn']; ?></td>
-        <td contenteditable='true' onBlur="updateDatabase(this,'Fe','<?php echo $id; ?>')" onClick='showEdit(this);' class='left'><?php echo $row['Fe']; ?></td>
-        <td contenteditable='true' onBlur="updateDatabase(this,'Co','<?php echo $id; ?>')" onClick='showEdit(this);' class='left'><?php echo $row['Co']; ?></td>
-        <td contenteditable='true' onBlur="updateDatabase(this,'Mo','<?php echo $id; ?>')" onClick='showEdit(this);' class='left'><?php echo $row['Mo']; ?></td>
-        </tr>
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
 
+$stmt = $pdo->prepare(
+    'SELECT * FROM fertiliser_specifications WHERE modx_user_id = ? ORDER BY name ASC'
+);
+$stmt->execute([$userId]);
+$products = $stmt->fetchAll();
 
-<?php
-    }
-}
+$h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
 
-mysqli_close($con);
+include __DIR__ . '/../../layouts/header.php';
+include __DIR__ . '/../../layouts/navbar.php';
 ?>
 
-    </tbody>
-</table>
-
-<script type="text/javascript">
-    function showEdit(editableObj) {
-        $(editableObj).css("background", "#97e499");
-    }
-    function updateDatabase(editableObj, column, id) {
-        $(editableObj).css("background", "#FDFDFD");
-        $.ajax({
-            url: "[[~57]]",
-            type: "POST",
-            data: 'column=' + column + '&editval=' + editableObj.innerHTML + '&id=' + id,
-            success: function (data) {
-                $(editableObj).css("background", "white");
-            }
-        });
-    }
-</script>
-?>
-            
-        </div>
+<div id="layoutSidenav">
+    <div id="layoutSidenav_nav">
+        <?php include __DIR__ . '/../../layouts/sidebar.php'; ?>
     </div>
-    
-<div class="bs-example2 bs-example-padded-bottom">
-    <button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#myModal">
-    Add Product
-    </button>
-    <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" style="display: none;">
-        <div class="modal-dialog">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
-                    <h2 class="modal-title">Add New Product</h2>
+    <div id="layoutSidenav_content">
+        <main>
+            <div class="container-fluid px-4">
+
+                <h1 class="mt-4"><?= $h($pageTitle) ?></h1>
+                <ol class="breadcrumb mb-4">
+                    <li class="breadcrumb-item"><a href="/dashboard/dashboard.php">Dashboard</a></li>
+                    <li class="breadcrumb-item active">Product List</li>
+                </ol>
+
+                <div class="card mb-4">
+                    <div class="card-header d-flex justify-content-between align-items-center">
+                        <span><i class="fas fa-flask me-1"></i>Company Product Analysis</span>
+                        <button type="button" class="btn btn-success btn-sm"
+                                data-bs-toggle="modal" data-bs-target="#addProductModal">
+                            <i class="fas fa-plus me-1"></i>Add Product
+                        </button>
+                    </div>
+                    <div class="card-body p-0">
+                        <p class="text-muted px-3 pt-3 mb-0">Products used in Soil Analysis recommendation programs.</p>
+                        <div class="table-responsive">
+                            <table class="table table-bordered table-hover mb-0">
+                                <thead class="table-dark">
+                                    <tr>
+                                        <th>ID</th><th>Product</th><th>Chemical</th>
+                                        <th>N</th><th>P</th><th>K</th><th>Na</th><th>Ca</th><th>Mg</th>
+                                        <th>B</th><th>Zn</th><th>Cu</th><th>Mn</th><th>Fe</th><th>Co</th><th>Mo</th>
+                                    </tr>
+                                </thead>
+                                <tbody>
+                                <?php if (empty($products)): ?>
+                                    <tr><td colspan="16" class="text-center text-muted">No products found.</td></tr>
+                                <?php else: ?>
+                                    <?php foreach ($products as $prod):
+                                        $id = (int) $prod['id']; ?>
+                                    <tr>
+                                        <td><?= $id ?></td>
+                                        <td><strong><?= $h($prod['name']) ?></strong></td>
+                                        <td><?= $h($prod['chemical']) ?></td>
+                                        <?php foreach (['n','p','k','Na','Ca','Mg','B','Zn','Cu','Mn','Fe','Co','Mo'] as $col): ?>
+                                        <td contenteditable="true"
+                                            onblur="updateDatabase(this,'<?= $h($col) ?>','<?= $id ?>')"
+                                            onclick="showEdit(this)"><?= $h($prod[$col]) ?></td>
+                                        <?php endforeach; ?>
+                                    </tr>
+                                    <?php endforeach; ?>
+                                <?php endif; ?>
+                                </tbody>
+                            </table>
+                        </div>
+                    </div>
                 </div>
-                    <div class="modal-body">
-                		<form method="post" action="" id="newClientDetails" >
-                		<div class="grid-form">
-                		    <input type="hidden" class="form-control" name="m_user" id="m_user" value="[[+modx.user.id]]" required>
-                		        <input type="hidden" class="form-control" name="modx_user_attributes" id="modx_user_attributes" value="[[+modx.user.id]]" required>
-                		    <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		    <label class="sr-only" for="name">Product:</label>
-                    		        <input type="text" class="form-control" name="name" placeholder="Product Name" id="name" required >
-                                </div>
-                                <div class="form-group">
-                    		    <label class="sr-only" for="chemical">Chemical Symbol:</label>
-                    		        <input type="text" class="form-control" name="chemical" placeholder="Chemical" id="chemical"  >
-                    		    </div>
-                		    </div>
-                		    <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		        <label class="sr-only" for="N">Nitrogen - N</label>
-                    		        <input type="email" class="form-control" name="N" placeholder="Nitrogen" required id="N"  >
-                                </div>
-                                <div class="form-group">
-                    		        <label class="sr-only" for="P">Phosphorus - P</label>
-                    		        <input type="text" class="form-control" name="P" placeholder="Phosphorus" id="P"  >
-                                </div>
-                		    </div>
-                		    <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		        <label class="sr-only" for="k">Postassium - K</label>
-                    		        <input type="text" class="form-control" name="K" placeholder="Postassium" id="K" >
-                                </div>
-                                <div class="form-group">    
-                    		        <label class="sr-only" for="Na">Sodium - Na</label>
-                    		        <input type="text" class="form-control" name="Na" placeholder="Sodium" id="Na" >
-                                </div>
-                		    </div>
-                		    <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		        <label class="sr-only"for="Ca">Calcium - Ca</label>
-                    		        <input type="text" class="form-control" name="Ca" placeholder="Calcium" required id="Ca"  >
-                                </div>
-                                <div class="form-group">                    		        
-                    		        <label class="sr-only" for="Mn">Magnesium - Mg</label>
-                    		        <input type="text" class="form-control" name="Mg" placeholder="Magnesium" required id="Mg"  >
-                    		    </div>
-                		    </div>
-                		    <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		        <label class="sr-only" for="B">Boron - B</label>
-                    		        <input type="text" class="form-control" name="B" placeholder="Boron" id="B" >
-                                </div>
-                                <div class="form-group">    
-                    		        <label class="sr-only" for="Zn">Zinc - Zn</label>
-                    		        <input type="text" class="form-control" name="Zn" placeholder="Zinc" id="Zn" >
-                                </div>
-                		    </div>
-                		    <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		        <label class="sr-only" for="Cu">Copper - Cu</label>
-                    		        <input type="text" class="form-control" name="Cu" placeholder="Copper" id="Cu" >
+
+            </div><!-- /container-fluid -->
+
+        </main>
+
+        <!-- Add Product modal -->
+        <div class="modal fade" id="addProductModal" tabindex="-1"
+             aria-labelledby="addProductModalLabel" aria-hidden="true">
+            <div class="modal-dialog modal-lg">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h5 class="modal-title" id="addProductModalLabel">Add New Product</h5>
+                        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
+                    </div>
+                    <form method="post" action="/controllers/newProductSubmit.php" id="newProductForm">
+                        <div class="modal-body">
+                            <input type="hidden" name="csrf_token"
+                                   value="<?= $h(generateCsrfToken()) ?>">
+
+                            <div class="row mb-3">
+                                <div class="col">
+                                    <label class="form-label">Product Name</label>
+                                    <input type="text" class="form-control" name="name" required>
                                 </div>
-                                <div class="form-group">    
-                    		        <label class="sr-only" for="Mn">Manganese - Mn</label>
-                    		        <input type="text" class="form-control" name="Mn" placeholder="Manganese" id="Mn" >
+                                <div class="col">
+                                    <label class="form-label">Chemical Symbol</label>
+                                    <input type="text" class="form-control" name="chemical">
                                 </div>
                             </div>
-                            <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		        <label class="sr-only" for="Fe">Iron - Fe</label>
-                    		        <input type="text" class="form-control" name="Fe" placeholder="Iron" id="Fe" >
-                                </div>
-                                <div class="form-group">    
-                    		        <label class="sr-only" for="Co">Colbalt - Co</label>
-                    		        <input type="text" class="form-control" name="Co" placeholder="Colbalt" id="Co" >
-                                </div>
-                		    </div>
-                		    <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		        <label class="sr-only" for="M0">Molybdenum - Mo</label>
-                    		        <input type="text" class="form-control" name="Mo" placeholder="Molybdenum" id="Mo" >
+
+                            <div class="row row-cols-2 row-cols-md-4 g-2">
+                                <?php foreach ([
+                                    'N' => 'Nitrogen', 'P' => 'Phosphorus', 'K' => 'Potassium',
+                                    'Na' => 'Sodium', 'Ca' => 'Calcium', 'Mg' => 'Magnesium',
+                                    'B' => 'Boron', 'Zn' => 'Zinc', 'Cu' => 'Copper',
+                                    'Mn' => 'Manganese', 'Fe' => 'Iron', 'Co' => 'Cobalt',
+                                    'Mo' => 'Molybdenum',
+                                ] as $col => $label): ?>
+                                <div class="col">
+                                    <label class="form-label form-label-sm"><?= $h($label) ?> — <?= $h($col) ?></label>
+                                    <input type="number" step="0.01" class="form-control form-control-sm"
+                                           name="<?= $h($col) ?>" value="0">
                                 </div>
+                                <?php endforeach; ?>
                             </div>
-                         </div>
-                        </form>
-                	</div>
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
-                    <button type="button" class="btn btn-primary">Save changes</button>
-                </div>
-            </div><!-- /.modal-content -->
-        </div><!-- /.modal-dialog -->
-    </div>
-</div>
-           
-           
-</div>
-					</div>
-
-				</div>
-
-			</main>
-			
-			<footer class="py-4 bg-light mt-auto">
-                <div class="container-fluid px-4">
-                    <div class="d-flex align-items-center justify-content-between small">
-                        <div class="text-muted">[[SimpleCopyright? &startYear=`2003`]]. All Rights Reserved <a href="[[~1]]"></a></div>
-                        <div>
-                            <a href="[[~39~]]">Privacy Policy</a>
-                            &middot;
-                            <a href="[[~39~]]">Terms &amp; Conditions</a>
                         </div>
-                    </div>
+                        <div class="modal-footer">
+                            <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
+                            <button type="submit" class="btn btn-success">Save Product</button>
+                        </div>
+                    </form>
                 </div>
-            </footer>
-            
-		</div>
-
-	</div>
+            </div>
+        </div>
 
-	<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-	<script>
-    	/*!
-            * Start Bootstrap - SB Admin v7.0.3 (https://startbootstrap.com/template/sb-admin)
-            * Copyright 2013-2021 Start Bootstrap
-            * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-sb-admin/blob/master/LICENSE)
-            */
-            // 
-        // Scripts
-        // 
-        
-        window.addEventListener('DOMContentLoaded', event => {
-        
-            // Toggle the side navigation
-            const sidebarToggle = document.body.querySelector('#sidebarToggle');
-            if (sidebarToggle) {
-                // Uncomment Below to persist sidebar toggle between refreshes
-                // if (localStorage.getItem('sb|sidebar-toggle') === 'true') {
-                //     document.body.classList.toggle('sb-sidenav-toggled');
-                // }
-                sidebarToggle.addEventListener('click', event => {
-                    event.preventDefault();
-                    document.body.classList.toggle('sb-sidenav-toggled');
-                    localStorage.setItem('sb|sidebar-toggle', document.body.classList.contains('sb-sidenav-toggled'));
-                });
-            }
-        
-        });
+<?php include __DIR__ . '/../../layouts/footer.php'; ?>
 
-	</script>
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js" crossorigin="anonymous"></script>
-	<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest" crossorigin="anonymous"></script>
-</body>
-</html>
+<script>
+function showEdit(el) {
+    el.style.background = '#97e499';
+}
+function updateDatabase(el, column, id) {
+    el.style.background = '#FDFDFD';
+    fetch('/dashboard/client-settings/updateproduct.php', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+        body: 'column=' + encodeURIComponent(column)
+            + '&editval=' + encodeURIComponent(el.innerText.trim())
+            + '&id=' + encodeURIComponent(id)
+            + '&csrf_token=' + encodeURIComponent(
+                document.querySelector('input[name="csrf_token"]').value
+              )
+    }).then(() => { el.style.background = 'white'; });
+}
+</script>

+ 192 - 249
dashboard/client-settings/soil-recommendations.php

@@ -1,261 +1,204 @@
-<!doctype html>
-<html lang="en">
+<?php
+require_once __DIR__ . '/../../config/database.php';
+require_once __DIR__ . '/../../lib/auth.php';
+require_once __DIR__ . '/../../lib/csrf.php';
 
-<head>
-	<title>[[*longtitle]] | [[++site_name]]</title>
-	<base href="[[!++site_url]]">
-	<meta charset="[[++modx_charset]]">
-	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
-	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-	<meta name="keywords" content="[[*introtext]]">
-	<meta name="description" content="[[*description]]">
-	<link rel="icon" href="client-assets/images/favicon.ico?v=2" type="image/x-icon"> [[!Profile]]
-	
-	<script type="text/javascript">
-    	window.dataLayer = window.dataLayer || [];
-    
-    	function gtag() {
-    		dataLayer.push(arguments);
-    	}
-    	gtag('js', new Date());
-    	gtag('set', {
-    		'user_id': '[[+modx.user.id]]'
-    	}); // Set the user ID using signed-in user_id.
-    	gtag('config', 'UA-133963301-1');
-	</script>
-	
-	<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" />
-	<link href="client-assets/css/dashboard-2021.css" rel="stylesheet" />
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js" crossorigin="anonymous"></script>
-	<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
-	<script src="client-assets/js/skycons.js"></script>
-	<style>
-    	.btn-append {
-    		color: #495057;
-    		background-color: #e9ecef;
-    		border: 1px solid #ced4da;
-    	}
-    	.footer {
-    		position: absolute;
-    		bottom: 0;
-    		width: 100%;
-    		height: 60px;
-    		line-height: 60px;
-    	}
-	</style>
-</head>
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
 
-<body class="sb-nav-fixed" id="page-top"> 
-    [[!Personalize?
-        &yesChunk=`navHeaderLogged`
-        &noChunk=`navHeader`
-        &allowedGroups=`basicClients,bacicAdmin,companyClients,companyManagers`
-    ]]
-    
-	<div id="layoutSidenav">
-	    <div id="layoutSidenav_nav">
-    		<!-- Sidebar -->
-    		[[Wayfinder?
-    		    &startId=`2` 
-        		&displayStart=`0` 
-        		&startitemTpl=`startitemTpl` 
-        		&selfClass=`show` 
-        		&level=`2`
-        		&outerTpl=`outer`
-        		    &outerClass=`sb-sidenav accordion sb-sidenav-dark`
-        		&rowTpl=`row`
-        		    &rowClass=`nav-link`
-        		&parentRowTpl=`parentRow`
-        		    &parentClass=`nav-link collapsed`
-        		&innerTpl=`inner`
-        		    &innerClass=`collapse`
-        		&innerRowTpl=`secondInner`
-        		    &innerRowClass=`nav-link`
-    		]]
-        </div>
-		
-		<div id="layoutSidenav_content">
-			<main>
+requireLogin();
 
-				<div class="container-fluid px-4">
-				    <h1 class="mt-4">[[*pagetitle]]</h1>
+$pageTitle = 'Soil Recommendations';
+$siteName  = 'Crop Monitor';
 
-					<ol class="breadcrumb mb-4"> [[$dash-breadcrumbs]] </ol>
-					
-					<div class="row">
-					    <div class="grid-form">
-    <!---->
-    <div class="grid-form1">
-        <h3>Company Product Analysis</h3>
-        <p>Products used in Soil Analysis recommendation programs.</p>
-        <div class="tab-content">
-        <!-- Company Product list here -->    
-        
-        [[!companyProductList]]
-            
-        </div>
-    </div>
-    
-<div class="bs-example2 bs-example-padded-bottom">
-    <button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#myModal">
-    Add Product
-    </button>
-    <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" style="display: none;">
-        <div class="modal-dialog">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
-                    <h2 class="modal-title">Add New Product</h2>
-                </div>
-                    <div class="modal-body">
-                		<form method="post" action="" id="newClientDetails" >
-                		<div class="grid-form">
-                		    <input type="hidden" class="form-control" name="m_user" id="m_user" value="[[+modx.user.id]]" required>
-                		        <input type="hidden" class="form-control" name="modx_user_attributes" id="modx_user_attributes" value="[[+modx.user.id]]" required>
-                		    <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		    <label class="sr-only" for="name">Product:</label>
-                    		        <input type="text" class="form-control" name="name" placeholder="Product Name" id="name" required >
-                                </div>
-                                <div class="form-group">
-                    		    <label class="sr-only" for="chemical">Chemical Symbol:</label>
-                    		        <input type="text" class="form-control" name="chemical" placeholder="Chemical" id="chemical"  >
-                    		    </div>
-                		    </div>
-                		    <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		        <label class="sr-only" for="N">Nitrogen - N</label>
-                    		        <input type="email" class="form-control" name="N" placeholder="Nitrogen" required id="N"  >
-                                </div>
-                                <div class="form-group">
-                    		        <label class="sr-only" for="P">Phosphorus - P</label>
-                    		        <input type="text" class="form-control" name="P" placeholder="Phosphorus" id="P"  >
-                                </div>
-                		    </div>
-                		    <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		        <label class="sr-only" for="k">Postassium - K</label>
-                    		        <input type="text" class="form-control" name="K" placeholder="Postassium" id="K" >
-                                </div>
-                                <div class="form-group">    
-                    		        <label class="sr-only" for="Na">Sodium - Na</label>
-                    		        <input type="text" class="form-control" name="Na" placeholder="Sodium" id="Na" >
-                                </div>
-                		    </div>
-                		    <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		        <label class="sr-only"for="Ca">Calcium - Ca</label>
-                    		        <input type="text" class="form-control" name="Ca" placeholder="Calcium" required id="Ca"  >
-                                </div>
-                                <div class="form-group">                    		        
-                    		        <label class="sr-only" for="Mn">Magnesium - Mg</label>
-                    		        <input type="text" class="form-control" name="Mg" placeholder="Magnesium" required id="Mg"  >
-                    		    </div>
-                		    </div>
-                		    <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		        <label class="sr-only" for="B">Boron - B</label>
-                    		        <input type="text" class="form-control" name="B" placeholder="Boron" id="B" >
-                                </div>
-                                <div class="form-group">    
-                    		        <label class="sr-only" for="Zn">Zinc - Zn</label>
-                    		        <input type="text" class="form-control" name="Zn" placeholder="Zinc" id="Zn" >
-                                </div>
-                		    </div>
-                		    <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		        <label class="sr-only" for="Cu">Copper - Cu</label>
-                    		        <input type="text" class="form-control" name="Cu" placeholder="Copper" id="Cu" >
-                                </div>
-                                <div class="form-group">    
-                    		        <label class="sr-only" for="Mn">Manganese - Mn</label>
-                    		        <input type="text" class="form-control" name="Mn" placeholder="Manganese" id="Mn" >
-                                </div>
-                            </div>
-                            <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		        <label class="sr-only" for="Fe">Iron - Fe</label>
-                    		        <input type="text" class="form-control" name="Fe" placeholder="Iron" id="Fe" >
-                                </div>
-                                <div class="form-group">    
-                    		        <label class="sr-only" for="Co">Colbalt - Co</label>
-                    		        <input type="text" class="form-control" name="Co" placeholder="Colbalt" id="Co" >
-                                </div>
-                		    </div>
-                		    <div class="form-horizontal">
-                    		    <div class="form-group">
-                    		        <label class="sr-only" for="M0">Molybdenum - Mo</label>
-                    		        <input type="text" class="form-control" name="Mo" placeholder="Molybdenum" id="Mo" >
-                                </div>
-                            </div>
-                         </div>
-                        </form>
-                	</div>
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
-                    <button type="button" class="btn btn-primary">Save changes</button>
-                </div>
-            </div><!-- /.modal-content -->
-        </div><!-- /.modal-dialog -->
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+
+$errors  = [];
+$success = false;
+
+$h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
+
+// Load fertiliser_specifications for this user
+$stmt = $pdo->prepare('SELECT * FROM fertiliser_specifications WHERE modx_user_id = ? ORDER BY id DESC');
+$stmt->execute([$userId]);
+$products = $stmt->fetchAll();
+
+include __DIR__ . '/../../layouts/header.php';
+include __DIR__ . '/../../layouts/navbar.php';
+?>
+
+<div id="layoutSidenav">
+    <div id="layoutSidenav_nav">
+        <?php include __DIR__ . '/../../layouts/sidebar.php'; ?>
     </div>
-</div>
-           
-           
-</div>
-					</div>
+    <div id="layoutSidenav_content">
+        <main>
+            <div class="container-fluid px-4">
+                <h1 class="mt-4"><?= $h($pageTitle) ?></h1>
+                <ol class="breadcrumb mb-4">
+                    <li class="breadcrumb-item"><a href="/dashboard/dashboard.php">Dashboard</a></li>
+                    <li class="breadcrumb-item active">Soil Recommendations</li>
+                </ol>
+
+                <?php foreach ($errors as $err): ?>
+                    <div class="alert alert-danger"><?= $h($err) ?></div>
+                <?php endforeach; ?>
+                <?php if ($success): ?>
+                    <div class="alert alert-success">Product added successfully.</div>
+                <?php endif; ?>
+
+                <div class="row">
+                    <div class="col-12">
+                        <h5>Soil Analysis</h5>
+                        <p class="text-muted">Variables used in Soil Analysis recommendation programs.</p>
 
-				</div>
+                        <button type="button" class="btn btn-warning mb-3"
+                                data-bs-toggle="modal" data-bs-target="#addProductModal">
+                            Add Soil Recommendation
+                        </button>
 
-			</main>
-			
-			<footer class="py-4 bg-light mt-auto">
-                <div class="container-fluid px-4">
-                    <div class="d-flex align-items-center justify-content-between small">
-                        <div class="text-muted">[[SimpleCopyright? &startYear=`2003`]]. All Rights Reserved <a href="[[~1]]"></a></div>
-                        <div>
-                            <a href="[[~39~]]">Privacy Policy</a>
-                            &middot;
-                            <a href="[[~39~]]">Terms &amp; Conditions</a>
+                        <?php if (empty($products)): ?>
+                            <p class="text-muted">No products found. Add one using the button above.</p>
+                        <?php else: ?>
+                        <div class="table-responsive">
+                            <table class="table table-sm table-striped table-hover table-bordered">
+                                <thead class="table-dark">
+                                    <tr>
+                                        <th>Product</th>
+                                        <th>Chemical</th>
+                                        <th>N</th><th>P</th><th>K</th><th>Na</th>
+                                        <th>Ca</th><th>Mg</th><th>B</th><th>Zn</th>
+                                        <th>Cu</th><th>Mn</th><th>Fe</th><th>Co</th><th>Mo</th>
+                                    </tr>
+                                </thead>
+                                <tbody>
+                                <?php foreach ($products as $prod): ?>
+                                    <tr>
+                                        <td><?= $h($prod['name'] ?? '') ?></td>
+                                        <td><?= $h($prod['chemical'] ?? '') ?></td>
+                                        <td><?= $h($prod['N'] ?? '0') ?></td>
+                                        <td><?= $h($prod['P'] ?? '0') ?></td>
+                                        <td><?= $h($prod['K'] ?? '0') ?></td>
+                                        <td><?= $h($prod['Na'] ?? '0') ?></td>
+                                        <td><?= $h($prod['Ca'] ?? '0') ?></td>
+                                        <td><?= $h($prod['Mg'] ?? '0') ?></td>
+                                        <td><?= $h($prod['B'] ?? '0') ?></td>
+                                        <td><?= $h($prod['Zn'] ?? '0') ?></td>
+                                        <td><?= $h($prod['Cu'] ?? '0') ?></td>
+                                        <td><?= $h($prod['Mn'] ?? '0') ?></td>
+                                        <td><?= $h($prod['Fe'] ?? '0') ?></td>
+                                        <td><?= $h($prod['Co'] ?? '0') ?></td>
+                                        <td><?= $h($prod['Mo'] ?? '0') ?></td>
+                                    </tr>
+                                <?php endforeach; ?>
+                                </tbody>
+                            </table>
                         </div>
+                        <?php endif; ?>
                     </div>
                 </div>
-            </footer>
-            
-		</div>
+            </div>
+        </main>
 
-	</div>
-
-	<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-	<script>
-    	/*!
-            * Start Bootstrap - SB Admin v7.0.3 (https://startbootstrap.com/template/sb-admin)
-            * Copyright 2013-2021 Start Bootstrap
-            * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-sb-admin/blob/master/LICENSE)
-            */
-            // 
-        // Scripts
-        // 
-        
-        window.addEventListener('DOMContentLoaded', event => {
-        
-            // Toggle the side navigation
-            const sidebarToggle = document.body.querySelector('#sidebarToggle');
-            if (sidebarToggle) {
-                // Uncomment Below to persist sidebar toggle between refreshes
-                // if (localStorage.getItem('sb|sidebar-toggle') === 'true') {
-                //     document.body.classList.toggle('sb-sidenav-toggled');
-                // }
-                sidebarToggle.addEventListener('click', event => {
-                    event.preventDefault();
-                    document.body.classList.toggle('sb-sidenav-toggled');
-                    localStorage.setItem('sb|sidebar-toggle', document.body.classList.contains('sb-sidenav-toggled'));
-                });
-            }
-        
-        });
+        <?php include __DIR__ . '/../../layouts/footer.php'; ?>
+    </div>
+</div>
 
-	</script>
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js" crossorigin="anonymous"></script>
-	<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest" crossorigin="anonymous"></script>
-</body>
-</html>
+<!-- Add Product Modal -->
+<div class="modal fade" id="addProductModal" tabindex="-1" aria-labelledby="addProductModalLabel" aria-hidden="true">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="addProductModalLabel">Add New Product</h5>
+                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+            </div>
+            <form method="post" action="/controllers/newProductSubmit.php">
+                <input type="hidden" name="csrf_token" value="<?= $h(generateCsrfToken()) ?>">
+                <div class="modal-body">
+                    <div class="row mb-2">
+                        <div class="col">
+                            <label class="form-label">Product Name</label>
+                            <input type="text" class="form-control form-control-sm" name="name" placeholder="Product Name" required>
+                        </div>
+                        <div class="col">
+                            <label class="form-label">Chemical Symbol</label>
+                            <input type="text" class="form-control form-control-sm" name="chemical" placeholder="Chemical">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col">
+                            <label class="form-label">Nitrogen (N)</label>
+                            <input type="text" class="form-control form-control-sm" name="N" placeholder="0" required>
+                        </div>
+                        <div class="col">
+                            <label class="form-label">Phosphorus (P)</label>
+                            <input type="text" class="form-control form-control-sm" name="P" placeholder="0">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col">
+                            <label class="form-label">Potassium (K)</label>
+                            <input type="text" class="form-control form-control-sm" name="K" placeholder="0">
+                        </div>
+                        <div class="col">
+                            <label class="form-label">Sodium (Na)</label>
+                            <input type="text" class="form-control form-control-sm" name="Na" placeholder="0">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col">
+                            <label class="form-label">Calcium (Ca)</label>
+                            <input type="text" class="form-control form-control-sm" name="Ca" placeholder="0" required>
+                        </div>
+                        <div class="col">
+                            <label class="form-label">Magnesium (Mg)</label>
+                            <input type="text" class="form-control form-control-sm" name="Mg" placeholder="0" required>
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col">
+                            <label class="form-label">Boron (B)</label>
+                            <input type="text" class="form-control form-control-sm" name="B" placeholder="0">
+                        </div>
+                        <div class="col">
+                            <label class="form-label">Zinc (Zn)</label>
+                            <input type="text" class="form-control form-control-sm" name="Zn" placeholder="0">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col">
+                            <label class="form-label">Copper (Cu)</label>
+                            <input type="text" class="form-control form-control-sm" name="Cu" placeholder="0">
+                        </div>
+                        <div class="col">
+                            <label class="form-label">Manganese (Mn)</label>
+                            <input type="text" class="form-control form-control-sm" name="Mn" placeholder="0">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col">
+                            <label class="form-label">Iron (Fe)</label>
+                            <input type="text" class="form-control form-control-sm" name="Fe" placeholder="0">
+                        </div>
+                        <div class="col">
+                            <label class="form-label">Cobalt (Co)</label>
+                            <input type="text" class="form-control form-control-sm" name="Co" placeholder="0">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-6">
+                            <label class="form-label">Molybdenum (Mo)</label>
+                            <input type="text" class="form-control form-control-sm" name="Mo" placeholder="0">
+                        </div>
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
+                    <button type="submit" class="btn btn-primary">Save Product</button>
+                </div>
+            </form>
+        </div>
+    </div>
+</div>

+ 233 - 480
dashboard/crop-analysis/animal-dietary-balance/amdb.php

@@ -1,493 +1,246 @@
-<!doctype html>
-<html lang="en">
-
-<head>
-	<title>[[*longtitle]] | [[++site_name]]</title>
-	<base href="[[!++site_url]]">
-	<meta charset="[[++modx_charset]]">
-	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
-	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-	<meta name="keywords" content="[[*introtext]]">
-	<meta name="description" content="[[*description]]">
-	<link rel="icon" href="client-assets/images/favicon.ico?v=2" type="image/x-icon"> [[!Profile]]
-	
-	<script type="text/javascript">
-    	window.dataLayer = window.dataLayer || [];
-    
-    	function gtag() {
-    		dataLayer.push(arguments);
-    	}
-    	gtag('js', new Date());
-    	gtag('set', {
-    		'user_id': '[[+modx.user.id]]'
-    	}); // Set the user ID using signed-in user_id.
-    	gtag('config', 'UA-133963301-1');
-	</script>
-	
-	<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" />
-	<link href="client-assets/css/dashboard-2021.css" rel="stylesheet" />
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js" crossorigin="anonymous"></script>
-	<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
-	<script src="client-assets/js/skycons.js"></script>
-	<style>
-    	.btn-append {
-    		color: #495057;
-    		background-color: #e9ecef;
-    		border: 1px solid #ced4da;
-    	}
-    	.footer {
-    		position: absolute;
-    		bottom: 0;
-    		width: 100%;
-    		height: 60px;
-    		line-height: 60px;
-    	}
-	</style>
-</head>
-
-<body class="sb-nav-fixed" id="page-top"> 
-    [[!Personalize?
-        &yesChunk=`navHeaderLogged`
-        &noChunk=`navHeader`
-        &allowedGroups=`basicClients,bacicAdmin,companyClients,companyManagers`
-    ]]
-    
-	<div id="layoutSidenav">
-	    <div id="layoutSidenav_nav">
-    		<!-- Sidebar -->
-    		[[Wayfinder?
-    		    &startId=`2` 
-        		&displayStart=`0` 
-        		&startitemTpl=`startitemTpl` 
-        		&selfClass=`show` 
-        		&level=`2`
-        		&outerTpl=`outer`
-        		    &outerClass=`sb-sidenav accordion sb-sidenav-dark`
-        		&rowTpl=`row`
-        		    &rowClass=`nav-link`
-        		&parentRowTpl=`parentRow`
-        		    &parentClass=`nav-link collapsed`
-        		&innerTpl=`inner`
-        		    &innerClass=`collapse`
-        		&innerRowTpl=`secondInner`
-        		    &innerRowClass=`nav-link`
-    		]]
-        </div>
-		
-		<div id="layoutSidenav_content">
-			<main>
-
-				<div class="container-fluid px-4">
-				    <h1 class="mt-4">[[*pagetitle]]</h1>
-
-					<ol class="breadcrumb mb-4"> [[$dash-breadcrumbs]] </ol>
-					
-					<div class="row">
-					    <script type="text/javascript" src="//d3js.org/d3.v3.js"></script>
-<script type="text/javascript" src="/client-assets/js/plant.js"></script>
-
-<div class="grid-form1">
-<h3 id="forms-example" class="">[[*longtitle]]</h3>
-
-<span class="error">* required fields.</span>
-
-[[!clientDetailsFORM]]
-
-<form method="post" action="[[~63~]]" id="csvForm" >
-
-    [[!Personalize?
-        &yesChunk=`analysisLogged_Clientdetails`
-        &noChunk=`analysis_Clientdetails`
-    ]]
-    
-    <hr>
-
-    <label class="col"><b>Animal Detail</b></label>
-
-<div class="row">
-    
-    <div class="col-md-3 ">
-        <small id="n" class="form-text text-muted">Enter Dry Matter Intake Per Day  Kg</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control" name="lab_no" id="lab_no" placeholder="Dry Matter" ></input>
-            <div class="input-group-append">
-                <span class="input-group-text" id="">kg</span>
-            </div>
-        </div>
-    </div>
+<?php
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
+require_once __DIR__ . '/../../../lib/csrf.php';
 
-    <div class="col-md-3 ">
-        <small for="batch_no">Animal Weight</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control" name="batch_no" id="batch_no" placeholder="Batch No">
-            <div class="input-group-append">
-                <span class="input-group-text" id="">kg</span>
-            </div>
-        </div>
-    </div>
-    
-    <div class="col-md-3 ">
-        <small id="calving_month" class="form-text text-muted">Calving Month</small>
-        <input class="form-control form-control-sm" name="calving_month" id="datepicker" placeholder="Month" data-date="102/2012" data-date-format="mm/yyyy" data-date-viewmode="years" data-date-minviewmode="months" ></input>
-        <script>
-            $('#datepicker').datepicker({
-                uiLibrary: 'bootstrap4',
-                format: "mm-yyyy",
-                startView: "year", 
-                minView: "year"
-            });
-        </script>
-    </div>
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
 
-    <div class="col-md-3 ">
-    	<small for="sample_id" class="form-text text-muted">Lactation Period<span class="error">*</span></small>
-    	<div><select name="analysis_type" id="analysis_type" class="form-control form-control-sm" required>
-    		<option>Select the Lactation Period...</option>
-    		<option id="peak">Peak Lactation</option>
-    		<option id="mid">Mid Lactation</option>
-    		<option id="late">Late Lactation</option>
-    	</select>
-    	</div>
-    </div>
+requireLogin();
 
-</div>
-    <div class="row">
-    <div class="col-md-3  mb-0">
-    	<small id="analysis_type" class="form-text text-muted">Analysis Type<span class="error">*</span></small>
-    	<div><select name="analysis_type" id="analysis_type" class="form-control form-control-sm" required>
-    		<option>Select the type of Analysis...</option>
-    		<option id="ash">Dry Ash Test</option>
-    		<option id="sap">Sap Analysis</option>
-    		<option id="field">Field Test</option>
-    	</select></div>
-    </div>
+$pageTitle = 'Animal Dietary Balance';
+$siteName  = 'Crop Monitor';
 
-    <div class="col-md-3  mb-0">
-        <small id="lab_no" class="form-text text-muted">Lab No</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="lab_no" id="lab_no" placeholder="Lab No"></input>
-        
-        </div>
-    </div>
-    
-    <div class="col-md-3  mb-0">
-        <small id="batch_no" class="form-text text-muted">Batch No</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="batch_no" id="batch_no" placeholder="Batch No"></input>
-            
-        </div>
-    </div>
-    <div class="col-md-3  mb-0">
-        <small id="date_sampled" class="form-text text-muted">Date Sampled</small>
-        <input data-provide="datepicker" class="form-control form-control-sm" name="date_sampled" id="datepicker" placeholder="Date Sampled" data-date-format="dd/mm/yyyy" data-date-end-date="0d" ></input>
-        <script>
-            $('#datepicker').datepicker({
-                uiLibrary: 'bootstrap4',
-                calendarWeeks: 'true',
-                todayHighlight: 'true'
-            });
-        </script>
-    </div>
-    
-    <!-- Hidden Fields for Database -->
-    <input type="hidden" class="form-control form-control-sm" name="m_user" id="m_user" placeholder="[[+modx.user.id]]" required>
-    <input type="hidden" class="form-control form-control-sm" name="client_id" id="client_id" placeholder="[[+modx.user.username]]" required>
-</div>
-
-<div class="row">
-    <div class="col-md-4 ">
-        <small id="lab_no" class="form-text text-muted">Sample ID<span class="error">*</span></small>
-        <input type="text" class="form-control form-control-sm" name="sample_id" id="sample_id" placeholder="Sample Id" required></input>
-    </div>
-    
-    <div class="col-md-4 ">
-        <small id="lab_no" class="form-text text-muted">Site ID</small>
-        <input type="text" class="form-control form-control-sm" name="site_id" id="site_id" placeholder="Paddock Id"></input>
-    </div>
-
-    [[!croptypeFORM]]
-</div>
-
-<hr class="my-2">
-
-<label class="col mb-0"><b>Analysis Inputs</b></label>
-		
-<div class="row">
-    
-    <div class=" col-md-2">
-    	<small id="ca" class="form-text text-muted">Calcium</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="ca" id="ca" placeholder="Ca - Calcium"></input>
-	    <div class="input-group-append">
-                <input class="btn btn-append" type="button" id="calcium" value="ppm" onClick="conversion(this, calcium, ca, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-
-	<div class=" col-md-2">
-	<small id="p" class="form-text text-muted">Phosphorus</small>
-        <div class="input-group input-group-sm mb-30">
-            <input type="text" class="form-control form-control-sm" name="p" id="p" placeholder="P - Phosphorus"></input>
-	    <div class="input-group-append">
-                <input class="btn btn-append" type="button" id="phosphorus" value="ppm" onClick="conversion(this, phosphorus, p, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-    
-    <div class=" col-md-2">
-	<small id="mg" class="form-text text-muted">Magnesium</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="mg" id="mg" placeholder="Mg - Magnesium"></input>
-	    <div class="input-group-append">
-                <input class="btn btn-append" type="button" id="magnesium" value="ppm" onClick="conversion(this, magnesium, mg, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-	<small id="k" class="form-text text-muted">Postassium</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="k" id="k" placeholder="K - Postassium"></input>
-	    <div class="input-group-append">
-                <input class="btn btn-append" type="button" id="postassium" value="ppm" onClick="conversion(this, postassium, k, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-    
-    <div class=" col-md-2">
-	<small id="nSmall" class="form-text text-muted">Nitrogen</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="n" id="n" placeholder="N - Nitrogen"></input>
-	    <div class="input-group-append">
-                <input class="btn btn-append" type="button" id="nitrogen" value="ppm" onClick="conversion(this, nitrogen, n, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-	<small id="s" class="form-text text-muted">Sulphur</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="s" id="s" placeholder="S - Sulphur"></input>
-	    <div class="input-group-append">
-                <input class="btn btn-append" type="button" id="sulphur" value="ppm" onClick="conversion(this, sulphur, s, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-
-</div>
+include __DIR__ . '/../../../layouts/header.php';
+include __DIR__ . '/../../../layouts/navbar.php';
+?>
 
+<style>
+    .btn-append {
+        color: #495057;
+        background-color: #e9ecef;
+        border: 1px solid #ced4da;
+    }
+</style>
+
+<div id="layoutSidenav">
+    <div id="layoutSidenav_nav">
+        <?php include __DIR__ . '/../../../layouts/sidebar.php'; ?>
+    </div>
+    <div id="layoutSidenav_content">
+        <main>
+            <div class="container-fluid px-4">
+                <h1 class="mt-4"><?= htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8') ?></h1>
+                <ol class="breadcrumb mb-4">
+                    <li class="breadcrumb-item"><a href="/dashboard/dashboard.php">Dashboard</a></li>
+                    <li class="breadcrumb-item active">Animal Dietary Balance</li>
+                </ol>
+
+                <div class="row">
+                    <div class="container">
+                        <h3>Animal Dietary Balance Entry</h3>
+                        <span class="text-danger small">* required fields.</span>
+
+                        <?php include __DIR__ . '/../../../components/clientDetailsForm.php'; ?>
+
+                        <form method="post" action="/controllers/animalTestSubmit.php" id="csvForm">
+                            <input type="hidden" name="csrf_token" value="<?= htmlspecialchars(generateCsrfToken(), ENT_QUOTES, 'UTF-8') ?>">
+
+                            <hr>
+                            <label class="col"><b>Animal Detail</b></label>
+
+                            <div class="row">
+                                <div class="col-md-3">
+                                    <small class="form-text text-muted">Dry Matter Intake Per Day</small>
+                                    <div class="input-group input-group-sm mb-3">
+                                        <input type="text" class="form-control" name="dry_matter_kg" id="dry_matter_kg" placeholder="Dry Matter">
+                                        <span class="input-group-text">kg</span>
+                                    </div>
+                                </div>
+
+                                <div class="col-md-3">
+                                    <small class="form-text text-muted">Animal Weight</small>
+                                    <div class="input-group input-group-sm mb-3">
+                                        <input type="text" class="form-control" name="animal_weight_kg" id="animal_weight_kg" placeholder="Animal Weight">
+                                        <span class="input-group-text">kg</span>
+                                    </div>
+                                </div>
+
+                                <div class="col-md-3">
+                                    <small class="form-text text-muted">Calving Month</small>
+                                    <input class="form-control form-control-sm" name="calving_month" id="calving_month" placeholder="mm/yyyy" type="month">
+                                </div>
+
+                                <div class="col-md-3">
+                                    <small class="form-text text-muted">Lactation Period <span class="text-danger">*</span></small>
+                                    <select name="lactation_period" id="lactation_period" class="form-control form-control-sm" required>
+                                        <option value="">Select the Lactation Period...</option>
+                                        <option value="peak">Peak Lactation</option>
+                                        <option value="mid">Mid Lactation</option>
+                                        <option value="late">Late Lactation</option>
+                                    </select>
+                                </div>
+                            </div>
+
+                            <div class="row">
+                                <div class="col-md-3">
+                                    <small class="form-text text-muted">Analysis Type <span class="text-danger">*</span></small>
+                                    <select name="analysis_type" id="analysis_type" class="form-control form-control-sm" required>
+                                        <option value="">Select the type of Analysis...</option>
+                                        <option value="ash">Dry Ash Test</option>
+                                        <option value="sap">Sap Analysis</option>
+                                        <option value="field">Field Test</option>
+                                    </select>
+                                </div>
+
+                                <div class="col-md-3">
+                                    <small class="form-text text-muted">Lab No</small>
+                                    <input type="text" class="form-control form-control-sm" name="lab_no" id="lab_no" placeholder="Lab No">
+                                </div>
+
+                                <div class="col-md-3">
+                                    <small class="form-text text-muted">Batch No</small>
+                                    <input type="text" class="form-control form-control-sm" name="batch_no" id="batch_no" placeholder="Batch No">
+                                </div>
+
+                                <div class="col-md-3">
+                                    <small class="form-text text-muted">Date Sampled</small>
+                                    <input type="date" class="form-control form-control-sm" name="date_sampled" id="date_sampled" placeholder="Date Sampled">
+                                </div>
+                            </div>
+
+                            <div class="row">
+                                <div class="col-md-4">
+                                    <small class="form-text text-muted">Sample ID <span class="text-danger">*</span></small>
+                                    <input type="text" class="form-control form-control-sm" name="sample_id" id="sample_id" placeholder="Sample Id" required>
+                                </div>
+
+                                <div class="col-md-4">
+                                    <small class="form-text text-muted">Site ID</small>
+                                    <input type="text" class="form-control form-control-sm" name="site_id" id="site_id" placeholder="Paddock Id">
+                                </div>
+
+                                <div class="col-md-4">
+                                    <small class="form-text text-muted">Crop Type</small>
+                                    <input type="text" class="form-control form-control-sm" name="crop_type" id="crop_type" placeholder="Crop / Pasture Type">
+                                </div>
+                            </div>
+
+                            <hr class="my-2">
+                            <label class="col mb-0"><b>Analysis Inputs</b></label>
+
+                            <div class="row mt-2">
+                                <?php
+                                $elements = [
+                                    ['ca',  'Calcium',    10000],
+                                    ['p',   'Phosphorus', 10000],
+                                    ['mg',  'Magnesium',  10000],
+                                    ['k',   'Potassium',  10000],
+                                    ['n',   'Nitrogen',   10000],
+                                    ['s',   'Sulphur',    10000],
+                                ];
+                                foreach ($elements as [$id, $label, $factor]): ?>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted"><?= htmlspecialchars($label, ENT_QUOTES, 'UTF-8') ?></small>
+                                    <div class="input-group input-group-sm mb-0">
+                                        <input type="text" class="form-control form-control-sm"
+                                               name="<?= $id ?>" id="<?= $id ?>"
+                                               placeholder="<?= htmlspecialchars($id . ' - ' . $label, ENT_QUOTES, 'UTF-8') ?>">
+                                        <input class="btn btn-append" type="button"
+                                               id="btn_<?= $id ?>" value="ppm"
+                                               onclick="conversion(this, document.getElementById('btn_<?= $id ?>'), document.getElementById('<?= $id ?>'), <?= $factor ?>)">
+                                    </div>
+                                </div>
+                                <?php endforeach; ?>
+                            </div>
+
+                            <div class="row mt-2">
+                                <?php
+                                $elements2 = [
+                                    ['na', 'Sodium',      10000],
+                                    ['b',  'Boron',       10000],
+                                    ['zn', 'Zinc',        10000],
+                                    ['cu', 'Copper',      10000],
+                                    ['mn', 'Manganese',   10000],
+                                    ['fe', 'Iron',        10000],
+                                ];
+                                foreach ($elements2 as [$id, $label, $factor]): ?>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted"><?= htmlspecialchars($label, ENT_QUOTES, 'UTF-8') ?></small>
+                                    <div class="input-group input-group-sm mb-0">
+                                        <input type="text" class="form-control form-control-sm"
+                                               name="<?= $id ?>" id="<?= $id ?>"
+                                               placeholder="<?= htmlspecialchars($id . ' - ' . $label, ENT_QUOTES, 'UTF-8') ?>">
+                                        <input class="btn btn-append" type="button"
+                                               id="btn_<?= $id ?>" value="ppm"
+                                               onclick="conversion(this, document.getElementById('btn_<?= $id ?>'), document.getElementById('<?= $id ?>'), <?= $factor ?>)">
+                                    </div>
+                                </div>
+                                <?php endforeach; ?>
+                            </div>
+
+                            <div class="row mt-2">
+                                <?php
+                                $elements3 = [
+                                    ['m',  'Molybdenum', 10000],
+                                    ['co', 'Cobalt',     10000],
+                                    ['se', 'Selenium',   10000],
+                                    ['ch', 'Chloride',   10000],
+                                ];
+                                foreach ($elements3 as [$id, $label, $factor]): ?>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted"><?= htmlspecialchars($label, ENT_QUOTES, 'UTF-8') ?></small>
+                                    <div class="input-group input-group-sm mb-0">
+                                        <input type="text" class="form-control form-control-sm"
+                                               name="<?= $id ?>" id="<?= $id ?>"
+                                               placeholder="<?= htmlspecialchars($id . ' - ' . $label, ENT_QUOTES, 'UTF-8') ?>">
+                                        <input class="btn btn-append" type="button"
+                                               id="btn_<?= $id ?>" value="ppm"
+                                               onclick="conversion(this, document.getElementById('btn_<?= $id ?>'), document.getElementById('<?= $id ?>'), <?= $factor ?>)">
+                                    </div>
+                                </div>
+                                <?php endforeach; ?>
+                            </div>
+
+                            <div class="mt-3">
+                                <button type="submit" class="btn btn-success">Submit</button>
+                            </div>
+                        </form>
+
+                        <?php include __DIR__ . '/../../../components/newClientModal.php'; ?>
+
+                        <hr>
+
+                        <div class="card mt-3">
+                            <div class="card-body">
+                                <h5 class="card-title">Excel/CSV Upload</h5>
+                                <p class="card-text">Download a CSV of this form for easy filling or upload a filled form to pre-populate.</p>
+                                <div class="input-group mt-3">
+                                    <input type="file" class="form-control" id="CM-upload">
+                                    <button class="btn btn-outline-secondary" type="button" id="CM-download">Download</button>
+                                </div>
+                            </div>
+                        </div>
 
-	
-<div class="row">
-	<div class=" col-md-2">
-		<small id="na" class="form-text text-muted">Soduim</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="na" id="na" placeholder="Na - Soduim"></input>
-    	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="soduim" value="ppm" onClick="conversion(this, soduim, na, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-    
-    <div class=" col-md-2">
-		<small id="b" class="form-text text-muted">Boron</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="b" id="b" placeholder="B - Boron"></input>
-    	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="boron" value="ppm" onClick="conversion(this, boron, b, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-    
-    <div class=" col-md-2">
-		<small id="zn" class="form-text text-muted">Zinc</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="zn" id="zn" placeholder="Zn - Zinc"></input>
-    	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="zinc" value="ppm" onClick="conversion(this, zinc, zn, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-    
-    <div class=" col-md-2">
-		<small id="cu" class="form-text text-muted">Copper</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="cu" id="cu" placeholder="Cu - Copper"></input>
-    	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="copper" value="ppm" onClick="conversion(this, copper, cu, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-		<small id="mn" class="form-text text-muted">Manganese</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="mn" id="mn" placeholder="Mn - Manganese"></input>
-    	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="manganese" value="ppm" onClick="conversion(this, manganese, mn, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-    
-	<div class=" col-md-2">
-		<small id="fe" class="form-text text-muted">Iron</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="fe" id="fe" placeholder="Fe - Iron"></input>
-    	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="iron" value="ppm" onClick="conversion(this, iron, fe, 10000)" ></input>
+                    </div>
+                </div>
             </div>
-        </div>
-    </div>
-	
-</div>
+        </main>
 
-<div class="row">
-	<div class=" col-md-2">
-		<small id="m" class="form-text text-muted">Molybdenum</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="m" id="m" placeholder="M - Molybdenum"></input>
-     	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="molybdenum" value="ppm" onClick="conversion(this, molybdenum, m, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-		<small id="co" class="form-text text-muted">Cobalt</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="co" id="co" placeholder="Co - Cobalt"></input>
-	 	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="cobalt" value="ppm" onClick="conversion(this, cobalt, co, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-		<small id="se" class="form-text text-muted">Selenium</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="se" id="se" placeholder="Se - Selenium"></input>
-	 	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="selenium" value="ppm" onClick="conversion(this, selenium, se, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-		<small id="ch" class="form-text text-muted">Chloride</small>
-        <div class="input-group input-group-sm mb-0">
-            <input type="text" class="form-control form-control-sm" name="ch" id="ch" placeholder="Ch - Chloride"></input>
-	 	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="chloride" value="ppm" onClick="conversion(this, chloride, cg, 10000)" ></input>
-            </div>
-        </div>
+        <?php include __DIR__ . '/../../../layouts/footer.php'; ?>
     </div>
-    
 </div>
 
-
-<script type="text/javascript">
-    function conversion(value, input, element, conversion) {
-        var Nvalue = document.getElementById(element.id).value;
-        var el = document.getElementById(input.id);
-        if ( el.value === "%" ) {
-            el.value = "ppm";
-            document.getElementById(element.id).value = Nvalue*conversion;
-        } else {
-            el.value = "%";
-            document.getElementById(element.id).value = Nvalue/conversion;
-        }
+<script>
+function conversion(btnEl, input, element, factor) {
+    var val = parseFloat(element.value) || 0;
+    if (btnEl.value === 'ppm') {
+        btnEl.value = '%';
+        element.value = (val / factor).toFixed(4);
+    } else {
+        btnEl.value = 'ppm';
+        element.value = (val * factor).toFixed(2);
     }
+}
 </script>
-    
-	
-		
-    <button form="csvForm" type="submit" class="btn btn-success">Submit</button>
-</form>
-
-[[!newClientDetails]]
-
-<hr>
-
-<label class="col"><b>Excel/CSV Upload</b></label>
-
-<div class="input-group mt-3">
-  <div class="custom-file">
-    <input type="file" class="custom-file-input" id="CM-upload">
-    <label class="custom-file-label" for="CM-upload">Choose file</label>
-  </div>
-  <div class="input-group-append">
-    <button class="btn btn-outline-secondary" type="button" id="CM-download">Download</button>
-  </div>
-</div>
-
-<hr>
-
-<script type="text/javascript">
-    function Choice() {
-        y = document.getElementById("client_id");
-        document.getElementById("email").value = email[y.selectedIndex];
-        document.getElementById("name").value = name[y.selectedIndex];
-        document.getElementById("company").value = company[y.selectedIndex];
-        document.getElementById("site_address").value = site_address[y.selectedIndex];
-        document.getElementById("state_postcode").value = state_postcode[y.selectedIndex];
-        }
-</script>
-
-</div>
-					</div>
-
-				</div>
-
-			</main>
-			
-			<footer class="py-4 bg-light mt-auto">
-                <div class="container-fluid px-4">
-                    <div class="d-flex align-items-center justify-content-between small">
-                        <div class="text-muted">[[SimpleCopyright? &startYear=`2003`]]. All Rights Reserved <a href="[[~1]]"></a></div>
-                        <div>
-                            <a href="[[~39~]]">Privacy Policy</a>
-                            &middot;
-                            <a href="[[~39~]]">Terms &amp; Conditions</a>
-                        </div>
-                    </div>
-                </div>
-            </footer>
-            
-		</div>
-
-	</div>
-
-	<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-	<script>
-    	/*!
-            * Start Bootstrap - SB Admin v7.0.3 (https://startbootstrap.com/template/sb-admin)
-            * Copyright 2013-2021 Start Bootstrap
-            * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-sb-admin/blob/master/LICENSE)
-            */
-            // 
-        // Scripts
-        // 
-        
-        window.addEventListener('DOMContentLoaded', event => {
-        
-            // Toggle the side navigation
-            const sidebarToggle = document.body.querySelector('#sidebarToggle');
-            if (sidebarToggle) {
-                // Uncomment Below to persist sidebar toggle between refreshes
-                // if (localStorage.getItem('sb|sidebar-toggle') === 'true') {
-                //     document.body.classList.toggle('sb-sidenav-toggled');
-                // }
-                sidebarToggle.addEventListener('click', event => {
-                    event.preventDefault();
-                    document.body.classList.toggle('sb-sidenav-toggled');
-                    localStorage.setItem('sb|sidebar-toggle', document.body.classList.contains('sb-sidenav-toggled'));
-                });
-            }
-        
-        });
-
-	</script>
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js" crossorigin="anonymous"></script>
-	<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest" crossorigin="anonymous"></script>
-</body>
-</html>

+ 109 - 197
dashboard/crop-analysis/animal-dietary-balance/animal-dietary-balance.php

@@ -1,218 +1,130 @@
+<?php
+/**
+ * Animal Dietary Balance results display page.
+ * Loads a single animal_records row by rid + rand params.
+ */
+
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+requireLogin();
+
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+
+$recordId = (int)   ($_GET['rid']  ?? 0);
+$randId   = (float) ($_GET['rand'] ?? 0);
+
+$row = null;
+if ($recordId > 0) {
+    $stmt = $pdo->prepare('SELECT * FROM animal_records WHERE id = ? AND rand = ? LIMIT 1');
+    $stmt->execute([$recordId, $randId]);
+    $row = $stmt->fetch();
+}
+
+$h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
+?>
 <!doctype html>
 <html lang="en">
-	<head>
-		<title>[[*longtitle]] | [[++site_name]]</title>
-		<base href="[[!++site_url]]" >
-		<meta charset="[[++modx_charset]]" >
-		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" >
-		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
-		<meta name="keywords" content="[[*introtext]]" >
-		<meta name="description" content="[[*description]]" >
-
-		[[!Profile]]
-		
-		[[$dash-header]]
-		
-		<link href="/client-assets/css/dashboard.css" rel="stylesheet" type="text/css" />
-		<script src="https://unpkg.com/gijgo@1.9.11/js/gijgo.min.js" type="text/javascript"></script>
-		<link href="https://unpkg.com/gijgo@1.9.11/css/gijgo.min.css" rel="stylesheet" type="text/css" />
-		<script src="client-assets/js/skycons.js" type="text/javascript"></script>
-    
-        <link href="client-assets/home/css/graphing.css" rel="stylesheet" type="text/css" media="screen" />
-        <link href="client-assets/home/css/alux.min.css" rel="stylesheet" type="text/css" media="screen" />
-        
-        <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" integrity="sha512-YcsIPGdhPK4P/uRW6/sruonlYj+Q7UHWeKfTAkBW+g83NKM+jMJFJ4iAPfSnVp7BKD4dKMHmVSvICUbE/V1sSw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
-
-        <style>
-            @media print {
-                @page {
-                    size: A4 portrait;
-                }
-                @page :left {
-                    margin-left: 0.5cm;
-                }
-                @page :right {
-                    margin-right: 0.5cm;
-                }
-                @page :top {
-                    margin-top: 0cm;
-                }
-                @page :bottom {
-                    margin-bottom: 0cm;
-                }
-            }
-        </style>
-
-    </head>
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Animal Dietary Balance | Crop Monitor</title>
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
+    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.css" crossorigin="anonymous" rel="stylesheet">
+    <link href="/client-assets/css/dashboard.css" rel="stylesheet">
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" crossorigin="anonymous"></script>
+    <style>
+        @media print {
+            @page { size: A4 portrait; margin: 0.5cm; }
+            .d-print-none { display: none !important; }
+        }
+        .progress { border-radius: 0 !important; }
+    </style>
+</head>
 <body>
+<div class="container" id="content">
 
-<link rel="stylesheet" href="client-assets/home/css/graphPrint.css" media="print">
-
-<style>
-    .progress {
-        border-radius: 0rem !important;
-    }
-</style>
+    <?php if (!$row): ?>
+        <div class="alert alert-danger mt-4">Record not found or access denied.</div>
+    <?php else: ?>
 
-<div class="container" id="content">
-    <div class="row">
-        [[!logoHeader]]
+    <div class="row mb-3">
+        <div class="col-md-3">
+            <img class="img-fluid" src="/client-assets/images/crop-monitor.png" alt="Crop Monitor">
+        </div>
     </div>
 
-    [[!animalAnalysisClient]]
-    
-    
-    <!-- Graph Button -->
-    <div class="d-print-none">
-        [[!animalAnalysisReportButton]]
-        <!-- <button class="btn btn-secondary downloadPDF" >Generate PDF</button> -->
+    <div class="d-print-none mb-3">
+        <button class="btn btn-success btn-sm downloadPDF">
+            <i class="fas fa-download me-1"></i>Download PDF
+        </button>
     </div>
 
-    <!-- GRAPH BANNER -->
-    
     <div class="row">
-        <div class="col-md-12 text-center font-weight-bold h4" >ANIMAL DIETARY MINERAL BALANCE</div>
+        <div class="col-md-12 text-center fw-bold h4">ANIMAL DIETARY MINERAL BALANCE</div>
     </div>
 
     <hr class="p-1 m-1">
-    
-    <!-- CHART HEADER  -->
-    
-    <table class="chart">
+
+    <table class="table table-bordered table-sm">
         <tbody>
-          <tr class="chart-header">
-            <th class="text-center col-1-7">Elements</th>
-            <th class="text-center col-1-7">Daily Intake</th>
-            <th class="text-center col-1-7">Daily Req.</th>
-            <th class="text-center col-1-7">Nutrient Bal</th>
-            <th class="text-center col-1-7">Deficit</th>
-            <th class="text-center col-1-7">Satisfactory</th>
-            <th >Surplus</th>
-          </tr>
-          
-          <tr>
-            <td class="border-left"></td>
-            <td class="border-left"></td>
-            <td class="border-left"></td>
-            <td class="border-left nutrient-balance"></td>
-            <td class="border-left"></td>
-            <td class="border-left"></td>
-            <td class="border-left border-right"></td>
-          </tr>
-          
-          [[!animalAnalysisCalcs? &element=`n`  &nutrient=`Nitrogen` &weight=`g`]]
-          [[!animalAnalysisCalcs? &element=`p`  &nutrient=`Phosphorus` &weight=`g`]]
-          [[!animalAnalysisCalcs? &element=`k`  &nutrient=`Potassium` &weight=`g`]]
-          [[!animalAnalysisCalcs? &element=`mg`  &nutrient=`Magnesium` &weight=`g`]]
-          [[!animalAnalysisCalcs? &element=`ca`  &nutrient=`Calcium` &weight=`g`]]
-          [[!animalAnalysisCalcs? &element=`na`  &nutrient=`Sodium` &weight=`g`]]
-    
-          
-          <tr>
-            <td class="text-left border-left"></td>
-            <td class="text-center border-left"></td>
-            <td class="text-center border-left"></td>
-            <td class="text-center border-left nutrient-balance"></td>
-            <td class="text-center border-left"></td>
-            <td class="text-center border-left"></td>
-            <td class="text-center border-left border-right"></td>
-          </tr>
-          
-          [[!animalAnalysisCalcs? &element=`fe` &nutrient=`Iron` &weight=`mg`]]
-          [[!animalAnalysisCalcs? &element=`mn`  &nutrient=`Manganese` &weight=`mg`]]
-          [[!animalAnalysisCalcs? &element=`zn`  &nutrient=`Zinc` &weight=``]]
-          [[!animalAnalysisCalcs? &element=`cu`  &nutrient=`Copper` &weight=`mg`]]
-          [[!animalAnalysisCalcs? &element=`b`  &nutrient=`Boron` &weight=`mg`]]
-          
-          <tr>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left nutrient-balance"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left border-right"></td>
-          </tr>
-          
-          [[!animalAnalysisCalcs? &element=`mo` &nutrient=`Molybdenum` &weight=`mg`]]
-          [[!animalAnalysisCalcs? &element=`co`  &nutrient=`Colbalt` &weight=`mg`]]
-          [[!animalAnalysisCalcs? &element=`se`  &nutrient=`Selenium` &weight=`mg`]]
-          [[!animalAnalysisCalcs? &element=`ch`  &nutrient=`Chloride` &weight=`g`]]
-          
-          <tr>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left nutrient-balance"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left border-right"></td>
-          </tr>
-          
+            <tr>
+                <td class="fw-bold text-end">Client:</td>
+                <td><?= $h($row['client_name'] ?? '') ?></td>
+                <td class="fw-bold text-end">Sample ID:</td>
+                <td><?= $h($row['sample_id'] ?? '') ?></td>
+            </tr>
+            <tr>
+                <td class="fw-bold text-end">Lab No:</td>
+                <td><?= $h($row['lab_no'] ?? '') ?></td>
+                <td class="fw-bold text-end">Date Sampled:</td>
+                <td><?= $h($row['date_sampled'] ?? '') ?></td>
+            </tr>
+            <tr>
+                <td class="fw-bold text-end">Analysis Type:</td>
+                <td><?= $h($row['analysis_type'] ?? '') ?></td>
+                <td class="fw-bold text-end">Site ID:</td>
+                <td><?= $h($row['site_id'] ?? '') ?></td>
+            </tr>
         </tbody>
     </table>
 
-    <div class="clearfix"></div>
-    
-    <p>Note: The above assessment is for individual nutrient levels.  The influence of nutrient interations is not considered above.  Some key Nutrient Ratio Indices, which do account for these interactions, are shown below.  Although based on published calculations, they should be used with caution, as metabolic disorders can be induced by a multitude of factors, and not just these nutrient rations alone.</p>
-    
-    <div class="clearfix"></div>
-    
-    [[!animalRatios]]
-    
-    <div class="clearfix"></div>      
-    
-    <p>The normal range levels shown above are based on the minimum for either animal or plant requirement (which ever is the greater).  The following nutrient ratio indices have been calculated as requested to assist in evaluation the sutability of ths sample as a dairy feed. Although based on published calculations, hey should be useed with caution, as metabolic disorders can be induced by a multitude of factors and not just these nutrient ratios alone.</p>
+    <!-- Analysis calculation rows — pending PHP migration of animalAnalysisCalcs -->
+    <div class="alert alert-info mt-3">
+        <i class="fas fa-info-circle me-1"></i>
+        Animal dietary analysis calculation display is pending full PHP migration.
+    </div>
+
+    <p>Note: The above assessment is for individual nutrient levels. The influence of nutrient interactions is not considered above.</p>
+
     <p><b>References:</b></p>
-    <p><ul>
-        <li>AFIA Laboratory Methods Manual v7 Appendix 2.2R(1) , p93 <a href="https://www.afia.org.au/files/pdfs/AFIA_Lab_Manual_v7.pdf">Link</a></li>
-        <li>NRC Nutrient Requirements of Dairy Cattle. National Academy Press, Washington DC. 7th Revised Edition, 2001 <a href="#">Link</a></li>
-        <li>Milk Production from Pasture – Principles and Practices. Massey University, NZ, 2002 <a href="#">Link</a></li>
-        <li>Forage Evaluation in Ruminant Nutrition. CABI, Wallingford, UK, 2000<a href="https://www.researchgate.net/profile/Khalid_Hassan10/post/Can_anybody_help_with_ruminant_nutrition_textbook2/attachment/59d6393779197b80779966e8/AS%3A400869553131522%401472586157622/download/Forage+evaluation+in+Ruminant.pdf">Link</a></li>
-    </ul></p>
-    <div class="clearfix"></div>
-    
+    <ul>
+        <li>AFIA Laboratory Methods Manual v7 Appendix 2.2R(1), p93</li>
+        <li>NRC Nutrient Requirements of Dairy Cattle. National Academy Press, Washington DC. 7th Revised Edition, 2001</li>
+        <li>Milk Production from Pasture – Principles and Practices. Massey University, NZ, 2002</li>
+    </ul>
+
+    <?php endif; ?>
+
 </div>
 
-    <!-- 
-    <script src="https://cloud.tinymce.com/stable/tinymce.min.js?apiKey=xcotawi18mg1imp8im144buq68h9g3ndd3c9c8215w8qu3ld"></script>
-    <script>
-        tinymce.init({
-            selector: 'textarea',
-            menubar: false,
-            toolbar: 'bold italic  | alignleft aligncenter alignright alignjustify | bullist numlist | removeformat',
-            plugins: 'autosave',
-            autosave_interval: '20s'
-        });
-    </script>
-    -->
-    
-    [[$dash-footer]]
-    
-    <script>
-        //https://github.com/eKoopmans/html2pdf.js
-        $('.downloadPDF').click(function () {
-        	var element = document.getElementById('content'); //document.createElement("body");
-        	element.classList.remove('screen');
-        	element.classList.add('print');
-        	var opt = {
-        		margin:       3,
-        		filename:     'soil-analysis.pdf',
-        		image:        { type: 'jpeg', quality: 1.0 },
-        		html2canvas:  { scale: 2, letterRendering: true, windowWidth: 1024 },  //, windowWidth: 1024
-        		jsPDF:        { orientation: 'portrait', unit: 'mm', format: 'a4', putOnlyUsedFonts: true, floatPrecision: 'smart', }
-        	};
-        	html2pdf()
-        	    .from(element)
-        	    .toPdf()
-        	    .set(opt)
-        	    .save()
-        	    .then(function(){
-        		    element.classList.remove('print');
-        		    element.classList.add('screen');
-        	});
-        	
-        });
-    </script>
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
+<script>
+document.querySelector('.downloadPDF') && document.querySelector('.downloadPDF').addEventListener('click', function () {
+    var opt = {
+        margin: 3,
+        filename: 'animal-dietary-balance.pdf',
+        image: { type: 'jpeg', quality: 1.0 },
+        html2canvas: { scale: 2, letterRendering: true, windowWidth: 1024 },
+        jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' }
+    };
+    html2pdf().from(document.getElementById('content')).set(opt).save();
+});
+</script>
 </body>
-</html>
+</html>

+ 16 - 135
dashboard/crop-analysis/animal-dietary-balance/animal-submit.php

@@ -1,140 +1,21 @@
 <?php
-error_reporting(E_ALL);
-ini_set('display_errors', 1);
+/**
+ * animal-submit.php — DEPRECATED
+ *
+ * Superseded by /controllers/animalTestSubmit.php
+ * which uses PDO prepared statements, CSRF protection, and proper auth.
+ */
 
-$sql = null;
-//$con = mysqli_connect("localhost", "root", "R3M0T31", "cropmonitor");
-$con = mysqli_connect("localhost", "cropmonitor", "brvnCcaEYxlPCS3", "cropmonitor");
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
 
-//set todays date
-$date = date("Y-m-d H:i:s");
-
-//Get figures
-$email = (isset($_POST["email"])) ? $_POST["email"] : "";
-$client_name = (isset($_POST["name"])) ? $_POST["name"] : "";
-$site_address = (isset($_POST["site_address"])) ? $_POST["site_address"] : "";
-$state_postcode = (isset($_POST["state_postcode"])) ? $_POST["state_postcode"] : "";
-$analysis_type = (isset($_POST["analysis_type"])) ? $_POST["analysis_type"] : "";
-$lab_no = (isset($_POST["lab_no"])) ? $_POST["lab_no"] : "";
-$batch_no = (isset($_POST["batch_no"])) ? $_POST["batch_no"] : "";
-$date_sampled = (isset($_POST["date_sampled"])) ? $_POST["date_sampled"] : "";
-$sample_id = (isset($_POST["sample_id"])) ? $_POST["sample_id"] : "";
-$site_id = (isset($_POST["site_id"])) ? $_POST["site_id"] : "";
-$crop_type = (isset($_POST["crop_type"])) ? $_POST["crop_type"] : "";
-$n = (isset($_POST["n"])) ? $_POST["n"] : "";
-$p = (isset($_POST["p"])) ? $_POST["p"] : "";
-$k = (isset($_POST["k"])) ? $_POST["k"] : "";
-$s = (isset($_POST["s"])) ? $_POST["s"] : "";
-$mg = (isset($_POST["mg"])) ? $_POST["mg"] : "";
-$ca = (isset($_POST["ca"])) ? $_POST["ca"] : "";
-$na = (isset($_POST["na"])) ? $_POST["na"] : "";
-$fe = (isset($_POST["fe"])) ? $_POST["fe"] : "";
-$mn = (isset($_POST["mn"])) ? $_POST["mn"] : "";
-$zn = (isset($_POST["zn"])) ? $_POST["zn"] : "";
-$cu = (isset($_POST["cu"])) ? $_POST["cu"] : "";
-$b = (isset($_POST["b"])) ? $_POST["b"] : "";
-$m = (isset($_POST["m"])) ? $_POST["m"] : "";
-$co = (isset($_POST["co"])) ? $_POST["co"] : "";
-$se = (isset($_POST["se"])) ? $_POST["se"] : "";
-$ch = (isset($_POST["cl"])) ? $_POST["cl"] : "";
-// calculate meq/100g for additional calulations
-$k_meq = ( $k / 390 ) ;
-$s_meq = ( $s / 100000 * 33333 * 1.11 ) ;
-$ca_meq = ( $ca / 200 ) ;
-$mg_meq = ( $mg / 120 ) ;
-$na_meq = ( $na / 230 );
-$cl_meq = ( $cl / 100000 * 448.34 * 1.11 ) ;
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
 
-//$rand = substr(md5(microtime()),rand(0,26),5);
-$rand = mt_rand(10000, 99999);
+requireLogin();
 
-// Check connection
-	if (mysqli_connect_errno())
-		{
-		echo "Failed to connect to MySQL: " . mysqli_connect_error();
-		}
-		
-$sql = mysqli_query($con, "INSERT into `plant_records`
-            (
-                date,
-                email,
-                client_name,
-                site_address,
-                state_postcode,
-                analysis_type,
-                lab_no,
-                batch_no,
-                date_sampled,
-                sample_id,
-                site_id,
-                crop_type,
-                n,
-                p,
-                k,
-                s,
-                mg,
-                ca,
-                na,
-                fe,
-                mn,
-                zn,
-                cu,
-                b,
-                m,
-                co,
-                se,
-                cl,
-                k_meq,
-                s_meq,
-                ca_meq,
-                mg_meq,
-                na_meq,
-                rand
-            ) VALUES (
-                '" . $date . "',
-                '" . $email . "',
-                '" . $client_name . "',
-                '" . $site_address . "',
-                '" . $state_postcode . "',
-                '" . $analysis_type . "',
-                '" . $lab_no . "',
-                '" . $batch_no . "',
-                '" . $date_sampled . "',
-                '" . $sample_id . "',
-                '" . $site_id . "',
-                '" . $crop_type . "',
-                '" . $n . "',
-                '" . $p . "',
-                '" . $k . "',
-                '" . $s . "',
-                '" . $mg . "',
-                '" . $ca . "',
-                '" . $na . "',
-                '" . $fe . "',
-                '" . $mn . "',
-                '" . $zn . "',
-                '" . $cu . "',
-                '" . $b . "',
-                IF('" . $m . "'='',NULL,'" . $m . "'),
-                IF('" . $co . "'='',NULL,'" . $co . "'),
-                IF('" . $se . "'='',NULL,'" . $se . "'),
-                IF('" . $cl . "'='',NULL,'" . $cl . "'),
-                '" . $k_meq . "',
-                '" . $s_meq . "',
-                '" . $ca_meq . "',
-                '" . $mg_meq . "',
-                '" . $na_meq . "',
-                '" . $rand . "'
-            )" );
-$insert_id = mysqli_insert_id($con);
-            
-if ($sql === TRUE)
-	{
-	//echo "success"; //CHECKING
-	// forward to results page if successfully inserts to database
-	echo "<script>location.href = '[[~26]]?rand=" . $rand . "&cid=" . $sample_id . "&rid=" . $insert_id . "&stid=" .$crop_type . "';</script>";
-    } else {
-    die(mysqli_error($con)); // TODO: better error handling
-    //echo "User Profile incorrect";
-}
-/.
+http_response_code(410);
+echo '<p>This endpoint is no longer active. '
+   . 'Please update the form action to <code>/controllers/animalTestSubmit.php</code>.</p>';
+exit;

+ 55 - 157
dashboard/crop-analysis/compost-test-data/compost-test-data.php

@@ -1,161 +1,59 @@
-<!doctype html>
-<html lang="en">
+<?php
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
+require_once __DIR__ . '/../../../lib/csrf.php';
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+requireLogin();
+
+$pageTitle = 'Compost Test Analysis';
+$siteName  = 'Crop Monitor';
+
+include __DIR__ . '/../../../layouts/header.php';
+include __DIR__ . '/../../../layouts/navbar.php';
+?>
+
+<div id="layoutSidenav">
+    <div id="layoutSidenav_nav">
+        <?php include __DIR__ . '/../../../layouts/sidebar.php'; ?>
+    </div>
+    <div id="layoutSidenav_content">
+        <main>
+            <div class="container-fluid px-4">
+                <h1 class="mt-4"><?= htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8') ?></h1>
+                <ol class="breadcrumb mb-4">
+                    <li class="breadcrumb-item"><a href="/dashboard/dashboard.php">Dashboard</a></li>
+                    <li class="breadcrumb-item active">Compost Test Analysis</li>
+                </ol>
+
+                <div class="row">
+                    <div class="container">
+                        <h3>Compost Test Details</h3>
+                        <span class="text-danger small">* required fields.</span>
+
+                        <?php include __DIR__ . '/../../../components/clientDetailsForm.php'; ?>
+                        <?php include __DIR__ . '/../../../components/newClientModal.php'; ?>
+
+                        <form method="post" action="/controllers/compostTestSubmit.php" id="compostForm">
+                            <input type="hidden" name="csrf_token" value="<?= htmlspecialchars(generateCsrfToken(), ENT_QUOTES, 'UTF-8') ?>">
+
+                            <div class="alert alert-info mt-3">
+                                <i class="fas fa-info-circle me-1"></i>
+                                Compost analysis form — pending full field migration.
+                                Contact your administrator for the current compost test spreadsheet.
+                            </div>
+
+                            <button type="submit" class="btn btn-success mt-2">Submit</button>
+                        </form>
 
-<head>
-	<title>[[*longtitle]] | [[++site_name]]</title>
-	<base href="[[!++site_url]]">
-	<meta charset="[[++modx_charset]]">
-	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
-	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-	<meta name="keywords" content="[[*introtext]]">
-	<meta name="description" content="[[*description]]">
-	<link rel="icon" href="client-assets/images/favicon.ico?v=2" type="image/x-icon"> [[!Profile]]
-	
-	<script type="text/javascript">
-    	window.dataLayer = window.dataLayer || [];
-    
-    	function gtag() {
-    		dataLayer.push(arguments);
-    	}
-    	gtag('js', new Date());
-    	gtag('set', {
-    		'user_id': '[[+modx.user.id]]'
-    	}); // Set the user ID using signed-in user_id.
-    	gtag('config', 'UA-133963301-1');
-	</script>
-	
-	<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" />
-	<link href="client-assets/css/dashboard-2021.css" rel="stylesheet" />
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js" crossorigin="anonymous"></script>
-	<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
-	<script src="client-assets/js/skycons.js"></script>
-	<style>
-    	.btn-append {
-    		color: #495057;
-    		background-color: #e9ecef;
-    		border: 1px solid #ced4da;
-    	}
-    	.footer {
-    		position: absolute;
-    		bottom: 0;
-    		width: 100%;
-    		height: 60px;
-    		line-height: 60px;
-    	}
-	</style>
-</head>
-
-<body class="sb-nav-fixed" id="page-top"> 
-    [[!Personalize?
-        &yesChunk=`navHeaderLogged`
-        &noChunk=`navHeader`
-        &allowedGroups=`basicClients,bacicAdmin,companyClients,companyManagers`
-    ]]
-    
-	<div id="layoutSidenav">
-	    <div id="layoutSidenav_nav">
-    		<!-- Sidebar -->
-    		[[Wayfinder?
-    		    &startId=`2` 
-        		&displayStart=`0` 
-        		&startitemTpl=`startitemTpl` 
-        		&selfClass=`show` 
-        		&level=`2`
-        		&outerTpl=`outer`
-        		    &outerClass=`sb-sidenav accordion sb-sidenav-dark`
-        		&rowTpl=`row`
-        		    &rowClass=`nav-link`
-        		&parentRowTpl=`parentRow`
-        		    &parentClass=`nav-link collapsed`
-        		&innerTpl=`inner`
-        		    &innerClass=`collapse`
-        		&innerRowTpl=`secondInner`
-        		    &innerRowClass=`nav-link`
-    		]]
-        </div>
-		
-		<div id="layoutSidenav_content">
-			<main>
-
-				<div class="container-fluid px-4">
-				    <h1 class="mt-4">[[*pagetitle]]</h1>
-
-					<ol class="breadcrumb mb-4"> [[$dash-breadcrumbs]] </ol>
-					
-					<div class="row">
-					    <div class="grid-form1">
-<h3 id="forms-example" class="">[[*longtitle]]</h3>
-
-<span class="error">* required fields.</span>
-
-<form method="post" action="[[~28~]]" >
-
-    [[!Personalize?
-        &yesChunk=`analysisLogged_Clientdetails`
-        &noChunk=`analysis_Clientdetails`
-    ]]
-
-    [[$compostAnalysisForm]]
-	
-		
- <button type="submit" class="btn btn-success">Submit</button>
-</form>
-
-</div>
-					</div>
-
-				</div>
-
-			</main>
-			
-			<footer class="py-4 bg-light mt-auto">
-                <div class="container-fluid px-4">
-                    <div class="d-flex align-items-center justify-content-between small">
-                        <div class="text-muted">[[SimpleCopyright? &startYear=`2003`]]. All Rights Reserved <a href="[[~1]]"></a></div>
-                        <div>
-                            <a href="[[~39~]]">Privacy Policy</a>
-                            &middot;
-                            <a href="[[~39~]]">Terms &amp; Conditions</a>
-                        </div>
                     </div>
                 </div>
-            </footer>
-            
-		</div>
+            </div>
+        </main>
 
-	</div>
-
-	<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-	<script>
-    	/*!
-            * Start Bootstrap - SB Admin v7.0.3 (https://startbootstrap.com/template/sb-admin)
-            * Copyright 2013-2021 Start Bootstrap
-            * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-sb-admin/blob/master/LICENSE)
-            */
-            // 
-        // Scripts
-        // 
-        
-        window.addEventListener('DOMContentLoaded', event => {
-        
-            // Toggle the side navigation
-            const sidebarToggle = document.body.querySelector('#sidebarToggle');
-            if (sidebarToggle) {
-                // Uncomment Below to persist sidebar toggle between refreshes
-                // if (localStorage.getItem('sb|sidebar-toggle') === 'true') {
-                //     document.body.classList.toggle('sb-sidenav-toggled');
-                // }
-                sidebarToggle.addEventListener('click', event => {
-                    event.preventDefault();
-                    document.body.classList.toggle('sb-sidenav-toggled');
-                    localStorage.setItem('sb|sidebar-toggle', document.body.classList.contains('sb-sidenav-toggled'));
-                });
-            }
-        
-        });
-
-	</script>
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js" crossorigin="anonymous"></script>
-	<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest" crossorigin="anonymous"></script>
-</body>
-</html>
+        <?php include __DIR__ . '/../../../layouts/footer.php'; ?>
+    </div>
+</div>

+ 15 - 148
dashboard/crop-analysis/plant-test-data/generating-plant-analysis.php

@@ -1,154 +1,21 @@
 <?php
-error_reporting(E_ALL);
-//error_reporting(E_ALL ^ E_NOTICE);
-ini_set('display_errors', 1);
-$errorLog = 1;
+/**
+ * generating-plant-analysis.php — DEPRECATED
+ *
+ * Superseded by /controllers/plantTestSubmit.php
+ * which uses PDO prepared statements, CSRF protection, and proper auth.
+ */
 
-if(isset($_POST['PlantcsvForm'])) {
-    
-    if ($errorLog = "1") { error_log(print_r($_POST, true), 3, "/home/cropmonitor/public_html/site/plantformSubmit-errors.log"); }
-    
-    $sql = null;
-    $con = mysqli_connect("localhost", "cropmonitor", "brvnCcaEYxlPCS3", "cropmonitor");
-    
-    //set todays date
-    $date = date("Y-m-d H:i:s");
-    
-    // ****************************************************************************
-    // Client Details
-    $client_id = '1'; //(isset($_POST["client_id"])) ? $_POST["client_id"] : "";
-    $modx_id = '1'; //(isset($_POST["m_user"])) ? $_POST["m_user"] : "";
-    $email = (isset($_POST["email"])) ? $_POST["email"] : "";
-    $client_name = (isset($_POST["name"])) ? $_POST["name"] : "";
-    $site_address = (isset($_POST["site_address"])) ? $_POST["site_address"] : "";
-    $state_postcode = (isset($_POST["state_postcode"])) ? $_POST["state_postcode"] : "";
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
 
-    // Analysis Details
-    $analysis_type = (isset($_POST["analysis_type"])) ? $_POST["analysis_type"] : "";
-    $lab_no = (isset($_POST["lab_no"])) ? $_POST["lab_no"] : "";
-    $batch_no = (isset($_POST["batch_no"])) ? $_POST["batch_no"] : "";
-    $date_sampled = (isset($_POST["date_sampled"])) ? $_POST["date_sampled"] : "";
-    $sample_id = (isset($_POST["sample_id"])) ? $_POST["sample_id"] : "";
-    $site_id = (isset($_POST["site_id"])) ? $_POST["site_id"] : "";
-    $crop_type = (isset($_POST["crop_type"])) ? $_POST["crop_type"] : "";
-    
-    // Sample Details
-    $n = (isset($_POST["n"])) ? $_POST["n"] : "";
-    $p = (isset($_POST["p"])) ? $_POST["p"] : "";
-    $k = (isset($_POST["k"])) ? $_POST["k"] : "";
-    $s = (isset($_POST["s"])) ? $_POST["s"] : "";
-    $mg = (isset($_POST["mg"])) ? $_POST["mg"] : "";
-    $ca = (isset($_POST["ca"])) ? $_POST["ca"] : "";
-    $na = (isset($_POST["na"])) ? $_POST["na"] : "";
-    $fe = (isset($_POST["fe"])) ? $_POST["fe"] : "";
-    $mn = (isset($_POST["mn"])) ? $_POST["mn"] : "";
-    $zn = (isset($_POST["zn"])) ? $_POST["zn"] : "";
-    $cu = (isset($_POST["cu"])) ? $_POST["cu"] : "";
-    $b = (isset($_POST["b"])) ? $_POST["b"] : "";
-    $m = (isset($_POST["m"])) ? $_POST["m"] : "";
-    $co = (isset($_POST["co"])) ? $_POST["co"] : "";
-    $se = (isset($_POST["se"])) ? $_POST["se"] : "";
-    $ch = (isset($_POST["cl"])) ? $_POST["cl"] : "";
-    
-    //$rand = substr(md5(microtime()),rand(0,26),5);
-    $rand = mt_rand(10000, 99999);
-    
-    // Check connection
-    if (mysqli_connect_errno()){
-        echo "Failed to connect to MySQL: " . mysqli_connect_error();
-    }
-    		
-    $sql = mysqli_query($con, "INSERT into `plant_records`
-                (
-                    client_records_id,
-                    modx_user_id,
-                    date,
-                    email,
-                    client_name,
-                    site_address,
-                    state_postcode,
-                    analysis_type,
-                    lab_no,
-                    batch_no,
-                    date_sampled,
-                    sample_id,
-                    site_id,
-                    crop_type,
-                    n,
-                    p,
-                    k,
-                    s,
-                    mg,
-                    ca,
-                    na,
-                    fe,
-                    mn,
-                    zn,
-                    cu,
-                    b,
-                    m,
-                    co,
-                    se,
-                    cl,
-                    rand
-                ) VALUES (
-                    '{$client_id}',
-                    '{$modx_id}',
-                    '{$date}',
-                    '{$email}',
-                    '{$client_name}',
-                    '{$site_address}',
-                    '{$state_postcode}',
-                    '{$analysis_type}',
-                    '{$lab_no}',
-                    '{$batch_no}',
-                    '{$date_sampled}',
-                    '{$sample_id}',
-                    '{$site_id}',
-                    '{$crop_type}',
-                    '{$n}',
-                    '{$p}',
-                    '{$k}',
-                    '{$s}',
-                    '{$mg}',
-                    '{$ca}',
-                    '{$na}',
-                    '{$fe}',
-                    '{$mn}',
-                    '{$zn}',
-                    '{$cu}',
-                    '{$b}',
-                    '{$m}',
-                    '{$co}',
-                    '{$se}',
-                    '{$cl}',
-                    '{$rand}'
-                )" );
-    
-    $insert_id = mysqli_insert_id($con);
-                
-    if ($sql === TRUE) {
-        sleep(10);
-        // forward to results page if successfully inserts to database  [[~26]]
-    	echo "<script>location.href = '[[~26]]?rand=" . $rand . "&cid=" . $sample_id . "&rid=" . $insert_id . "&stid=" .$crop_type . "','_blank';</script>";
-        } else {
-        die(mysqli_error($con)); // TODO: better error handling
-        //echo "User Profile incorrect";
-    }
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
 }
-?>
-
-<div id="loader-gif" style="z-index:10000;" >[[$plantPOPUP]]</div>
-
-<script>
-    $(document).ready(function() {
-        $("#start-loader").click(function() {
-            $("#loader-gif").show();
-                setTimeout(function() {
-                $("#loader-gif").hide();
-            }, 5000);
-        });
-    });
-</script>
 
+requireLogin();
 
+http_response_code(410);
+echo '<p>This endpoint is no longer active. '
+   . 'Please update the form action to <code>/controllers/plantTestSubmit.php</code>.</p>';
+exit;

+ 3 - 0
dashboard/crop-analysis/plant-test-data/index.php

@@ -0,0 +1,3 @@
+<?php
+header('Location: /dashboard/crop-analysis/plant-test-data/plant-test-data.php');
+exit;

+ 237 - 231
dashboard/crop-analysis/plant-test-data/plant-analysis.php

@@ -1,252 +1,258 @@
-<!doctype html>
-<html lang="en">
-	<head>
-		<title>[[*longtitle]] | [[++site_name]]</title>
-		<base href="[[!++site_url]]" >
-		<meta charset="[[++modx_charset]]" >
-		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" >
-		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
-		<meta name="keywords" content="[[*introtext]]" >
-		<meta name="description" content="[[*description]]" >
-
-		[[!Profile]]
-		
-		[[$dash-header]]
-		
-		<link href="/client-assets/css/dashboard.css" rel="stylesheet" type="text/css" />
-		<script src="https://unpkg.com/gijgo@1.9.11/js/gijgo.min.js" type="text/javascript"></script>
-		<link href="https://unpkg.com/gijgo@1.9.11/css/gijgo.min.css" rel="stylesheet" type="text/css" />
-		<script src="client-assets/js/skycons.js" type="text/javascript"></script>
-    
-        <link href="client-assets/home/css/graphing.css" rel="stylesheet" type="text/css" media="screen" />
-        <link href="client-assets/home/css/alux.min.css" rel="stylesheet" type="text/css" media="screen" />
-        
-        <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" integrity="sha512-YcsIPGdhPK4P/uRW6/sruonlYj+Q7UHWeKfTAkBW+g83NKM+jMJFJ4iAPfSnVp7BKD4dKMHmVSvICUbE/V1sSw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
-
-        <style>
-            @media print {
-                @page {
-                    size: A4 portrait;
-                }
-                @page :left {
-                    margin-left: 0.5cm;
-                }
-                @page :right {
-                    margin-right: 0.5cm;
-                }
-                @page :top {
-                    margin-top: 0cm;
-                }
-                @page :bottom {
-                    margin-bottom: 0cm;
-                }
-            }
-        </style>
-
-    </head>
-<body>
+<?php
+/**
+ * Plant analysis results display page.
+ * Loads a single plant_records row by rid + rand params.
+ */
 
-<title>[[*pagetitle]]</title>
-<link rel="stylesheet" href="client-assets/home/css/graphPrint.css" media="print">
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
 
-<style>
-    .progress {
-        border-radius: 0rem !important;
-    }
-</style>
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
 
-<div id="loader-gif" style="display:none; z-index:10000;" >[[$plantPOPUP]]</div>
+requireLogin();
 
-<script>
-    $(document).ready(function() {
-        $("#start-loader").click(function() {
-            $("#loader-gif").show();
-                setTimeout(function() {
-                $("#loader-gif").hide();
-            }, 5000);
-        });
-    });
-</script>
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+
+$recordId = (int)   ($_GET['rid']  ?? 0);
+$randId   = (float) ($_GET['rand'] ?? 0);
+
+$row  = null;
+$specs = [];
+
+if ($recordId > 0) {
+    $stmt = $pdo->prepare('SELECT * FROM plant_records WHERE id = ? AND rand = ? LIMIT 1');
+    $stmt->execute([$recordId, $randId]);
+    $row = $stmt->fetch();
+}
+
+if ($row) {
+    $crop = $row['crop'] ?? '';
+    $stmt = $pdo->prepare('SELECT * FROM plant_specifications WHERE crop = ? LIMIT 1');
+    $stmt->execute([$crop]);
+    $specs = $stmt->fetch() ?: [];
+}
+
+$h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
+
+function statusBar(float $found, float $min, float $max): string {
+    if ($max <= 0) return '<td colspan="3" class="text-muted text-center small">N/A</td>';
+    $pct = ($max > $min) ? min(100, max(0, ($found - $min) / ($max - $min) * 100)) : 0;
+    if ($found < $min) {
+        $bar = '<td class="text-center"><div class="progress"><div class="progress-bar bg-danger" style="width:100%"></div></div></td>';
+        $bar .= '<td></td><td></td>';
+    } elseif ($found > $max) {
+        $bar = '<td></td><td></td>';
+        $bar .= '<td class="text-center"><div class="progress"><div class="progress-bar bg-warning" style="width:100%"></div></div></td>';
+    } else {
+        $bar = '<td></td>';
+        $bar .= '<td class="text-center"><div class="progress"><div class="progress-bar bg-success" style="width:' . round($pct) . '%"></div></div></td>';
+        $bar .= '<td></td>';
+    }
+    return $bar;
+}
+?>
+<!doctype html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Plant Analysis | Crop Monitor</title>
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
+    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.css" crossorigin="anonymous" rel="stylesheet">
+    <link href="/client-assets/css/dashboard.css" rel="stylesheet">
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" crossorigin="anonymous"></script>
+    <style>
+        @media print {
+            @page { size: A4 portrait; margin: 0.5cm; }
+            .d-print-none { display: none !important; }
+        }
+        .progress { border-radius: 0 !important; }
+        table.chart { width: 100%; border-collapse: collapse; }
+        table.chart th, table.chart td { padding: 3px 6px; font-size: 0.85rem; }
+        .chart-header th { background: #343a40; color: white; }
+        .chart-header-sub th { background: #6c757d; color: white; }
+        .lightgreen { background: #d4edda !important; color: #155724 !important; }
+        .lightred   { background: #f8d7da !important; color: #721c24 !important; }
+        .lightpurple { background: #e2d9f3 !important; color: #432874 !important; }
+        .stripe-1   { background: #f8f9fa; }
+        .border-left  { border-left: 1px solid #dee2e6; }
+        .border-right { border-right: 1px solid #dee2e6; }
+        .border-bottom { border-bottom: 1px solid #dee2e6; }
+        .border-top { border-top: 1px solid #dee2e6; }
+    </style>
+</head>
+<body>
+<div class="container" id="content">
+
+    <?php if (!$row): ?>
+        <div class="alert alert-danger mt-4">Record not found or access denied.</div>
+    <?php else: ?>
 
-<div class="container">
-    <div class="row mb-5">
-        [[!logoHeader]]
+    <div class="row mb-3">
+        <div class="col-md-3">
+            <img class="img-fluid" src="/client-assets/images/crop-monitor.png" alt="Crop Monitor">
+        </div>
     </div>
-    
-    [[!plantAnalysisClient]]
 
-    <!-- Graph Button -->
-    <div class="d-print-none">
-        [[!plantAnalysisReportButton]]
+    <div class="d-print-none mb-3">
+        <button class="btn btn-success btn-sm downloadPDF">
+            <i class="fas fa-download me-1"></i>Download PDF
+        </button>
     </div>
-    
-    <!-- GRAPH BANNER -->
-    
+
     <div class="row">
-        <div class="col-md-12 text-center font-weight-bold h4" >ANALYSIS RESULTS</div>
+        <div class="col-md-12 text-center fw-bold h4">ANALYSIS RESULTS</div>
     </div>
-    
+
     <hr class="p-1 m-1">
-    
-    <!-- CHART HEADER  -->
-    
+
     <table class="chart">
         <tbody>
-          <tr class="chart-header">
-            <th colspan=3 class="black text-center col-50 border-left border-right border-top">ELEMENT</th>
-            <th colspan=3 class="black text-center col-50 border-right border-top">STATUS</th>
-          </tr>
-          
-          <tr class="chart-header-sub">
-            <th class="black text-center col-16 border-bottom border-left">Elements</th>
-            <th class="black text-center col-16 border-bottom">Desired</th>
-            <th class="black text-center col-16 border-bottom">Found</th>
-            <th class="text-center col-16 stripe-1">Deficient</th>
-            <th class="text-center col-16 stripe-1">Ideal</th>
-            <th class="text-center col-16 border-right stripe-1">High</th>
-          </tr>
-          
-          <tr class="sub-chart-plant">
-            <td class="border-left"></td>
-            <td class="border-left"></td>
-            <td class="border-left nutrient-balance"></td>
-            <td class="border-left"></td>
-            <td class="border-left"></td>
-            <td class="border-left border-right"></td>
-          </tr>
-          
-          <tr class="chart-header-sub">
-            <th colspan=3 class="col-16 border-left text-center lightgreen">MAJOR ELEMENTS</th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left border-right stripe-1"></th>
-          </tr>
-          
-          [[!plantAnalysisCalcs? &element=n  &sbl=`` &nutrient=`Nitrogen` &type=`%` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-          [[!plantAnalysisCalcs? &element=p  &sbl=`` &nutrient=`Phosphorus` &type=`%` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-          [[!plantAnalysisCalcs? &element=k  &sbl=`` &nutrient=`Potassium` &type=`%` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-          [[!plantAnalysisCalcs? &element=mg  &sbl=`` &nutrient=`Magnesium` &type=`%` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-          [[!plantAnalysisCalcs? &element=ca  &sbl=`` &nutrient=`Calcium` &type=`%` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-          [[!plantAnalysisCalcs? &element=na  &sbl=`` &nutrient=`Sodium` &type=`%` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-    
-    <!-- Blank Graph row for seperation -->
-          <tr>
-            <td class="border-left"></td>
-            <td class="border-left"></td>
-            <td class="border-left nutrient-balance"></td>
-            <td class="border-left"></td>
-            <td class="border-left"></td>
-            <td class="border-left border-right"></td>
-          </tr>
-          
-          <tr class="chart-header-sub">
-            <th colspan=3 class="col-16 border-left text-center lightred">TRACE ELEMENTS</th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left border-right stripe-1"></th>
-          </tr>
-          
-    <!-- Blank Graph row for seperation -->
-          <tr>
-            <td class="border-left"></td>
-            <td class="border-left"></td>
-            <td class="border-left nutrient-balance"></td>
-            <td class="border-left"></td>
-            <td class="border-left"></td>
-            <td class="border-left border-right"></td>
-          </tr>
-
-          [[!plantAnalysisCalcs? &element=fe &sbl=`` &nutrient=`Iron` &type=`ppm` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-          [[!plantAnalysisCalcs? &element=mn  &sbl=`` &nutrient=`Manganese` &type=`ppm` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-          [[!plantAnalysisCalcs? &element=zn  &sbl=`` &nutrient=`Zinc` &type=`ppm` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-          [[!plantAnalysisCalcs? &element=cu  &sbl=`` &nutrient=`Copper` &type=`ppm` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-          [[!plantAnalysisCalcs? &element=b  &sbl=`` &nutrient=`Boron` &type=`ppm` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-
-    <!-- Blank Graph row for seperation -->
-          <tr>
-            <td class="border-left"></td>
-            <td class="border-left"></td>
-            <td class="border-left nutrient-balance"></td>
-            <td class="border-left"></td>
-            <td class="border-left"></td>
-            <td class="border-left border-right"></td>
-          </tr>
-
-          <tr class="chart-header-sub">
-            <th colspan=3 class="col-16 border-left text-center lightpurple">MACRO ELEMENTS</th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left border-right stripe-1"></th>
-          </tr>
-          
-          [[!plantAnalysisCalcs? &element=m  &sbl=`` &nutrient=`Molybdenum` &type=`ppm` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-          [[!plantAnalysisCalcs? &element=co  &sbl=`` &nutrient=`Cobalt` &type=`ppm` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-          [[!plantAnalysisCalcs? &element=se  &sbl=`` &nutrient=`Selenium` &type=`ppm` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-          [[!plantAnalysisCalcs? &element=cl  &sbl=`` &nutrient=`Chloride` &type=`ppm` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-          [[!plantAnalysisCalcs? &element=c  &sbl=`` &nutrient=`Carbon` &type=`ppm` &min=`` &max=`` &text=`y` &graph=`progress-bar-success`]]
-          
-          <tr>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left nutrient-balance"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left border-right"></td>
-          </tr>
-          
+            <tr class="chart-header">
+                <th colspan="3" class="text-center border-left border-right border-top">ELEMENT</th>
+                <th colspan="3" class="text-center border-right border-top">STATUS</th>
+            </tr>
+            <tr class="chart-header-sub">
+                <th class="text-center border-bottom border-left">Elements</th>
+                <th class="text-center border-bottom">Desired</th>
+                <th class="text-center border-bottom">Found</th>
+                <th class="text-center stripe-1">Deficient</th>
+                <th class="text-center stripe-1">Ideal</th>
+                <th class="text-center border-right stripe-1">High</th>
+            </tr>
+            <tr>
+                <td class="border-left" colspan="6"></td>
+            </tr>
+            <tr class="chart-header-sub">
+                <th colspan="3" class="border-left text-center lightgreen">MAJOR ELEMENTS</th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left border-right stripe-1"></th>
+            </tr>
+
+            <?php
+            $majorElements = [
+                ['n',  'Nitrogen',   '%'],
+                ['p',  'Phosphorus', '%'],
+                ['k',  'Potassium',  '%'],
+                ['mg', 'Magnesium',  '%'],
+                ['ca', 'Calcium',    '%'],
+                ['na', 'Sodium',     '%'],
+            ];
+            foreach ($majorElements as [$el, $nutrient, $unit]):
+                $found = (float) ($row[$el] ?? 0);
+                $min   = (float) ($specs['min_' . $el] ?? 0);
+                $max   = (float) ($specs['max_' . $el] ?? 0);
+                $desired = ($min > 0 || $max > 0) ? number_format($min, 2) . '–' . number_format($max, 2) : '—';
+            ?>
+            <tr>
+                <td class="border-left"><?= $h($nutrient) ?> (<?= $h($unit) ?>)</td>
+                <td><?= $h($desired) ?></td>
+                <td><?= $found > 0 ? number_format($found, 3) : '—' ?></td>
+                <?= statusBar($found, $min, $max) ?>
+            </tr>
+            <?php endforeach; ?>
+
+            <tr><td colspan="6" class="border-left"></td></tr>
+            <tr class="chart-header-sub">
+                <th colspan="3" class="border-left text-center lightred">TRACE ELEMENTS</th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left border-right stripe-1"></th>
+            </tr>
+            <tr><td colspan="6" class="border-left"></td></tr>
+
+            <?php
+            $traceElements = [
+                ['fe', 'Iron',      'ppm'],
+                ['mn', 'Manganese', 'ppm'],
+                ['zn', 'Zinc',      'ppm'],
+                ['cu', 'Copper',    'ppm'],
+                ['b',  'Boron',     'ppm'],
+            ];
+            foreach ($traceElements as [$el, $nutrient, $unit]):
+                $found = (float) ($row[$el] ?? 0);
+                $min   = (float) ($specs['min_' . $el] ?? 0);
+                $max   = (float) ($specs['max_' . $el] ?? 0);
+                $desired = ($min > 0 || $max > 0) ? number_format($min, 1) . '–' . number_format($max, 1) : '—';
+            ?>
+            <tr>
+                <td class="border-left"><?= $h($nutrient) ?> (<?= $h($unit) ?>)</td>
+                <td><?= $h($desired) ?></td>
+                <td><?= $found > 0 ? number_format($found, 2) : '—' ?></td>
+                <?= statusBar($found, $min, $max) ?>
+            </tr>
+            <?php endforeach; ?>
+
+            <tr><td colspan="6" class="border-left"></td></tr>
+            <tr class="chart-header-sub">
+                <th colspan="3" class="border-left text-center lightpurple">MACRO ELEMENTS</th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left border-right stripe-1"></th>
+            </tr>
+
+            <?php
+            $macroElements = [
+                ['m',  'Molybdenum', 'ppm'],
+                ['co', 'Cobalt',     'ppm'],
+                ['se', 'Selenium',   'ppm'],
+                ['cl', 'Chloride',   'ppm'],
+                ['c',  'Carbon',     'ppm'],
+            ];
+            foreach ($macroElements as [$el, $nutrient, $unit]):
+                $found = (float) ($row[$el] ?? 0);
+                $min   = (float) ($specs['min_' . $el] ?? 0);
+                $max   = (float) ($specs['max_' . $el] ?? 0);
+                $desired = ($min > 0 || $max > 0) ? number_format($min, 2) . '–' . number_format($max, 2) : '—';
+            ?>
+            <tr>
+                <td class="border-left"><?= $h($nutrient) ?> (<?= $h($unit) ?>)</td>
+                <td><?= $h($desired) ?></td>
+                <td><?= $found > 0 ? number_format($found, 3) : '—' ?></td>
+                <?= statusBar($found, $min, $max) ?>
+            </tr>
+            <?php endforeach; ?>
+
+            <tr>
+                <td class="border-bottom border-left"></td>
+                <td class="border-bottom"></td>
+                <td class="border-bottom"></td>
+                <td class="border-bottom"></td>
+                <td class="border-bottom"></td>
+                <td class="border-bottom border-right"></td>
+            </tr>
+
         </tbody>
     </table>
 
-    <div class="analysis-footer">
-        <p><i class="fa fa-envira nav_icon " style="color: green;"></i>It is always an advantage to assess tisue analysis results along with a corresponding soil analysis for more accurate diagnosis of plant nutrient status.</p>
-        <p><i class="fa fa-envira nav_icon " style="color: green;"></i>Trace element levels - manganese, copper and zinc, can all be affeced by fungicide spray residues, giving msleading results, See Comment above.</p>
-        <p><i class="fa fa-envira nav_icon " style="color: green;"></i>Talk to your qualified consultant to make a plan for correction or maintenance of the found nutrient levels.</p>
-        <p style="font-style: italic; font-size: 9px;">Desired ranges indexed from:  CSIRO Plant Anlysis Handbook 2nd Ed.  Hill Laborotories consultants guide.  PIRSA Soil and Plant Analysis.</p>
-        <p style="font-style: italic; font-size: 9px;">Any recommendationa provided by Cropmonitor are advice only We are not paid consultants and we are not covered to accept responsibiliy for any of our suggestions. As no control can be exercised over storage, handling, mixing application or use, or weather, plant or soil conditions before, during or after application (all of which may affect the preformance of our program), no responsibility for, or liability for any failure in performance, losses, damage or injuries consequential or otherwise, arisiing form such storage mixng application or use will be accepted under any circumstances whatsoever. The buyer assumes all responsibility for the use of any of our products.</p>
+    <div class="mt-4 small text-muted">
+        <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>
+        <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>
+        <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>
+        <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>
+        <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>
     </div>
 
-    
+    <?php endif; ?>
+
 </div>
 
-    <!-- 
-    <script src="https://cloud.tinymce.com/stable/tinymce.min.js?apiKey=xcotawi18mg1imp8im144buq68h9g3ndd3c9c8215w8qu3ld"></script>
-    <script>
-        tinymce.init({
-            selector: 'textarea',
-            menubar: false,
-            toolbar: 'bold italic  | alignleft aligncenter alignright alignjustify | bullist numlist | removeformat',
-            plugins: 'autosave',
-            autosave_interval: '20s'
-        });
-    </script>
-    -->
-    
-    [[$dash-footer]]
-    
-    <script>
-        //https://github.com/eKoopmans/html2pdf.js
-        $('.downloadPDF').click(function () {
-        	var element = document.getElementById('content'); //document.createElement("body");
-        	element.classList.remove('screen');
-        	element.classList.add('print');
-        	var opt = {
-        		margin:       3,
-        		filename:     'soil-analysis.pdf',
-        		image:        { type: 'jpeg', quality: 1.0 },
-        		html2canvas:  { scale: 2, letterRendering: true, windowWidth: 1024 },  //, windowWidth: 1024
-        		jsPDF:        { orientation: 'portrait', unit: 'mm', format: 'a4', putOnlyUsedFonts: true, floatPrecision: 'smart', }
-        	};
-        	html2pdf()
-        	    .from(element)
-        	    .toPdf()
-        	    .set(opt)
-        	    .save()
-        	    .then(function(){
-        		    element.classList.remove('print');
-        		    element.classList.add('screen');
-        	});
-        	
-        });
-    </script>
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
+<script>
+document.querySelector('.downloadPDF') && document.querySelector('.downloadPDF').addEventListener('click', function () {
+    var opt = {
+        margin: 3,
+        filename: 'plant-analysis.pdf',
+        image: { type: 'jpeg', quality: 1.0 },
+        html2canvas: { scale: 2, letterRendering: true, windowWidth: 1024 },
+        jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' }
+    };
+    html2pdf().from(document.getElementById('content')).set(opt).save();
+});
+</script>
 </body>
-</html>
+</html>

+ 88 - 102
dashboard/crop-analysis/plant-test-data/plant-rec-update.php

@@ -1,109 +1,95 @@
 <?php
-//Database connection
-    //$con = mysqli_connect("localhost", "root", "R3M0T31", "cropmonitor");
-    $con = mysqli_connect("localhost", "cropmonitor", "brvnCcaEYxlPCS3", "cropmonitor");
-    
-    /* check connection */
-    if (mysqli_connect_errno()) {
-        printf("Connect failed: %s\n", mysqli_connect_error());
-        exit();
+/**
+ * plant-rec-update.php
+ *
+ * Renders the plant specifications reference table for a given plant type.
+ * Included by plant analysis pages — not a standalone page.
+ * Requires auth.php and database.php to already be included.
+ */
+
+if (!function_exists('isLoggedIn')) {
+    require_once __DIR__ . '/../../../lib/auth.php';
+}
+if (!function_exists('getDBConnection')) {
+    require_once __DIR__ . '/../../../config/database.php';
+}
+
+requireLogin();
+
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+
+$plantType = trim($_REQUEST['empid'] ?? '');
+
+if ($plantType === '') {
+    echo '<p class="text-muted">No plant type specified.</p>';
+    return;
+}
+
+// ── Plant specifications table ────────────────────────────────────────────
+$stmt = $pdo->prepare('
+    SELECT plant_stage,
+           n_min, n_max, P_min, P_max, K_min, K_max, S_min, S_max,
+           Ca_min, Ca_max, Mg_min, Mg_max, Na_min, Na_max,
+           Cu_min, Cu_max, Zn_min, Zn_max, Mn_min, Mn_max,
+           B_min, B_max, Fe_min, Fe_max,
+           M_Min, M_Max, Co_min, Co_max, se_min, se_max, cl_min, cl_max
+    FROM plant_specifications
+    WHERE modx_user_id = ? AND plant_type = ?
+');
+$stmt->execute([$userId, $plantType]);
+$specs = $stmt->fetchAll();
+
+$h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
+
+if (empty($specs)) {
+    echo '<p class="text-muted">No specifications found for plant type: ' . $h($plantType) . '</p>';
+} else {
+    echo '<div class="table-responsive text-nowrap">';
+    echo '<table class="table table-sm table-striped table-hover table-bordered">';
+    echo '<thead><tr>';
+    $cols = array_keys($specs[0]);
+    foreach ($cols as $col) {
+        $label = ucwords(str_replace('_', ' ', $col));
+        echo '<th class="text-center text-capitalize">' . $h($label) . '</th>';
     }
-    
-    $empid =  mysqli_real_escape_string($con, $_REQUEST["empid"]);
-    $user_id = $modx->user->get('id');
+    echo '</tr></thead><tbody>';
 
-        $query  = "CREATE TEMPORARY TABLE `plant_temp`
-                    SELECT id, modx_user_id, plant_type, plant_stage, n_min, n_max, P_min, P_max, K_min, K_max, S_min, S_max, Ca_min, Ca_max, Mg_min, Mg_max, Na_min, Na_max, Cu_min, Cu_max, Zn_min, Zn_max, Mn_min, Mn_max, B_min, B_max, Fe_min, Fe_max, M_min, M_max, Co_min, Co_max, se_min, se_max, cl_min, cl_max, c_min, c_max 
-                    FROM `plant_specifications`
-                    WHERE `modx_user_id` = '" . $user_id . "'
-                    AND `plant_type` = '" . $empid . "'; ";
-        
-        $query .= "ALTER TABLE `plant_temp`
-                    DROP `id`, 
-                    DROP `modx_user_id`,
-                    DROP `plant_type`
-                    ; ";
-        $query .= "SELECT * FROM `plant_temp`; ";
-        
-        $result = mysqli_multi_query($con, $query);
-    
-        if ($result) {
-            do {
-                // grab the result of the next query
-                if (($result = mysqli_store_result($con)) === false && mysqli_error($con) != '') {
-                    echo "Query failed: " . mysqli_error($con);
-                }
-            } while (mysqli_more_results($con) && mysqli_next_result($con)); // while there are more results
-        } else {
-            echo "First query failed..." . mysqli_error($con);
+    foreach ($specs as $row) {
+        echo '<tr>';
+        foreach ($row as $key => $val) {
+            $display = ($val === '' || $val === null) ? '0.0'
+                : (is_numeric($val) ? number_format((float) $val, 2, '.', '') : $h($val));
+            echo '<td class="text-center">' . $display . '</td>';
         }
-        
-        //get results from database
-        $all_property = array();  //declare an array for saving property
-        
-        
-        //showing property
-        echo '<div class="table-responsive text-nowrap">
-                <table class="table table-sm table-striped table-hover table-bordered">
-                <tbody>
-                    <tr>';  //initialize table tag
-                        while ($property = mysqli_fetch_field($result)) {
-                            $header = str_replace("_"," ",$property->name);
-                            echo '<th scope="col" class="text-center text-capitalize" >' . $header . '</th>';  //get field name for header
-                            array_push($all_property, $property->name);  //save those to array
+        echo '</tr>';
+    }
+    echo '</tbody></table></div>';
+}
 
-                        }
-                    echo '</tr>'; //end tr tag
+// ── Plant images gallery ──────────────────────────────────────────────────
+$imgStmt = $pdo->prepare('SELECT id, name FROM plant_images ORDER BY id DESC LIMIT 20');
+$imgStmt->execute();
+$images = $imgStmt->fetchAll();
 
-        //showing all data
-        while ($row = mysqli_fetch_array($result)) {
-            echo "<tr>";
-            foreach ($all_property as $item) {
-                if ($row[$item] == "" ) { $this_value = "0.0" ; } else { if (is_numeric($row[$item]) ) { $this_value = number_format((float)$row[$item], 2, '.', ''); } else { $this_value = $row[$item]; } }
-                echo '<td scope="row" class="text-center" contenteditable="true" onBlur="updateDatabase(this,"'. $row[$item->id] . '","' . $row['id'] . '")" onClick="showEdit(this);" >' . $this_value . '</td>'; //get items using property value
-            }
-            echo '</tr>';
-        }
-        echo "</tbody></table></div>";
-        
-        
-        echo "
-        <div class='row'>
-            <div class='col-2'>
-                <div class='row '>
-        ";
-        
-                      
-        $img_query = 'SELECT * FROM `plant_images` ORDER BY `id` DESC';
-        $img_result = mysqli_query($con, $img_query);  
-        while($img_row = mysqli_fetch_array($img_result)) {  
-            echo ' <img src="data:image/jpeg;base64,'.base64_encode($img_row['name'] ).'" class="img-thumbnail rounded float-left border-success" alt="" /> ';  
-        }
-                    
-        echo "
-                </div>
-                <br>
-                <div class='row'>
-                    <form method='post' enctype='multipart/form-data'>
-                        <div class='form-group'>  
-                            <input type='file' class='form-control-file' name='image' id='image' />  
-                        </div>
-                        <input type='submit' name='insert' id='insert' value='Insert' class='btn btn-info btn-sm' />
-                    </form>
-                </div>
-            </div>
-            
-            <div class='col-10 border border-success'>
-                <div class='row'>
-                    
-                </div> 
-            </div>
-        </div>
-        
-        ";
+if (!empty($images)) {
+    echo '<div class="row mt-2">';
+    foreach ($images as $img) {
+        echo '<div class="col-auto">';
+        echo '<img src="data:image/jpeg;base64,' . base64_encode($img['name']) . '" '
+           . 'class="img-thumbnail border-success" style="max-width:120px;" alt="">';
+        echo '</div>';
+    }
+    echo '</div>';
+}
 
-        $query .= "DROP TEMPORARY TABLE `temp`; ";
-        
-        mysqli_query($con, $query);
-        mysqli_close($con);
-		?>
+// ── Image upload form ─────────────────────────────────────────────────────
+?>
+<form method="post" enctype="multipart/form-data" class="mt-3">
+    <div class="mb-2">
+        <input type="file" class="form-control form-control-sm" name="image" id="image" accept="image/*">
+    </div>
+    <button type="submit" name="insert" id="insert" value="Insert" class="btn btn-info btn-sm">
+        Upload Image
+    </button>
+</form>

+ 81 - 265
dashboard/crop-analysis/plant-test-data/plant-recommendations.php

@@ -1,282 +1,98 @@
-<!doctype html>
-<html lang="en">
+<?php
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
 
-<head>
-	<title>[[*longtitle]] | [[++site_name]]</title>
-	<base href="[[!++site_url]]">
-	<meta charset="[[++modx_charset]]">
-	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
-	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-	<meta name="keywords" content="[[*introtext]]">
-	<meta name="description" content="[[*description]]">
-	<link rel="icon" href="client-assets/images/favicon.ico?v=2" type="image/x-icon"> [[!Profile]]
-	
-	<script type="text/javascript">
-    	window.dataLayer = window.dataLayer || [];
-    
-    	function gtag() {
-    		dataLayer.push(arguments);
-    	}
-    	gtag('js', new Date());
-    	gtag('set', {
-    		'user_id': '[[+modx.user.id]]'
-    	}); // Set the user ID using signed-in user_id.
-    	gtag('config', 'UA-133963301-1');
-	</script>
-	
-	<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" />
-	<link href="client-assets/css/dashboard-2021.css" rel="stylesheet" />
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js" crossorigin="anonymous"></script>
-	<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
-	<script src="client-assets/js/skycons.js"></script>
-	<style>
-    	.btn-append {
-    		color: #495057;
-    		background-color: #e9ecef;
-    		border: 1px solid #ced4da;
-    	}
-    	.footer {
-    		position: absolute;
-    		bottom: 0;
-    		width: 100%;
-    		height: 60px;
-    		line-height: 60px;
-    	}
-	</style>
-</head>
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
 
-<body class="sb-nav-fixed" id="page-top"> 
-    [[!Personalize?
-        &yesChunk=`navHeaderLogged`
-        &noChunk=`navHeader`
-        &allowedGroups=`basicClients,bacicAdmin,companyClients,companyManagers`
-    ]]
-    
-	<div id="layoutSidenav">
-	    <div id="layoutSidenav_nav">
-    		<!-- Sidebar -->
-    		[[Wayfinder?
-    		    &startId=`2` 
-        		&displayStart=`0` 
-        		&startitemTpl=`startitemTpl` 
-        		&selfClass=`show` 
-        		&level=`2`
-        		&outerTpl=`outer`
-        		    &outerClass=`sb-sidenav accordion sb-sidenav-dark`
-        		&rowTpl=`row`
-        		    &rowClass=`nav-link`
-        		&parentRowTpl=`parentRow`
-        		    &parentClass=`nav-link collapsed`
-        		&innerTpl=`inner`
-        		    &innerClass=`collapse`
-        		&innerRowTpl=`secondInner`
-        		    &innerRowClass=`nav-link`
-    		]]
-        </div>
-		
-		<div id="layoutSidenav_content">
-			<main>
+requireLogin();
 
-				<div class="container-fluid px-4">
-				    <h1 class="mt-4">[[*pagetitle]]</h1>
+$pageTitle = 'Plant Recommendations';
+$siteName  = 'Crop Monitor';
 
-					<ol class="breadcrumb mb-4"> [[$dash-breadcrumbs]] </ol>
-					
-					<div class="row">
-					    <div class="container-fluid">
-    <div class="grid-form">
-        <!---->
-        <div class="grid-form1">
-            <h3>Plant Analysis</h3>
-            <p>Variables used in Plant Analysis recommendation programs.</p>
-            <div class="tab-content">
-            <!-- Company Product list here -->    
-            
-            <?php
-error_reporting(E_ALL);
-    ini_set('display_errors', 1);
-    
-    $result    = null;
-    $modx_user = $modx->user->get('id');
-    
-    /* Database connection */
-    //$con = mysqli_connect("localhost", "root", "R3M0T31", "cropmonitor");
-    $con = mysqli_connect("localhost", "cropmonitor", "brvnCcaEYxlPCS3", "cropmonitor");
-    
-    /* Check Database Connection */
-    if (mysqli_connect_errno()) {
-        printf("Connect failed: %s\n", mysqli_connect_error());
-        exit();
-    }
-    
-    /* Image Insert for Plant Identification */
-    if(isset($_POST["insert"]))  
-        {  
-            $file = addslashes(file_get_contents($_FILES["image"]["tmp_name"]));  
-            $query = "INSERT INTO plant_images(name) VALUES ('$file')";  
-            if(mysqli_query($con, $query))  
-                {  
-                    echo '<script>alert("Image saved")</script>';  
-                }  
-        }  
-?>
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
 
-<div class="container-fluid">
-    <div class="row">
-        <div class="form-group col-4">
-            <select id="plant" class="form-control">
-                <option value="" selected="selected">Select Plant Type</option>
-                    <?php
-                        $sql = "SELECT DISTINCT `plant_type` FROM `plant_specifications` WHERE `modx_user_id` = " . $modx_user . " ORDER BY `plant_type` ASC; ";
-                        $resultset = mysqli_query($con, $sql) or die("database error:". mysqli_error($con));
-                        while( $rows = mysqli_fetch_assoc($resultset) ) {
-                    ?>
-                <option value="<?php echo $rows["plant_type"]; ?>" "><?php echo $rows["plant_type"]; ?></option>
-                <?php $plant = $rows["plant_type"]; } ?>
-            </select>
-        </div>
-    </div>
-    
-    
-    <div class="row" id="show_product"></div>
-    
-    
-    
-    <hr>
-    <div class="row">
-        <div class="col">
-            <div class="row">
-                <p>Nutrient Requirements</p>
-            </div>
-            <div class="row">
-                <div class="col border border-success">
-                    <!-- Show Selected Plant Data Script -->
-                    <script type="text/javascript">
-                        $(document).ready(function(){
-                            // code to get all records from table via select box
-                            $("#plant").change(function() {
-                                var plant = $(this).find(":selected").val();
-                                    $.ajax({
-                                        url: '[[~89]]',
-                                        type: "POST",
-                                        data: 'empid='+ plant,
-                                        success: function(data) {
-                                            $('#show_product').html(data);
-                                    }
-                                });
-                            })
-                        });
-                    </script>
-                </div>
-            </div>
-        </div>
-    </div>
-        
-</div>
+// Handle image upload
+if (isset($_POST['insert']) && isset($_FILES['image'])) {
+    $file = file_get_contents($_FILES['image']['tmp_name']);
+    if ($file !== false) {
+        $stmt = $pdo->prepare('INSERT INTO plant_images (name) VALUES (?)');
+        $stmt->execute([$file]);
+    }
+}
 
+// Fetch distinct plant types for this user
+$stmt = $pdo->prepare(
+    'SELECT DISTINCT plant_type FROM plant_specifications WHERE modx_user_id = ? ORDER BY plant_type ASC'
+);
+$stmt->execute([$userId]);
+$plantTypes = $stmt->fetchAll(\PDO::FETCH_COLUMN);
 
+$h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
 
-<!-- Update Values Script -->
-<script type="text/javascript">
-    function showEdit(editableObj) {
-        $(editableObj).css("background", "#97e499");
-    }
-    function updateDatabase(editableObj, column, id) {
-        $(editableObj).css("background", "#FDFDFD");
-        $.ajax({
-            url: "[[~57]]",
-            type: "POST",
-            data: 'column=' + column + '&editval=' + editableObj.innerHTML + '&id=' + id,
-            success: function (data) {
-                $(editableObj).css("background", "white");
-            }
-        });
-    }
-</script>
+include __DIR__ . '/../../../layouts/header.php';
+include __DIR__ . '/../../../layouts/navbar.php';
+?>
 
-<!-- Image Insert Script -->
-<script>  
- $(document).ready(function(){  
-      $('#insert').click(function(){  
-           var image_name = $('#image').val();  
-           if(image_name == '')  
-           {  
-                alert("Please Select Image");  
-                return false;  
-           }  
-           else  
-           {  
-                var extension = $('#image').val().split('.').pop().toLowerCase();  
-                if(jQuery.inArray(extension, ['gif','png','jpg','jpeg']) == -1)  
-                {  
-                     alert('Invalid Image File');  
-                     $('#image').val('');  
-                     return false;  
-                }  
-           }  
-      });  
- });  
- </script>
- ?>
-                
-            </div>
-        </div>
+<div id="layoutSidenav">
+    <div id="layoutSidenav_nav">
+        <?php include __DIR__ . '/../../../layouts/sidebar.php'; ?>
     </div>
-</div>
-					</div>
+    <div id="layoutSidenav_content">
+        <main>
+            <div class="container-fluid px-4">
+                <h1 class="mt-4"><?= $h($pageTitle) ?></h1>
+                <ol class="breadcrumb mb-4">
+                    <li class="breadcrumb-item"><a href="/dashboard/dashboard.php">Dashboard</a></li>
+                    <li class="breadcrumb-item active">Plant Recommendations</li>
+                </ol>
 
-				</div>
+                <div class="row">
+                    <div class="col-12">
+                        <h5>Plant Analysis</h5>
+                        <p class="text-muted">Variables used in Plant Analysis recommendation programs.</p>
 
-			</main>
-			
-			<footer class="py-4 bg-light mt-auto">
-                <div class="container-fluid px-4">
-                    <div class="d-flex align-items-center justify-content-between small">
-                        <div class="text-muted">[[SimpleCopyright? &startYear=`2003`]]. All Rights Reserved <a href="[[~1]]"></a></div>
-                        <div>
-                            <a href="[[~39~]]">Privacy Policy</a>
-                            &middot;
-                            <a href="[[~39~]]">Terms &amp; Conditions</a>
+                        <div class="row mb-3">
+                            <div class="col-md-4">
+                                <select id="plantType" class="form-select">
+                                    <option value="" selected>Select Plant Type</option>
+                                    <?php foreach ($plantTypes as $pt): ?>
+                                        <option value="<?= $h($pt) ?>"><?= $h($pt) ?></option>
+                                    <?php endforeach; ?>
+                                </select>
+                            </div>
                         </div>
+
+                        <div id="plantSpecsDisplay"></div>
+
+                        <hr>
+                        <p class="text-muted small">To add plant specification ranges, please contact your administrator or use the database management tools.</p>
                     </div>
                 </div>
-            </footer>
-            
-		</div>
-
-	</div>
+            </div>
+        </main>
 
-	<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-	<script>
-    	/*!
-            * Start Bootstrap - SB Admin v7.0.3 (https://startbootstrap.com/template/sb-admin)
-            * Copyright 2013-2021 Start Bootstrap
-            * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-sb-admin/blob/master/LICENSE)
-            */
-            // 
-        // Scripts
-        // 
-        
-        window.addEventListener('DOMContentLoaded', event => {
-        
-            // Toggle the side navigation
-            const sidebarToggle = document.body.querySelector('#sidebarToggle');
-            if (sidebarToggle) {
-                // Uncomment Below to persist sidebar toggle between refreshes
-                // if (localStorage.getItem('sb|sidebar-toggle') === 'true') {
-                //     document.body.classList.toggle('sb-sidenav-toggled');
-                // }
-                sidebarToggle.addEventListener('click', event => {
-                    event.preventDefault();
-                    document.body.classList.toggle('sb-sidenav-toggled');
-                    localStorage.setItem('sb|sidebar-toggle', document.body.classList.contains('sb-sidenav-toggled'));
-                });
-            }
-        
-        });
+        <?php include __DIR__ . '/../../../layouts/footer.php'; ?>
+    </div>
+</div>
 
-	</script>
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js" crossorigin="anonymous"></script>
-	<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest" crossorigin="anonymous"></script>
-</body>
-</html>
+<script>
+document.getElementById('plantType').addEventListener('change', function () {
+    var plant = this.value;
+    if (!plant) {
+        document.getElementById('plantSpecsDisplay').innerHTML = '';
+        return;
+    }
+    fetch('/dashboard/crop-analysis/plant-test-data/plant-rec-update.php', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+        body: 'empid=' + encodeURIComponent(plant)
+    })
+    .then(function (r) { return r.text(); })
+    .then(function (html) {
+        document.getElementById('plantSpecsDisplay').innerHTML = html;
+    });
+});
+</script>

+ 65 - 0
dashboard/crop-analysis/plant-test-data/plant-report-save.php

@@ -0,0 +1,65 @@
+<?php
+/**
+ * plant-report-save.php
+ *
+ * AJAX endpoint: auto-saves plant report consultant notes to the reports table.
+ * Called by the auto-save JS in plant-report.php via POST.
+ *
+ * POST params: general_details, recommended_details, foliar_details
+ * GET params:  rid (plant_records.id), rand (plant_records.rand)
+ */
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
+
+if (!isLoggedIn()) {
+    http_response_code(403);
+    echo json_encode(['success' => false, 'message' => 'Unauthorised']);
+    exit;
+}
+
+header('Content-Type: application/json');
+
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+
+$recordId = (int)   ($_GET['rid']  ?? 0);
+$randId   = (float) ($_GET['rand'] ?? 0);
+
+if ($recordId <= 0) {
+    http_response_code(400);
+    echo json_encode(['success' => false, 'message' => 'Missing record ID']);
+    exit;
+}
+
+// Verify the plant record belongs to this user
+$check = $pdo->prepare(
+    'SELECT id FROM plant_records WHERE id = ? AND rand = ? AND modx_user_id = ? LIMIT 1'
+);
+$check->execute([$recordId, $randId, $userId]);
+if (!$check->fetch()) {
+    http_response_code(403);
+    echo json_encode(['success' => false, 'message' => 'Record not found or access denied']);
+    exit;
+}
+
+$data = [
+    'general_details'     => trim($_POST['general_details']     ?? ''),
+    'recommended_details' => trim($_POST['recommended_details'] ?? ''),
+    'foliar_details'      => trim($_POST['foliar_details']      ?? ''),
+];
+
+$comment = json_encode($data, JSON_UNESCAPED_UNICODE);
+
+$stmt = $pdo->prepare('
+    INSERT INTO reports (modx_user_id, record_id, rand, comment, dateTime)
+    VALUES (?, ?, ?, ?, CURDATE())
+    ON DUPLICATE KEY UPDATE comment = VALUES(comment), dateTime = CURDATE()
+');
+$stmt->execute([$userId, $recordId, (int) $randId, $comment]);
+
+echo json_encode(['success' => true, 'saved' => date('H:i:s')]);

+ 181 - 153
dashboard/crop-analysis/plant-test-data/plant-report.php

@@ -1,168 +1,196 @@
+<?php
+/**
+ * plant-report.php
+ *
+ * Displays the plant analysis consultant notes / PDF report.
+ * Accessed via rid + rand URL params.
+ * Notes auto-save to the reports table via plant-report-save.php.
+ */
+
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+requireLogin();
+
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+
+$recordId = (int)   ($_GET['rid']  ?? 0);
+$randId   = (float) ($_GET['rand'] ?? 0);
+
+$row   = null;
+$notes = ['general_details' => '', 'recommended_details' => '', 'foliar_details' => ''];
+
+if ($recordId > 0) {
+    $stmt = $pdo->prepare('SELECT * FROM plant_records WHERE id = ? AND rand = ? LIMIT 1');
+    $stmt->execute([$recordId, $randId]);
+    $row = $stmt->fetch();
+}
+
+if ($row) {
+    $rpt = $pdo->prepare(
+        'SELECT comment FROM reports WHERE record_id = ? AND modx_user_id = ? ORDER BY id DESC LIMIT 1'
+    );
+    $rpt->execute([$recordId, $userId]);
+    $saved = $rpt->fetchColumn();
+    if ($saved) {
+        $decoded = json_decode($saved, true);
+        if (is_array($decoded)) {
+            $notes = array_merge($notes, $decoded);
+        }
+    }
+}
+
+$h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
+?>
 <!doctype html>
 <html lang="en">
-	<head>
-		<title>[[*longtitle]] | [[++site_name]]</title>
-		<base href="[[!++site_url]]" >
-		<meta charset="[[++modx_charset]]" >
-		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" >
-		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
-		<meta name="keywords" content="[[*introtext]]" >
-		<meta name="description" content="[[*description]]" >
-
-		[[!Profile]]
-		
-		[[$dash-header]]
-		
-		<link href="/client-assets/css/dashboard.css" rel="stylesheet" type="text/css" />
-		<script src="https://unpkg.com/gijgo@1.9.11/js/gijgo.min.js" type="text/javascript"></script>
-		<link href="https://unpkg.com/gijgo@1.9.11/css/gijgo.min.css" rel="stylesheet" type="text/css" />
-		<script src="client-assets/js/skycons.js" type="text/javascript"></script>
-    
-        <link href="client-assets/home/css/graphing.css" rel="stylesheet" type="text/css" media="screen" />
-        <link href="client-assets/home/css/alux.min.css" rel="stylesheet" type="text/css" media="screen" />
-        
-        <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" integrity="sha512-YcsIPGdhPK4P/uRW6/sruonlYj+Q7UHWeKfTAkBW+g83NKM+jMJFJ4iAPfSnVp7BKD4dKMHmVSvICUbE/V1sSw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
-
-        <style>
-            @media print {
-                @page {
-                    size: A4 portrait;
-                }
-                @page :left {
-                    margin-left: 0.5cm;
-                }
-                @page :right {
-                    margin-right: 0.5cm;
-                }
-                @page :top {
-                    margin-top: 0cm;
-                }
-                @page :bottom {
-                    margin-bottom: 0cm;
-                }
-            }
-        </style>
-
-    </head>
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Plant Analysis Report | Crop Monitor</title>
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
+    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.css" crossorigin="anonymous" rel="stylesheet">
+    <link href="/client-assets/css/dashboard.css" rel="stylesheet">
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" crossorigin="anonymous"></script>
+    <style>
+        @media print {
+            @page { size: A4 portrait; margin: 0.5cm; }
+            .d-print-none { display: none !important; }
+        }
+        .chart-header     { background: #343a40; color: white; text-align: center; padding: 6px; }
+        .chart-header-sub { background: #6c757d; color: white; text-align: center; padding: 4px; }
+        textarea { width: 100%; min-height: 120px; border: 1px solid #dee2e6; padding: 8px; font-size: 0.9rem; }
+        .save-status { font-size: 0.75rem; color: #6c757d; }
+    </style>
+</head>
 <body>
+<div class="container" id="content">
 
-<div class="container">
-    [[!logoHeader]]
-    [[!plantAnalysisClient]]
+    <?php if (!$row): ?>
+        <div class="alert alert-danger mt-4">Record not found or access denied.</div>
+    <?php else: ?>
 
-    <div class="clearfix"></div>
-    <!-- GRAPH BANNER -->
-    
-    <div class="row">
-        <div class="col graph-header text-center">Plant Analysis Summary</div>
+    <div class="row mb-3">
+        <div class="col-md-3">
+            <img class="img-fluid" src="/client-assets/images/crop-monitor.png" alt="Crop Monitor">
+        </div>
     </div>
 
-    <!-- CHART HEADER  -->
-    <div class="row">
-        <div class="chart-header text-center col">General Comment</div>
-    </div>
-    <div class="row">
-        <form class="col no-pad" action="" method="post" id="general_Details" >
-            <textarea id="general_Details" name="overview">[[!general_Details]]</textarea> <input type="hidden" id="textareaID" value="general_Details" >
-            <div class="pdfHide">
-                <br>
-                <input type="submit" form="general_Details" id="general_Details" class="btn btn-success" name="general_Details" value="Save/Update">
-                <br><br>
-            </div>
-        </form>
-    </div>
-    
-    <div class="row">
-        <div class="chart-header text-center col">Recommended Remedial Program</div>
-    </div>
-    <div class="row">
-        <form class="col no-pad" action="" method="post" id="recommended_Details" >
-            <textarea id="recommended_Details" name="recommended_Details">[[!recommended_Details]]</textarea> <input type="hidden" id="textareaID" value="recommended_Details" >
-            <div class="pdfHide">
-                <br>
-                <input type="submit" form="recommended_Details" id="recommended_Details" class="btn btn-success" name="recommended_Details" value="Save/Update">
-                <br><br>
-            </div>
-        </form>
-    </div>
-    
-    <div class="row">
-        <div class="chart-header text-center col">Foliar Program</div>
+    <div class="d-print-none mb-3 d-flex align-items-center gap-2">
+        <button class="btn btn-success btn-sm downloadPDF">
+            <i class="fas fa-download me-1"></i>Download PDF
+        </button>
+        <span class="save-status" id="saveStatus"></span>
     </div>
-    <div class="row">
-        <form class="col no-pad" action="" method="post" id="foliar_Details" >
-            <textarea id="foliar_Details" name="foliar_Details">[[!foliar_Details]]</textarea> <input type="hidden" id="textareaID" value="foliar_Details" >
-            <div class="pdfHide">
-                <br>
-                <input type="submit" form="foliar_Details" id="foliar_Details" class="btn btn-success" name="foliar_Details" value="Save/Update">
-                <br><br>
-            </div>
-        </form>
+
+    <!-- Client info -->
+    <table class="table table-sm table-bordered mb-3" style="font-size:0.85rem;">
+        <tbody>
+            <tr>
+                <th class="w-25">Client</th>
+                <td><?= $h($row['client_name'] ?? '') ?></td>
+                <th class="w-25">Lab No</th>
+                <td><?= $h($row['lab_no'] ?? '') ?></td>
+            </tr>
+            <tr>
+                <th>Sample ID</th>
+                <td><?= $h($row['sample_id'] ?? '') ?></td>
+                <th>Site ID</th>
+                <td><?= $h($row['site_id'] ?? '') ?></td>
+            </tr>
+            <tr>
+                <th>Crop</th>
+                <td><?= $h($row['crop_type'] ?? '') ?></td>
+                <th>Date Sampled</th>
+                <td><?= $h($row['date_sampled'] ?? '') ?></td>
+            </tr>
+        </tbody>
+    </table>
+
+    <div class="text-center fw-bold h5 mb-3">PLANT ANALYSIS SUMMARY</div>
+
+    <form class="report-form">
+        <!-- General Comment -->
+        <div class="chart-header mb-1">General Comment</div>
+        <div class="mb-3">
+            <textarea id="general_details" name="general_details"><?= $h($notes['general_details']) ?></textarea>
+        </div>
+
+        <!-- Recommended Program -->
+        <div class="chart-header mb-1">Recommended Remedial Program</div>
+        <div class="mb-3">
+            <textarea id="recommended_details" name="recommended_details"><?= $h($notes['recommended_details']) ?></textarea>
+        </div>
+
+        <!-- Foliar Program -->
+        <div class="chart-header mb-1">Foliar Program</div>
+        <div class="mb-3">
+            <textarea id="foliar_details" name="foliar_details"><?= $h($notes['foliar_details']) ?></textarea>
+        </div>
+    </form>
+
+    <div class="mt-4 small text-muted">
+        <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>
+        <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>
+        <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>
+        <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>
+        <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>
     </div>
-    
-    <p><i class="fa fa-envira nav_icon " style="color: green;"></i>It is always an advantage to assess tisue analysis results along with a corresponding soil analysis for more accurate diagnosis of plant nutrient status.</p>
-    
-    <div class="clearfix"></div>
-    
-    <p><i class="fa fa-envira nav_icon " style="color: green;"></i>Trace element levels - manganese, copper and zinc, can all be affeced by fungicide spray residues, giving msleading results, See Comment above.</p>
-    
-    <div class="clearfix"></div>
-    
-    <p><i class="fa fa-envira nav_icon " style="color: green;"></i>Talk to your qualified consultant to make a plan for correction or maintenance of the found nutrient levels.</p>
-    
-    <div class="clearfix"></div>
-    
-    <p style="font-style: italic; font-size: 9px;">Desired ranges indexed from:  CSIRO Plant Anlysis Handbook 2nd Ed.  Hill Laborotories consultants guide.  PIRSA Soil and Plant Analysis.</p>
-    
-    <div class="clearfix"></div>
-    
-    <p style="font-style: italic; font-size: 9px;">Any recommendationa provided by Cropmonitor are advice only We are not pad consultanta and we are not overed to accept responsibiliy for an of our suggestions. As no control can be exercised over storage, handling, mixing application or use, or weather, plant or soil conditions before, during or after application (all of which may affect the preformance of our program), no responsibility for, or liability for any failure in performance, losses, damage or injuries consequential or otherwise, arisiing form such storage mixng application or use will be accepted under any circumstances whatsoever. The buyer assumes all responsibility for the use of any of our products.</p>
-    
-    <div class="clearfix"></div>
 
+    <?php endif; ?>
 </div>
-    
-</div>
 
-    <!-- 
-    <script src="https://cloud.tinymce.com/stable/tinymce.min.js?apiKey=xcotawi18mg1imp8im144buq68h9g3ndd3c9c8215w8qu3ld"></script>
-    <script>
-        tinymce.init({
-            selector: 'textarea',
-            menubar: false,
-            toolbar: 'bold italic  | alignleft aligncenter alignright alignjustify | bullist numlist | removeformat',
-            plugins: 'autosave',
-            autosave_interval: '20s'
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
+<script>
+(function () {
+    var timeoutId;
+    var form = document.querySelector('.report-form');
+    if (!form) return;
+
+    form.querySelectorAll('textarea').forEach(function (el) {
+        el.addEventListener('input', function () {
+            clearTimeout(timeoutId);
+            timeoutId = setTimeout(saveToDB, 1200);
         });
-    </script>
-    -->
-    
-    [[$dash-footer]]
-    
-    <script>
-        //https://github.com/eKoopmans/html2pdf.js
-        $('.downloadPDF').click(function () {
-        	var element = document.getElementById('content'); //document.createElement("body");
-        	element.classList.remove('screen');
-        	element.classList.add('print');
-        	var opt = {
-        		margin:       3,
-        		filename:     'soil-analysis.pdf',
-        		image:        { type: 'jpeg', quality: 1.0 },
-        		html2canvas:  { scale: 2, letterRendering: true, windowWidth: 1024 },  //, windowWidth: 1024
-        		jsPDF:        { orientation: 'portrait', unit: 'mm', format: 'a4', putOnlyUsedFonts: true, floatPrecision: 'smart', }
-        	};
-        	html2pdf()
-        	    .from(element)
-        	    .toPdf()
-        	    .set(opt)
-        	    .save()
-        	    .then(function(){
-        		    element.classList.remove('print');
-        		    element.classList.add('screen');
-        	});
-        	
+    });
+
+    function saveToDB() {
+        var data = new FormData(form);
+        var params = new URLSearchParams();
+        data.forEach(function (v, k) { params.append(k, v); });
+
+        document.getElementById('saveStatus').textContent = 'Saving…';
+
+        fetch('/dashboard/crop-analysis/plant-test-data/plant-report-save.php?rid=<?= (int)$recordId ?>&rand=<?= (float)$randId ?>', {
+            method: 'POST',
+            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+            body: params.toString()
+        })
+        .then(function (r) { return r.json(); })
+        .then(function (d) {
+            document.getElementById('saveStatus').textContent = d.success ? ('Saved ' + d.saved) : 'Save failed';
+        })
+        .catch(function () {
+            document.getElementById('saveStatus').textContent = 'Save error';
         });
-    </script>
+    }
+})();
+
+document.querySelector('.downloadPDF') && document.querySelector('.downloadPDF').addEventListener('click', function () {
+    var opt = {
+        margin: 3,
+        filename: 'plant-report.pdf',
+        image: { type: 'jpeg', quality: 1.0 },
+        html2canvas: { scale: 2, letterRendering: true, windowWidth: 1024 },
+        jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' }
+    };
+    html2pdf().from(document.getElementById('content')).set(opt).save();
+});
+</script>
 </body>
-</html>
+</html>

+ 229 - 0
dashboard/crop-analysis/plant-test-data/plant-test-data.php

@@ -0,0 +1,229 @@
+<?php
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
+require_once __DIR__ . '/../../../lib/csrf.php';
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+requireLogin();
+
+$pageTitle = 'Plant Tissue Analysis';
+$siteName  = 'Crop Monitor';
+
+include __DIR__ . '/../../../layouts/header.php';
+include __DIR__ . '/../../../layouts/navbar.php';
+?>
+
+<div id="layoutSidenav">
+    <div id="layoutSidenav_nav">
+        <?php include __DIR__ . '/../../../layouts/sidebar.php'; ?>
+    </div>
+    <div id="layoutSidenav_content">
+        <main>
+            <div class="container-fluid px-4">
+                <h1 class="mt-4"><?= htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8') ?></h1>
+                <ol class="breadcrumb mb-4">
+                    <li class="breadcrumb-item"><a href="/dashboard/dashboard.php">Dashboard</a></li>
+                    <li class="breadcrumb-item active">Plant Test Data</li>
+                </ol>
+
+                <div class="row">
+                    <div class="container">
+                        <h3>Plant Tissue Test Details</h3>
+                        <span class="text-danger small">* required fields.</span>
+
+                        <?php include __DIR__ . '/../../../components/clientDetailsForm.php'; ?>
+
+                        <form method="post" action="/controllers/plantTestSubmit.php" id="PlantcsvForm" class="needs-validation" novalidate>
+                            <input type="hidden" name="csrf_token" value="<?= htmlspecialchars(generateCsrfToken(), ENT_QUOTES, 'UTF-8') ?>">
+
+                            <hr>
+                            <div class="row">
+                                <div class="col">
+                                    <small class="form-text text-muted">Lab No</small>
+                                    <input type="text" class="form-control form-control-sm" name="lab_no" id="lab_no" placeholder="Lab No">
+                                </div>
+                                <div class="col">
+                                    <small class="form-text text-muted">Batch No</small>
+                                    <input type="text" class="form-control form-control-sm" name="batch_no" id="batch_no" placeholder="Batch No">
+                                </div>
+                                <div class="col">
+                                    <small class="form-text text-muted">Date Sampled</small>
+                                    <input type="date" class="form-control form-control-sm" name="date_sampled" id="date_sampled">
+                                </div>
+                                <div class="col">
+                                    <small class="form-text text-muted">Sample ID <span class="text-danger">*</span></small>
+                                    <input type="text" class="form-control form-control-sm" name="sample_id" id="sample_id" placeholder="Sample ID" required>
+                                </div>
+                                <div class="col">
+                                    <small class="form-text text-muted">Site ID</small>
+                                    <input type="text" class="form-control form-control-sm" name="site_id" id="site_id" placeholder="Paddock ID">
+                                </div>
+                                <div class="col">
+                                    <small class="form-text text-muted">Crop Type</small>
+                                    <input type="text" class="form-control form-control-sm" name="crop_type" id="crop_type" placeholder="Crop Type">
+                                </div>
+                            </div>
+
+                            <hr>
+                            <label><b>Major Elements (% dry weight)</b></label>
+                            <div class="row mt-2">
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Nitrogen (N) %</small>
+                                    <input type="text" class="form-control form-control-sm" name="n" id="n" placeholder="0.00">
+                                </div>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Phosphorus (P) %</small>
+                                    <input type="text" class="form-control form-control-sm" name="p" id="p" placeholder="0.00">
+                                </div>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Potassium (K) %</small>
+                                    <input type="text" class="form-control form-control-sm" name="k" id="k" placeholder="0.00">
+                                </div>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Sulphur (S) %</small>
+                                    <input type="text" class="form-control form-control-sm" name="s" id="s" placeholder="0.00">
+                                </div>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Magnesium (Mg) %</small>
+                                    <input type="text" class="form-control form-control-sm" name="mg" id="mg" placeholder="0.00">
+                                </div>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Calcium (Ca) %</small>
+                                    <input type="text" class="form-control form-control-sm" name="ca" id="ca" placeholder="0.00">
+                                </div>
+                            </div>
+                            <div class="row mt-2">
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Sodium (Na) %</small>
+                                    <input type="text" class="form-control form-control-sm" name="na" id="na" placeholder="0.00">
+                                </div>
+                            </div>
+
+                            <hr>
+                            <label><b>Trace Elements (ppm)</b></label>
+                            <div class="row mt-2">
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Iron (Fe) ppm</small>
+                                    <input type="text" class="form-control form-control-sm" name="fe" id="fe" placeholder="0.0">
+                                </div>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Manganese (Mn) ppm</small>
+                                    <input type="text" class="form-control form-control-sm" name="mn" id="mn" placeholder="0.0">
+                                </div>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Zinc (Zn) ppm</small>
+                                    <input type="text" class="form-control form-control-sm" name="zn" id="zn" placeholder="0.0">
+                                </div>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Copper (Cu) ppm</small>
+                                    <input type="text" class="form-control form-control-sm" name="cu" id="cu" placeholder="0.0">
+                                </div>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Boron (B) ppm</small>
+                                    <input type="text" class="form-control form-control-sm" name="b" id="b" placeholder="0.0">
+                                </div>
+                            </div>
+
+                            <hr>
+                            <label><b>Other Elements (ppm)</b></label>
+                            <div class="row mt-2">
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Molybdenum (Mo) ppm</small>
+                                    <input type="text" class="form-control form-control-sm" name="m" id="m" placeholder="0.00">
+                                </div>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Cobalt (Co) ppm</small>
+                                    <input type="text" class="form-control form-control-sm" name="co" id="co" placeholder="0.00">
+                                </div>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Selenium (Se) ppm</small>
+                                    <input type="text" class="form-control form-control-sm" name="se" id="se" placeholder="0.00">
+                                </div>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Chloride (Cl) ppm</small>
+                                    <input type="text" class="form-control form-control-sm" name="cl" id="cl" placeholder="0.00">
+                                </div>
+                            </div>
+
+                            <hr>
+                            <button form="PlantcsvForm" type="submit" name="PlantcsvForm" class="btn btn-success">
+                                Submit
+                            </button>
+                        </form>
+
+                        <?php include __DIR__ . '/../../../components/newClientModal.php'; ?>
+
+                        <hr>
+                        <div class="card mt-3">
+                            <div class="card-body">
+                                <h5 class="card-title">CSV Upload / Download</h5>
+                                <p class="card-text text-muted">Download a CSV template or upload a filled CSV to pre-populate the form.</p>
+                                <div class="input-group mt-2">
+                                    <input type="file" class="form-control form-control-sm" id="csvUpload" accept=".csv">
+                                    <button class="btn btn-success btn-sm" type="button" id="csvDownload">Download Template</button>
+                                </div>
+                            </div>
+                        </div>
+
+                    </div>
+                </div>
+            </div>
+        </main>
+
+        <?php include __DIR__ . '/../../../layouts/footer.php'; ?>
+    </div>
+</div>
+
+<script>
+(function () {
+    'use strict';
+    window.addEventListener('load', function () {
+        var forms = document.getElementsByClassName('needs-validation');
+        Array.prototype.forEach.call(forms, function (form) {
+            form.addEventListener('submit', function (event) {
+                if (!form.checkValidity()) {
+                    event.preventDefault();
+                    event.stopPropagation();
+                }
+                form.classList.add('was-validated');
+            }, false);
+        });
+    }, false);
+})();
+
+document.getElementById('csvUpload').addEventListener('change', function (e) {
+    var file = e.target.files[0];
+    if (!file) return;
+    var reader = new FileReader();
+    reader.onload = function (ev) {
+        var rows = ev.target.result.split('\n');
+        rows.forEach(function (row, i) {
+            if (i === 0) return;
+            var cols = row.split(',');
+            if (cols.length >= 2) {
+                var el = document.getElementById(cols[0].trim());
+                if (el) el.value = cols[1].trim();
+            }
+        });
+    };
+    reader.readAsText(file);
+});
+
+document.getElementById('csvDownload').addEventListener('click', function () {
+    var fields = ['lab_no','batch_no','date_sampled','sample_id','site_id','crop_type',
+                  'n','p','k','s','mg','ca','na','fe','mn','zn','cu','b','m','co','se','cl'];
+    var rows = [['id','value']];
+    fields.forEach(function (id) {
+        var el = document.getElementById(id);
+        rows.push([id, el ? el.value : '']);
+    });
+    var csv = rows.map(function (r) { return r.join(','); }).join('\n');
+    var a = document.createElement('a');
+    a.href = 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv);
+    a.download = 'plant-tissue-analysis.csv';
+    a.click();
+});
+</script>

+ 14 - 111
dashboard/crop-analysis/plant-test-data/plant-test-report.php

@@ -1,115 +1,18 @@
-<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js" integrity="sha256-gfQwA6PlkZsLqWu4bU4hXPrbTqzixm0B5MdvBLI+Oas=" crossorigin="anonymous"></script>
+<?php
+/**
+ * plant-test-report.php
+ *
+ * This was a modX resource body fragment.
+ * Redirects to the migrated plant test data entry page.
+ */
 
-<div class="grid-form1">
-<h3 id="forms-example" class="">[[*longtitle]]</h3>
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
 
-<span class="error">* required fields.</span>
+require_once __DIR__ . '/../../../lib/auth.php';
 
-[[!clientDetailsFORM]]
+requireLogin();
 
-<form method="post" action="#" id="PlantcsvForm" class="needs-validation" novalidate > <!-- ~[*id*]~] [[~28~]] -->
-
-    [[!Personalize?
-        &yesChunk=`analysisLogged_Clientdetails`
-        &noChunk=`analysis_Clientdetails`
-    ]]
-    
-    <hr>
-
-    [[$plantAnalysisForm]]
-	
- <button form="PlantcsvForm" type="submit" name="PlantcsvForm" class="btn btn-success">Submit</button>
-</form>
-
-[[!plantformSubmit]]
-
-[[!newClientDetails]]
-
-<hr>
-
-<div class="card">
-    <div class="card-body">
-        <h5 class="card-title">Excel/CSV Upload</h5>
-        <p class="card-text">Download a csv of this form for easy filling or upload a filled form to pre-populate.</p>
-        <div class="input-group mt-3">
-            <div class="custom-file">
-                <input type="file" class="custom-file-input" id="upload">
-                <label class="custom-file-label border-success" for="upload">Choose file</label>
-            </div>
-            <div class="input-group-append">
-                <button class="btn btn-success" type="button" id="download">Download</button>
-            </div>
-        </div>
-    </div>
-</div>
-
-</div>
-
-
-<!-- ************************ Download Form as CSV ************************ -->
-<script>
-    document.getElementById("upload").addEventListener("change", upload, false);
-    document.getElementById("download").addEventListener("click", download, false);
-    
-    function upload(e) {
-        var data = null;
-        var file = e.target.files[0];
-        var reader = new FileReader();
-        reader.readAsText(file);
-        reader.onload = function (event) {
-            var csvData = event.target.result;
-            var parsedCSV = d3.csv.parseRows(csvData);
-            parsedCSV.forEach(function (d, i) {
-                if (i == 0) return true; // skip the header
-                document.getElementById(d[0]).value = d[1];
-            });
-        }
-    }
-    
-    function download(e) {
-        data = [ ["id","value"]];
-        var f = d3.selectAll("#csvForm input, select")[0];
-        f.forEach(function(d,i){
-          	data.push([d.id, d.value]);
-        });
-        console.log(data);
-        var csvContent = "data:text/csv;charset=utf-8,";
-        data.forEach(function (d, i) {
-            dataString = d.join(",");
-            csvContent += i < data.length ? dataString + "\n" : dataString;
-        });
-        var url = window.location.pathname;
-        var filename = url.substring(url.lastIndexOf('/')+1);
-        var fname = filename.split(".")[0];
-        var today = new Date();
-        var date = today.getDate()+''+(today.getMonth()+1)+''+today.getFullYear();
-        var csvname = date+''+fname+"-plant.csv";
-    
-        var encodedUri = encodeURI(csvContent);
-        var link = document.createElement("a");
-        link.setAttribute("href", encodedUri);
-        link.setAttribute("download", csvname);
-        link.click();
-    }
-</script>
-<script type="text/javascript">
-    // JavaScript for disabling form submission if there are invalid fields
-    (function() {
-      'use strict';
-      window.addEventListener('load', function() {
-        // Fetch all the forms we want to apply custom Bootstrap validation styles to
-        var forms = document.getElementsByClassName('needs-validation');
-        // Loop over them and prevent submission
-        var validation = Array.prototype.filter.call(forms, function(form) {
-          form.addEventListener('submit', function(event) {
-            if (form.checkValidity() === false) {
-              event.preventDefault();
-              event.stopPropagation();
-            }
-            form.classList.add('was-validated');
-          }, false);
-        });
-      }, false);
-    })();
-    
-</script>
+header('Location: /dashboard/crop-analysis/plant-test-data/plant-test-data.php');
+exit;

+ 226 - 165
dashboard/crop-analysis/soil-test-data/soil-analysis-bs.php

@@ -1,181 +1,242 @@
+<?php
+/**
+ * soil-analysis-bs.php
+ *
+ * Displays soil analysis ratios and key calculated values for a soil record.
+ * Standalone PDF-printable page. Accessed via rid + rand URL params.
+ */
+
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+requireLogin();
+
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+
+$recordId = (int)   ($_GET['rid']  ?? 0);
+$randId   = (float) ($_GET['rand'] ?? 0);
+
+$row = null;
+if ($recordId > 0) {
+    $stmt = $pdo->prepare('SELECT * FROM soil_records WHERE id = ? AND rand = ? LIMIT 1');
+    $stmt->execute([$recordId, $randId]);
+    $row = $stmt->fetch();
+}
+
+$h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
+
+/**
+ * Render a ratio row: element1 / element2, with a simple status bar.
+ * Returns 3 <td> cells: Deficit | Ideal | High
+ */
+function ratioBar(float $a, float $b, float $idealMin, float $idealMax): string {
+    if ($b <= 0) return '<td colspan="3" class="text-muted text-center small">N/A</td>';
+    $ratio = $a / $b;
+    if ($idealMax <= 0) return '<td colspan="3" class="text-center small">' . number_format($ratio, 2) . ':1</td>';
+
+    if ($ratio < $idealMin) {
+        return '<td class="text-center"><div class="progress"><div class="progress-bar bg-danger" style="width:100%"></div></div></td>'
+             . '<td></td><td></td>';
+    } elseif ($ratio > $idealMax) {
+        return '<td></td><td></td>'
+             . '<td class="text-center"><div class="progress"><div class="progress-bar bg-warning" style="width:100%"></div></div></td>';
+    } else {
+        $range = $idealMax - $idealMin;
+        $pct   = $range > 0 ? min(100, max(0, ($ratio - $idealMin) / $range * 100)) : 50;
+        return '<td></td>'
+             . '<td class="text-center"><div class="progress"><div class="progress-bar bg-success" style="width:' . round($pct) . '%"></div></div></td>'
+             . '<td></td>';
+    }
+}
+?>
 <!doctype html>
 <html lang="en">
-	<head>
-		<title>[[*longtitle]] | [[++site_name]]</title>
-		<base href="[[!++site_url]]" >
-		<meta charset="[[++modx_charset]]" >
-		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" >
-		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
-		<meta name="keywords" content="[[*introtext]]" >
-		<meta name="description" content="[[*description]]" >
-
-		[[!Profile]]
-		
-		[[$dash-header]]
-		
-		<link href="/client-assets/css/dashboard.css" rel="stylesheet" type="text/css" />
-		<script src="https://unpkg.com/gijgo@1.9.11/js/gijgo.min.js" type="text/javascript"></script>
-		<link href="https://unpkg.com/gijgo@1.9.11/css/gijgo.min.css" rel="stylesheet" type="text/css" />
-		<script src="client-assets/js/skycons.js" type="text/javascript"></script>
-    
-        <link href="client-assets/home/css/graphing.css" rel="stylesheet" type="text/css" media="screen" />
-        <link href="client-assets/home/css/alux.min.css" rel="stylesheet" type="text/css" media="screen" />
-        
-        <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" integrity="sha512-YcsIPGdhPK4P/uRW6/sruonlYj+Q7UHWeKfTAkBW+g83NKM+jMJFJ4iAPfSnVp7BKD4dKMHmVSvICUbE/V1sSw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
-
-        <style>
-            @media print {
-                @page {
-                    size: A4 portrait;
-                }
-                @page :left {
-                    margin-left: 0.5cm;
-                }
-                @page :right {
-                    margin-right: 0.5cm;
-                }
-                @page :top {
-                    margin-top: 0cm;
-                }
-                @page :bottom {
-                    margin-bottom: 0cm;
-                }
-            }
-        </style>
-
-    </head>
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Soil Analysis Ratios | Crop Monitor</title>
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
+    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.css" crossorigin="anonymous" rel="stylesheet">
+    <link href="/client-assets/css/dashboard.css" rel="stylesheet">
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" crossorigin="anonymous"></script>
+    <style>
+        @media print {
+            @page { size: A4 portrait; margin: 0.5cm; }
+            .d-print-none { display: none !important; }
+        }
+        .progress { border-radius: 0 !important; }
+        table.chart { width: 100%; border-collapse: collapse; }
+        table.chart th, table.chart td { padding: 3px 6px; font-size: 0.85rem; }
+        .chart-header th { background: #343a40; color: white; }
+        .chart-header-sub th { background: #6c757d; color: white; }
+        .lightblue { background: #cce5ff !important; color: #004085 !important; }
+        .stripe-1  { background: #f8f9fa; }
+        .border-left  { border-left: 1px solid #dee2e6; }
+        .border-right { border-right: 1px solid #dee2e6; }
+        .border-bottom { border-bottom: 1px solid #dee2e6; }
+        .border-top { border-top: 1px solid #dee2e6; }
+    </style>
+</head>
 <body>
+<div class="container" id="content">
 
-<div class="container pagebreak">
-    <div class="row">
-        [[!logoHeader]]
+    <?php if (!$row): ?>
+        <div class="alert alert-danger mt-4">Record not found or access denied.</div>
+    <?php else: ?>
+
+    <div class="row mb-3">
+        <div class="col-md-3">
+            <img class="img-fluid" src="/client-assets/images/crop-monitor.png" alt="Crop Monitor">
+        </div>
     </div>
-    
-    <div class="row">
-        <div class="col-md-12 text-center font-weight-bold h4" >ANALYSIS RESULTS</div>
+
+    <div class="d-print-none mb-3">
+        <button class="btn btn-success btn-sm downloadPDF">
+            <i class="fas fa-download me-1"></i>Download PDF
+        </button>
     </div>
-    
-    <hr class="p-1 m-1">
-    
-    <div class="row p-3">
-        <div class="col-md-6 border ">
-            <div class="row h4 text-center">Base Saturation</div>
-            <div class="row">Body</div>
-        </div>
-        <div class="col-md-6 border ">
-            <div class="row h4 text-center">Header</div>
-            <div class="row">Body</div>
-        </div>
+
+    <div class="row">
+        <div class="col-md-12 text-center fw-bold h4">ANALYSIS RESULTS — RATIOS</div>
     </div>
-    
+
     <hr class="p-1 m-1">
-    
+
     <table class="chart">
         <tbody>
-          <tr class="chart-header">
-            <th colspan=3 class="text-center col-md-6 border-left border-right border-top">ELEMENT</th>
-            <th colspan=3 class="text-center col-md-6 border-right border-top">STATUS</th>
-          </tr>
-          
-          <tr class="chart-header-sub">
-            <th class="text-center col-18 border-left"></th>
-            <th class="text-center col-15">DESIRED</th>
-            <th class="text-center col-15">FOUND</th>
-            <th class="text-center col-16 stripe-1">LIGHT</th>
-            <th class="text-center col-16 stripe-1">MEDIUM</th>
-            <th class="text-center col-16 border-right stripe-1">HEAVY</th>
-          </tr>
-          
-          <tr>
-            <td class="border-left"></td>
-            <td class="border-left"></td>
-            <td class="border-left nutrient-balance"></td>
-            <td class="border-left"></td>
-            <td class="border-left"></td>
-            <td class="border-left border-right"></td>
-          </tr>
-          
-          <tr class="chart-header-sub">
-            <th colspan=3 class="col-16 border-left text-center lightblue">RATIOS</th>
-            <th class="text-center col-16 stripe-1"></th>
-            <th class="text-center col-16 stripe-1"></th>
-            <th class="text-center col-16 border-right stripe-1"></th>
-          </tr>
-          
-          
-          <!-- Ca : Mg -->
-          [[!soilAnalysisRatio? &element=`ca_mehlick3` &elementTwo=`mg_mehlick3` &sbl=`` &rec=`ca_mg_ratio` &nutrient=`Ca:Mg Ratio` &min=`` &max=`` &text=`y` &rec_text=`r` &decimal=`1` &graph=lightblueGraph]]
-          <!-- Mg : K -->
-          [[!soilAnalysisRatio? &element=`mg_mehlick3` &elementTwo=`k_mehlick3` &sbl=`` &rec=`c_n_ratio` &nutrient=`Mg:K Ratio `&type=`:1` &min=`` &max=`` &text=`y` &rec_text=`r` &decimal=`1` &graph=lightblueGraph]]
-          <!-- K : Na -->
-          [[!soilAnalysisRatio? &element=`k_mehlick3` &elementTwo=`na_mehlick3` &sbl=`` &rec=`c_n_ratio` &nutrient=`K:Na Ratio `&type=`:1` &min=`` &max=`` &text=`y` &rec_text=`r` &decimal=`1` &graph=lightblueGraph]]
-          
-          <!-- P : S -->
-          [[!soilAnalysisRatio? &element=`k_mehlick3` &elementTwo=`na_mehlick3` &sbl=`` &rec=`c_n_ratio` &nutrient=`P:S Ratio `&type=`:1` &min=`` &max=`` &text=`y` &rec_text=`r` &decimal=`1` &graph=lightblueGraph]]
-          <!-- P : Zn -->
-          [[!soilAnalysisRatio? &element=`k_mehlick3` &elementTwo=`na_mehlick3` &sbl=`` &rec=`c_n_ratio` &nutrient=`P:Zn Ratio `&type=`:1` &min=`` &max=`` &text=`y` &rec_text=`r` &decimal=`1` &graph=lightblueGraph]]
-          <!-- Fe : Mn -->
-          [[!soilAnalysisRatio? &element=`k_mehlick3` &elementTwo=`na_mehlick3` &sbl=`` &rec=`c_n_ratio` &nutrient=`Fe:Mn Ratio `&type=`:1` &min=`` &max=`` &text=`y` &rec_text=`r` &decimal=`1` &graph=lightblueGraph]]
-          <!-- Ca : Mg -->
-          [[!soilAnalysisRatio? &element=`ocarbon` &elementTwo=`NO3_N` &sbl=`` &rec=`c_n_ratio` &nutrient=`C:N Ratio `&type=`:1` &min=`` &max=`` &text=`y` &rec_text=`r` &decimal=`1` &graph=lightblueGraph]]
-          
-          [[!soilAnalysisCalcs? &element=`NH3_N` &sbl=`` &nutrient=`Total Nitrogen` &type=`%` &min=`` &max=`` &text=`c` &rec_text=`r` &decimal=`1` &graph=lightblueGraph]]
-          [[!soilAnalysisCalcs? &element=`ocarbon` &sbl=`` &nutrient=`Total Carbon` &type=`%` &min=`` &max=`` &text=`c` &rec_text=`r` &decimal=`1` &graph=lightblueGraph]]
-          
-          
-          <tr>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left nutrient-balance"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left border-right"></td>
-          </tr>
+            <tr class="chart-header">
+                <th colspan="3" class="text-center border-left border-right border-top">ELEMENT</th>
+                <th colspan="3" class="text-center border-right border-top">STATUS</th>
+            </tr>
+            <tr class="chart-header-sub">
+                <th class="text-center border-bottom border-left">Ratio</th>
+                <th class="text-center border-bottom">Ideal Range</th>
+                <th class="text-center border-bottom">Found</th>
+                <th class="text-center stripe-1">Deficit</th>
+                <th class="text-center stripe-1">Ideal</th>
+                <th class="text-center border-right stripe-1">High</th>
+            </tr>
+
+            <tr>
+                <td class="border-left" colspan="6"></td>
+            </tr>
+
+            <tr class="chart-header-sub">
+                <th colspan="3" class="border-left text-center lightblue">RATIOS</th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left border-right stripe-1"></th>
+            </tr>
+
+            <?php
+            // Ca:Mg  ideal 2:1 – 5:1
+            $ca = (float)($row['ca_mehlick3'] ?? 0);
+            $mg = (float)($row['mg_mehlick3'] ?? 0);
+            $k  = (float)($row['k_mehlick3']  ?? 0);
+            $na = (float)($row['na_mehlick3'] ?? 0);
+            $p  = (float)($row['p_mehlick']   ?? 0);
+            $s  = (float)($row['s_morgan']    ?? 0);
+            $zn = (float)($row['zn_dtpa']     ?? 0);
+            $fe = (float)($row['fe_dtpa']     ?? 0);
+            $mn = (float)($row['mn_dtpa']     ?? 0);
+            $c  = (float)($row['ocarbon']     ?? 0);
+            $n  = (float)($row['NO3_N']       ?? 0);
+
+            $ratios = [
+                ['Ca:Mg Ratio',  $ca, $mg,  2.0,  5.0,  'Ca / Mg'],
+                ['Mg:K Ratio',   $mg, $k,   2.0,  6.0,  'Mg / K'],
+                ['K:Na Ratio',   $k,  $na,  5.0, 20.0,  'K / Na'],
+                ['P:S Ratio',    $p,  $s,   1.0,  4.0,  'P / S'],
+                ['P:Zn Ratio',   $p,  $zn,  8.0, 16.0,  'P / Zn'],
+                ['Fe:Mn Ratio',  $fe, $mn,  1.5,  3.5,  'Fe / Mn'],
+                ['C:N Ratio',    $c,  $n,   8.0, 15.0,  'C / N'],
+            ];
+
+            foreach ($ratios as [$label, $numerator, $denominator, $idealMin, $idealMax, $formula]):
+                $ratio    = ($denominator > 0) ? $numerator / $denominator : 0;
+                $idealStr = number_format($idealMin, 1) . ':1 – ' . number_format($idealMax, 1) . ':1';
+                $foundStr = ($denominator > 0) ? number_format($ratio, 2) . ':1' : '—';
+            ?>
+            <tr>
+                <td class="border-left"><?= $h($label) ?></td>
+                <td><?= $h($idealStr) ?></td>
+                <td><?= $h($foundStr) ?></td>
+                <?= ratioBar($numerator, $denominator, $idealMin, $idealMax) ?>
+            </tr>
+            <?php endforeach; ?>
+
+            <tr>
+                <td class="border-left" colspan="6"></td>
+            </tr>
+
+            <tr class="chart-header-sub">
+                <th colspan="3" class="border-left text-center lightblue">KEY VALUES</th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left border-right stripe-1"></th>
+            </tr>
+
+            <?php
+            $keyValues = [
+                ['Total Nitrogen (NH3-N)', (float)($row['NH3_N']   ?? 0), '%', 0.15, 0.40],
+                ['Total Carbon (OC)',      (float)($row['ocarbon']  ?? 0), '%', 1.5,  4.0],
+            ];
+            foreach ($keyValues as [$label, $found, $unit, $min, $max]):
+                $pct     = ($max > $min && $found > 0) ? min(100, max(0, ($found - $min) / ($max - $min) * 100)) : 0;
+                $desired = number_format($min, 2) . '–' . number_format($max, 2);
+                if ($found < $min) {
+                    $bar = '<td class="text-center"><div class="progress"><div class="progress-bar bg-danger" style="width:100%"></div></div></td><td></td><td></td>';
+                } elseif ($found > $max) {
+                    $bar = '<td></td><td></td><td class="text-center"><div class="progress"><div class="progress-bar bg-warning" style="width:100%"></div></div></td>';
+                } else {
+                    $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>';
+                }
+            ?>
+            <tr>
+                <td class="border-left"><?= $h($label) ?> (<?= $h($unit) ?>)</td>
+                <td><?= $h($desired) ?></td>
+                <td><?= $found > 0 ? number_format($found, 3) : '—' ?></td>
+                <?= $bar ?>
+            </tr>
+            <?php endforeach; ?>
+
+            <tr>
+                <td class="border-bottom border-left"></td>
+                <td class="border-bottom"></td>
+                <td class="border-bottom"></td>
+                <td class="border-bottom"></td>
+                <td class="border-bottom"></td>
+                <td class="border-bottom border-right"></td>
+            </tr>
+
         </tbody>
     </table>
-</div>
 
+    <div class="mt-4 small text-muted">
+        <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>
+        <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>
+    </div>
+
+    <?php endif; ?>
 </div>
 
-    <!-- 
-    <script src="https://cloud.tinymce.com/stable/tinymce.min.js?apiKey=xcotawi18mg1imp8im144buq68h9g3ndd3c9c8215w8qu3ld"></script>
-    <script>
-        tinymce.init({
-            selector: 'textarea',
-            menubar: false,
-            toolbar: 'bold italic  | alignleft aligncenter alignright alignjustify | bullist numlist | removeformat',
-            plugins: 'autosave',
-            autosave_interval: '20s'
-        });
-    </script>
-    -->
-    
-    [[$dash-footer]]
-    
-    <script>
-        //https://github.com/eKoopmans/html2pdf.js
-        $('.downloadPDF').click(function () {
-        	var element = document.getElementById('content'); //document.createElement("body");
-        	element.classList.remove('screen');
-        	element.classList.add('print');
-        	var opt = {
-        		margin:       3,
-        		filename:     'soil-analysis.pdf',
-        		image:        { type: 'jpeg', quality: 1.0 },
-        		html2canvas:  { scale: 2, letterRendering: true, windowWidth: 1024 },  //, windowWidth: 1024
-        		jsPDF:        { orientation: 'portrait', unit: 'mm', format: 'a4', putOnlyUsedFonts: true, floatPrecision: 'smart', }
-        	};
-        	html2pdf()
-        	    .from(element)
-        	    .toPdf()
-        	    .set(opt)
-        	    .save()
-        	    .then(function(){
-        		    element.classList.remove('print');
-        		    element.classList.add('screen');
-        	});
-        	
-        });
-    </script>
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
+<script>
+document.querySelector('.downloadPDF') && document.querySelector('.downloadPDF').addEventListener('click', function () {
+    var opt = {
+        margin: 3,
+        filename: 'soil-analysis-ratios.pdf',
+        image: { type: 'jpeg', quality: 1.0 },
+        html2canvas: { scale: 2, letterRendering: true, windowWidth: 1024 },
+        jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' }
+    };
+    html2pdf().from(document.getElementById('content')).set(opt).save();
+});
+</script>
 </body>
-</html>
+</html>

+ 27 - 120
dashboard/crop-analysis/soil-test-data/soil-analysis-full-report.php

@@ -1,120 +1,27 @@
-<!doctype html>
-<html lang="en">
-	<head>
-		<title>[[*longtitle]] | [[++site_name]]</title>
-		<base href="[[!++site_url]]" >
-		<meta charset="[[++modx_charset]]" >
-		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" >
-		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
-		<meta name="keywords" content="[[*introtext]]" >
-		<meta name="description" content="[[*description]]" >
-
-		[[!Profile]]
-		
-		 <link rel="icon" href="client-assets/images/favicon.ico?v=2" type="image/x-icon" >
- 
- <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
-
-<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" rel="stylesheet" type="text/css" />
-<link href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css" rel="stylesheet" type="text/css" />
-
-<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.css" integrity="sha256-PF6MatZtiJ8/c9O9HQ8uSUXr++R9KBYu4gbNG5511WE=" crossorigin="anonymous" rel="stylesheet" type="text/css"  />
-
-<link type="text/css" href="/client-assets/weather-icons/css/weather-icons.min.css?version=1.16" rel="stylesheet" type="text/css" />
-<link href="https://cdnjs.cloudflare.com/ajax/libs/magnific-popup.js/1.1.0/magnific-popup.css" rel="stylesheet" type="text/css" />
-
-<script type="text/javascript" src="https://use.fontawesome.com/1e2844bb90.js"></script>
-		
-		<link href="client-assets/css/dashboard.css" rel="stylesheet" type="text/css" />
-		<script src="https://unpkg.com/gijgo@1.9.11/js/gijgo.min.js" type="text/javascript"></script>
-		<link href="https://unpkg.com/gijgo@1.9.11/css/gijgo.min.css" rel="stylesheet" type="text/css" />
-		<script src="client-assets/js/skycons.js" type="text/javascript"></script>
-    
-        <link href="client-assets/home/css/graphing.css" rel="stylesheet" type="text/css" media="screen" />
-        <link href="client-assets/home/css/alux.min.css" rel="stylesheet" type="text/css" media="screen" />
-        
-        <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" integrity="sha512-YcsIPGdhPK4P/uRW6/sruonlYj+Q7UHWeKfTAkBW+g83NKM+jMJFJ4iAPfSnVp7BKD4dKMHmVSvICUbE/V1sSw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
-
-        <style>
-            @media print {
-                @page {
-                    size: A4 portrait;
-                }
-                @page :left {
-                    margin-left: 0.5cm;
-                }
-                @page :right {
-                    margin-right: 0.5cm;
-                }
-                @page :top {
-                    margin-top: 0cm;
-                }
-                @page :bottom {
-                    margin-bottom: 0cm;
-                }
-            }
-        </style>
-
-    </head>
-<body>
-
-<!-- Get Front Page -->
-
-
-<!-- Get Graph 
-[[!getResources? &resources=`32` &tpl=`graph-page` &includeContent=1]] -->
-
-<? include_once soil-analysis.php; ?>
-
-<p></p>
-<!-- Get Report -->
-[[!getResources? &resources=`41` &tpl=@INLINE [[+content]] &includeContent=`1`]]  -->
-
-<? include_once soil-report.php; ?>
-
-<!-- jQuery first, then Popper.js, then Bootstrap JS -->
-<script type="text/javascript" src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js" integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU=" crossorigin="anonymous"></script>
-<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
-<script type="text/javascript" src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>	
-
-<script type="text/javascript">
-    addEventListener("load", function() { 
-        setTimeout(hideURLbar, 0);
-    }, false);
-    function hideURLbar(){ 
-        window.scrollTo(0,1);
-    }
-</script>
-
-<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js" integrity="sha384-xrRywqdh3PHs8keKZN+8zzc5TX0GRTLCcmivcbNJWm2rs5C8PRhcEn3czEjhAO9o" crossorigin="anonymous"></script>
-
-<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.1/jquery.easing.js"></script>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/magnific-popup.js/1.1.0/jquery.magnific-popup.js"></script>
-
-    <script>
-        //https://github.com/eKoopmans/html2pdf.js
-        $('.downloadPDF').click(function () {
-        	var element = document.getElementById('content'); //document.createElement("body");
-        	element.classList.remove('screen');
-        	element.classList.add('print');
-        	var opt = {
-        		margin:       3,
-        		filename:     'soil-analysis.pdf',
-        		image:        { type: 'jpeg', quality: 1.0 },
-        		html2canvas:  { scale: 2, letterRendering: true, windowWidth: 1024 },  //, windowWidth: 1024
-        		jsPDF:        { orientation: 'portrait', unit: 'mm', format: 'a4', putOnlyUsedFonts: true, floatPrecision: 'smart', }
-        	};
-        	html2pdf()
-        	    .from(element)
-        	    .toPdf()
-        	    .set(opt)
-        	    .save()
-        	    .then(function(){
-        		    element.classList.remove('print');
-        		    element.classList.add('screen');
-        	});
-        	
-        });
-    </script>
-</body>
-</html>
+<?php
+/**
+ * soil-analysis-full-report.php
+ *
+ * Combined report view: redirects to the analysis page which links to the soil report.
+ * The individual pages (soil-analysis.php and soil-report.php) replaced the original
+ * combined getResources approach.
+ */
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
+
+requireLogin();
+
+$rid  = (int)   ($_GET['rid']  ?? 0);
+$rand = (float) ($_GET['rand'] ?? 0);
+
+if ($rid > 0) {
+    header("Location: /dashboard/crop-analysis/soil-test-data/soil-analysis.php?rid={$rid}&rand={$rand}");
+} else {
+    header("Location: /dashboard/crop-analysis/soil-test-data/soil-test-data.php");
+}
+exit;

+ 13 - 243
dashboard/crop-analysis/soil-test-data/soil-recommendations.php

@@ -1,247 +1,17 @@
-<!doctype html>
-<html lang="en">
+<?php
+/**
+ * soil-recommendations.php (soil-test-data)
+ *
+ * Redirects to the main soil recommendations page under client-settings.
+ */
 
-    <head>
-        <title>[[*longtitle]] | [[++site_name]]</title>
-        <base href="[[!++site_url]]">
-        <meta charset="[[++modx_charset]]">
-        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
-        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-        <meta name="keywords" content="[[*introtext]]">
-        <meta name="description" content="[[*description]]">
-        <link rel="icon" href="client-assets/images/favicon.ico?v=2" type="image/x-icon"> [[!Profile]]
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
 
-        <script type="text/javascript">
-            window.dataLayer = window.dataLayer || [];
+require_once __DIR__ . '/../../../lib/auth.php';
 
-            function gtag() {
-                dataLayer.push(arguments);
-            }
-            gtag('js', new Date());
-            gtag('set', {
-                'user_id': '[[+modx.user.id]]'
-            }); // Set the user ID using signed-in user_id.
-            gtag('config', 'UA-133963301-1');
-        </script>
+requireLogin();
 
-        <link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" />
-        <link href="client-assets/css/dashboard-2021.css" rel="stylesheet" />
-        <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js" crossorigin="anonymous"></script>
-        <link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
-        <script src="client-assets/js/skycons.js"></script>
-        <style>
-            .btn-append {
-                color: #495057;
-                background-color: #e9ecef;
-                border: 1px solid #ced4da;
-            }
-            .footer {
-                position: absolute;
-                bottom: 0;
-                width: 100%;
-                height: 60px;
-                line-height: 60px;
-            }
-        </style>
-    </head>
-
-    <body class="sb-nav-fixed" id="page-top">
-        <?php include __DIR__.'/../../components/navigation.php'; ?>
-
-        <div id="layoutSidenav">
-            <div id="layoutSidenav_nav">
-                <!-- Sidebar -->
-                <?php renderSidebar(); ?>
-            </div>
-            </div>
-
-            <div id="layoutSidenav_content">
-                <main>
-
-                    <div class="container-fluid px-4">
-                        <h1 class="mt-4">[[*pagetitle]]</h1>
-
-                        <ol class="breadcrumb mb-4"> [[$dash-breadcrumbs]] </ol>
-
-                        <div class="row">
-
-                            <div class="grid-form">
-                                <!---->
-                                <div class="grid-form1">
-                                    <h3>Soil Analysis</h3>
-                                    <p>Variables used in Soil Analysis recommendation programs.</p>
-                                    <div class="tab-content">
-                                        <!-- Company Product list here -->    
-
-                                        [[!companySoilRecomendations]]
-
-                                    </div>
-                                </div>
-
-
-
-                                <div class="bs-example2 bs-example-padded-bottom">
-                                    <button type="button" class="btn btn-primary btn-lg btn-warning " data-toggle="modal" data-target="#myModal">
-                                        Add Soil Recommendation
-                                    </button>
-                                    <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" style="display: none;">
-                                        <div class="modal-dialog">
-                                            <div class="modal-content">
-                                                <div class="modal-header">
-                                                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
-                                                    <h2 class="modal-title">Add New Product</h2>
-                                                </div>
-                                                <div class="modal-body">
-                                                    <form method="post" action="" id="newClientDetails" >
-                                                        <div class="grid-form">
-                                                            <input type="hidden" class="form-control" name="m_user" id="m_user" value="[[+modx.user.id]]" required>
-                                                            <input type="hidden" class="form-control" name="modx_user_attributes" id="modx_user_attributes" value="[[+modx.user.id]]" required>
-                                                            <div class="form-horizontal">
-                                                                <div class="form-group">
-                                                                    <label class="sr-only" for="name">Product:</label>
-                                                                    <input type="text" class="form-control" name="name" placeholder="Product Name" id="name" required >
-                                                                </div>
-                                                                <div class="form-group">
-                                                                    <label class="sr-only" for="chemical">Chemical Symbol:</label>
-                                                                    <input type="text" class="form-control" name="chemical" placeholder="Chemical" id="chemical"  >
-                                                                </div>
-                                                            </div>
-                                                            <div class="form-horizontal">
-                                                                <div class="form-group">
-                                                                    <label class="sr-only" for="N">Nitrogen - N</label>
-                                                                    <input type="email" class="form-control" name="N" placeholder="Nitrogen" required id="N"  >
-                                                                </div>
-                                                                <div class="form-group">
-                                                                    <label class="sr-only" for="P">Phosphorus - P</label>
-                                                                    <input type="text" class="form-control" name="P" placeholder="Phosphorus" id="P"  >
-                                                                </div>
-                                                            </div>
-                                                            <div class="form-horizontal">
-                                                                <div class="form-group">
-                                                                    <label class="sr-only" for="k">Postassium - K</label>
-                                                                    <input type="text" class="form-control" name="K" placeholder="Postassium" id="K" >
-                                                                </div>
-                                                                <div class="form-group">    
-                                                                    <label class="sr-only" for="Na">Sodium - Na</label>
-                                                                    <input type="text" class="form-control" name="Na" placeholder="Sodium" id="Na" >
-                                                                </div>
-                                                            </div>
-                                                            <div class="form-horizontal">
-                                                                <div class="form-group">
-                                                                    <label class="sr-only"for="Ca">Calcium - Ca</label>
-                                                                    <input type="text" class="form-control" name="Ca" placeholder="Calcium" required id="Ca"  >
-                                                                </div>
-                                                                <div class="form-group">                    		        
-                                                                    <label class="sr-only" for="Mn">Magnesium - Mg</label>
-                                                                    <input type="text" class="form-control" name="Mg" placeholder="Magnesium" required id="Mg"  >
-                                                                </div>
-                                                            </div>
-                                                            <div class="form-horizontal">
-                                                                <div class="form-group">
-                                                                    <label class="sr-only" for="B">Boron - B</label>
-                                                                    <input type="text" class="form-control" name="B" placeholder="Boron" id="B" >
-                                                                </div>
-                                                                <div class="form-group">    
-                                                                    <label class="sr-only" for="Zn">Zinc - Zn</label>
-                                                                    <input type="text" class="form-control" name="Zn" placeholder="Zinc" id="Zn" >
-                                                                </div>
-                                                            </div>
-                                                            <div class="form-horizontal">
-                                                                <div class="form-group">
-                                                                    <label class="sr-only" for="Cu">Copper - Cu</label>
-                                                                    <input type="text" class="form-control" name="Cu" placeholder="Copper" id="Cu" >
-                                                                </div>
-                                                                <div class="form-group">    
-                                                                    <label class="sr-only" for="Mn">Manganese - Mn</label>
-                                                                    <input type="text" class="form-control" name="Mn" placeholder="Manganese" id="Mn" >
-                                                                </div>
-                                                            </div>
-                                                            <div class="form-horizontal">
-                                                                <div class="form-group">
-                                                                    <label class="sr-only" for="Fe">Iron - Fe</label>
-                                                                    <input type="text" class="form-control" name="Fe" placeholder="Iron" id="Fe" >
-                                                                </div>
-                                                                <div class="form-group">    
-                                                                    <label class="sr-only" for="Co">Colbalt - Co</label>
-                                                                    <input type="text" class="form-control" name="Co" placeholder="Colbalt" id="Co" >
-                                                                </div>
-                                                            </div>
-                                                            <div class="form-horizontal">
-                                                                <div class="form-group">
-                                                                    <label class="sr-only" for="M0">Molybdenum - Mo</label>
-                                                                    <input type="text" class="form-control" name="Mo" placeholder="Molybdenum" id="Mo" >
-                                                                </div>
-                                                            </div>
-                                                        </div>
-                                                    </form>
-                                                </div>
-                                                <div class="modal-footer">
-                                                    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
-                                                    <button type="button" class="btn btn-primary">Save changes</button>
-                                                </div>
-                                            </div><!-- /.modal-content -->
-                                        </div><!-- /.modal-dialog -->
-                                    </div>
-                                </div>
-
-
-
-                            </div>
-
-                        </div>
-
-                    </div>
-
-                </main>
-
-                <footer class="py-4 bg-light mt-auto">
-                    <div class="container-fluid px-4">
-                        <div class="d-flex align-items-center justify-content-between small">
-                            <div class="text-muted">[[SimpleCopyright? &startYear=`2003`]]. All Rights Reserved <a href="[[~1]]"></a></div>
-                            <div>
-                                <a href="[[~39~]]">Privacy Policy</a>
-                                &middot;
-                                <a href="[[~39~]]">Terms &amp; Conditions</a>
-                            </div>
-                        </div>
-                    </div>
-                </footer>
-
-            </div>
-
-        </div>
-
-        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-        <script>
-            /*!
-            * Start Bootstrap - SB Admin v7.0.3 (https://startbootstrap.com/template/sb-admin)
-            * Copyright 2013-2021 Start Bootstrap
-            * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-sb-admin/blob/master/LICENSE)
-            */
-            // 
-            // Scripts
-            // 
-
-            window.addEventListener('DOMContentLoaded', event => {
-
-                // Toggle the side navigation
-                const sidebarToggle = document.body.querySelector('#sidebarToggle');
-                if (sidebarToggle) {
-                    // Uncomment Below to persist sidebar toggle between refreshes
-                    // if (localStorage.getItem('sb|sidebar-toggle') === 'true') {
-                    //     document.body.classList.toggle('sb-sidenav-toggled');
-                    // }
-                    sidebarToggle.addEventListener('click', event => {
-                        event.preventDefault();
-                        document.body.classList.toggle('sb-sidenav-toggled');
-                        localStorage.setItem('sb|sidebar-toggle', document.body.classList.contains('sb-sidenav-toggled'));
-                    });
-                }
-
-            });
-
-        </script>
-        <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js" crossorigin="anonymous"></script>
-        <script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest" crossorigin="anonymous"></script>
-    </body>
-</html>
+header('Location: /dashboard/client-settings/soil-recommendations.php');
+exit;

+ 220 - 406
dashboard/crop-analysis/soil-test-data/soil-report-pdf.php

@@ -1,431 +1,245 @@
+<?php
+/**
+ * soil-report-pdf.php
+ *
+ * Printable / PDF-export version of a soil analysis report.
+ * NOTE: The [[!soilAnalysisReportCalcs?]] and [[!soilProgramCalcs?]] snippet
+ * sections are pending full PHP migration. They currently render as blank.
+ */
+
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
+require_once __DIR__ . '/../../../lib/csrf.php';
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+requireLogin();
+
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+
+$recordId = (int)   ($_GET['rid']  ?? 0);
+$randId   = (float) ($_GET['rand'] ?? 0);
+
+$row  = null;
+$today = date('jS F Y');
+
+if ($recordId > 0) {
+    $stmt = $pdo->prepare(
+        'SELECT * FROM soil_records WHERE id = ? AND rand = ? LIMIT 1'
+    );
+    $stmt->execute([$recordId, $randId]);
+    $row = $stmt->fetch();
+}
+
+$h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
+?>
 <!doctype html>
 <html lang="en">
-    <head>
-        <title>[[*longtitle]] | [[++site_name]]</title>
-        <base href="[[!++site_url]]" >
-        <meta charset="[[++modx_charset]]" >
-        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" >
-        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
-        <meta name="keywords" content="[[*introtext]]" >
-        <meta name="description" content="[[*description]]" >
-
-        [[!Profile]]
-
-        <link rel="icon" href="client-assets/images/favicon.ico?v=2" type="image/x-icon" >
-
-        <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
-
-        <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" rel="stylesheet" type="text/css" />
-        <link href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css" rel="stylesheet" type="text/css" />
-
-        <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.css" integrity="sha256-PF6MatZtiJ8/c9O9HQ8uSUXr++R9KBYu4gbNG5511WE=" crossorigin="anonymous" rel="stylesheet" type="text/css"  />
-
-        <link type="text/css" href="/client-assets/weather-icons/css/weather-icons.min.css?version=1.16" rel="stylesheet" type="text/css" />
-        <link href="https://cdnjs.cloudflare.com/ajax/libs/magnific-popup.js/1.1.0/magnific-popup.css" rel="stylesheet" type="text/css" />
-
-        <script type="text/javascript" src="https://use.fontawesome.com/1e2844bb90.js"></script>
-
-        <link href="/client-assets/css/dashboard.css" rel="stylesheet" type="text/css" />
-        <script src="https://unpkg.com/gijgo@1.9.11/js/gijgo.min.js" type="text/javascript"></script>
-        <link href="https://unpkg.com/gijgo@1.9.11/css/gijgo.min.css" rel="stylesheet" type="text/css" />
-        <script src="client-assets/js/skycons.js" type="text/javascript"></script>
-
-        <link href="client-assets/home/css/graphing.css" rel="stylesheet" type="text/css" media="screen" />
-        <link href="client-assets/home/css/alux.min.css" rel="stylesheet" type="text/css" media="screen" />
-
-        <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" integrity="sha512-YcsIPGdhPK4P/uRW6/sruonlYj+Q7UHWeKfTAkBW+g83NKM+jMJFJ4iAPfSnVp7BKD4dKMHmVSvICUbE/V1sSw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
-
-        <style>
-            @media print {
-                @page {
-                    size: A4 portrait;
-                }
-                @page :left {
-                    margin-left: 0.5cm;
-                }
-                @page :right {
-                    margin-right: 0.5cm;
-                }
-                @page :top {
-                    margin-top: 0cm;
-                }
-                @page :bottom {
-                    margin-bottom: 0cm;
-                }
-            }
-        </style>
-
-    </head>
-    <body>
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Soil Analysis Report | Crop Monitor</title>
+    <script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
+    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.css" crossorigin="anonymous" rel="stylesheet">
+    <link href="/client-assets/css/dashboard.css" rel="stylesheet">
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" crossorigin="anonymous"></script>
+    <style>
+        @media print {
+            @page { size: A4 portrait; margin: 0.5cm; }
+            .d-print-none { display: none !important; }
+        }
+        .title th, .title td { padding: 2px 6px; }
+    </style>
+</head>
+<body>
 
-        <div class="container">
-            <div class="row">
-                <?php
-                // Replace Logo with Customer Logo if supplied.
-                $client = '';
+<div class="container" id="content">
 
-                echo "<div class='col-md-3'>";
-                if ($client === "") {
-                    echo "<img class='img-fluid' src='client-assets/images/crop-monitor.png'  alt='Crop Monitor' >";    
-                } else {
-                    echo "<img class='img-fluid' src='client-assets/images/crop-monitor.png'  alt='Crop Monitor' >";
-                }
+    <?php if (!$row): ?>
+        <div class="alert alert-danger mt-4">Record not found or access denied.</div>
+    <?php else: ?>
 
-                //echo "<span class='col'></span>";
+    <div class="row mb-3">
+        <div class="col-md-3">
+            <img class="img-fluid" src="/client-assets/images/crop-monitor.png" alt="Crop Monitor">
+        </div>
+        <div class="col-md-9"></div>
+    </div>
+
+    <table class="title w-100 mb-3">
+        <tbody>
+            <tr>
+                <td class="text-end fw-bold">DATE:</td>
+                <td><?= $h($today) ?></td>
+                <td></td>
+                <td class="text-end fw-bold">SAMPLE ID:</td>
+                <td><?= $h($row['site_id']) ?></td>
+            </tr>
+            <tr>
+                <td class="text-end fw-bold">CLIENT:</td>
+                <td><?= $h($row['client_name']) ?></td>
+                <td></td>
+                <td class="text-end fw-bold">DATE SAMPLED:</td>
+                <td><?= $h($row['date_sampled']) ?></td>
+            </tr>
+            <tr>
+                <td class="text-end fw-bold">ADDRESS:</td>
+                <td><?= $h($row['site_address']) ?></td>
+                <td></td>
+                <td class="text-end fw-bold">LAB NUMBER:</td>
+                <td><?= $h($row['lab_no']) ?></td>
+            </tr>
+            <tr>
+                <td></td>
+                <td><?= $h($row['state_postcode']) ?></td>
+                <td></td>
+                <td class="text-end fw-bold">CROP:</td>
+                <td><?= $h($row['sample_id']) ?></td>
+            </tr>
+            <tr>
+                <td></td>
+                <td><?= $h($row['email']) ?></td>
+                <td colspan="3"></td>
+            </tr>
+        </tbody>
+    </table>
+
+    <!-- Download button (hidden on print) -->
+    <div class="d-print-none mb-3">
+        <button class="btn btn-success btn-sm downloadPDF">
+            <i class="fas fa-download me-1"></i>Download PDF
+        </button>
+    </div>
+
+    <div class="row">
+        <div class="col-md-12 text-center fw-bold h4">Soil Analysis Summary</div>
+    </div>
+
+    <!-- Analysis calculation sections — pending PHP migration of soilAnalysisReportCalcs -->
+    <div class="row bg-dark text-white p-2 mt-3">
+        <div class="text-center col-md-12 h5">
+            Total kilograms per hectare of each element needed to balance soil in this test
+        </div>
+    </div>
+    <div class="row">
+        <div class="col text-muted fst-italic p-3">
+            [Soil analysis calculation components pending migration]
+        </div>
+    </div>
 
+    <hr>
 
-                //Client Test Description
-                if ($client === "") {
-                    echo "";    
-                } else {
-                    echo "<img class='img-fluid' src='client-assets/images/crop-monitor.png'  alt='Crop Monitor' >";
-                }
-                echo "</div>";
+    <!-- Report form (auto-saves via AJAX) -->
+    <form class="report-form" method="post">
+        <input type="hidden" name="csrf_token" value="<?= $h(generateCsrfToken()) ?>">
 
-                echo "<div class='col-md-9'></div>";
-                ?>
+        <div class="row bg-dark text-white p-2 mt-3">
+            <div class="text-center col-md-12 h5">Overview</div>
+        </div>
+        <div class="row">
+            <div class="col-md-12 p-0">
+                <textarea class="form-control rounded-0" rows="5" id="overview" name="overview"></textarea>
             </div>
+        </div>
 
-            <?php
-            $result    = null;
-
-            $client_id = (int) (isset($_GET["cid"])) ? $_GET["cid"] : ""; // client number
-            $record_id = (float) (isset($_GET["rid"])) ? $_GET["rid"] : ""; // record number
-            $rand_id = (float) (isset($_GET["rand"])) ? $_GET["rand"] : "";
-
-            $today = date('jS F Y');
-
-            //Database connection
-            //$con = mysqli_connect("localhost", "root", "R3M0T31", "cropmonitor");
-            $con = mysqli_connect("localhost", "cropmonitor", "brvnCcaEYxlPCS3", "cropmonitor");
+        <hr>
 
-            // Check connection
-            if (mysqli_connect_errno()) {
-                echo "Failed to connect to MySQL: " . mysqli_connect_error();
-            }
+        <div class="row bg-dark text-white p-2 mt-3">
+            <div class="text-center col-md-12 h5">
+                Ideal Soil Balancing Program for One Season of a FIVE YEAR Plan
+            </div>
+        </div>
+        <div class="col text-muted fst-italic p-3">
+            [Soil program calculation components pending migration]
+        </div>
 
-            // Get results from database 
-            $result = mysqli_query($con, "SELECT * FROM `soil_records` WHERE `id` = '" . $record_id . "' AND `rand` = '" . $rand_id . "' ");
-
-            if ($result === FALSE) {
-                die(mysqli_error($con)); // TODO: better error handling
-                echo "User Profile incorrect";
-            } else {
-                while ($row = mysqli_fetch_array($result)) {
-
-                    //TEST
-                    $client     = $row['client_name'];
-                    $address    = $row['site_address'];
-                    $state      = $row['state_postcode'];
-                    $email      = $row['email'];
-                    $labNo      = $row['lab_no'];
-                    $sampleDate = $row['date_sampled'];
-                    $sample     = $row['site_id'];
-                    $crop       = $row['sample_id'];
-
-                    if ($rand_id === NULL) { //if element not tested hide row
-
-                    } else {
-            ?>
-
-            <table class='title'>
-                <tbody>
-                    <tr>
-                        <th class='col-20'></th>
-                        <th class='col-20'></th>
-                        <th class='col-20'></th>
-                        <th class='col-20'></th>
-                        <th class='col-20'></th>
-                    </tr>
-                    <tr>
-                        <td class='right'><b>DATE:</b></td>
-                        <td class='left'><?php echo $today; ?></td>
-                        <td></td>
-                        <td class='right'><b>SAMPLE ID:</b></td>
-                        <td class='left'><?php echo $sample; ?></td>
-                    </tr>
-                    <tr>
-                        <td class='right'><b>CLIENT:</b></td>
-                        <td class='left'><?php echo $client; ?></td>
-                        <td></td>
-                        <td class='right'><b>DATE SAMPLED:</b></td>
-                        <td class='left'><?php echo $sampleDate; ?></td>
-                    </tr>
-                    <tr>
-                        <td class='right'><b>ADDRESS:</b></td>
-                        <td class='left'><?php echo $address; ?></td>
-                        <td></td>
-                        <td class='right'><b>LAB NUMBER:</b></td>
-                        <td class='left'><?php echo $labNo; ?></td>
-                    </tr>
-                    <tr>
-                        <td class='right'><b> </b></td>
-                        <td class='left'><?php echo $state; ?></td>
-                        <td></td>
-                        <td class='right'><b>CROP:</b></td>
-                        <td class='left'><?php echo $crop; ?></td>
-                    </tr>
-                    <tr>
-                        <td class='right'><b> </b></td>
-                        <td class='left'><?php echo $email; ?></td>
-                        <td></td>
-                        <td class='right'></td>
-                        <td class='left'></td>
-                    </tr>
-                </tbody>
-            </table>
-
-            <?php
-                    }
-                }
-            }
+        <hr>
 
-            mysqli_close($con);
-
-
-            /* 
-			<div class="row pt-3">
-				<div class="col-md-2 text-right"><b>DATE:</b></div>
-				<div class="col-md-3 text-left"><?php echo $today; ?></div>
-				<div class="col-md-1"></div>
-				<div class="col-md-3 text-right"><b>SAMPLE ID:</b></div>
-				<div class="col-md-3 text-left"><?php echo $sample; ?></div>
-			</div>
-			<div class="row pt-1">
-				<div class="col-md-2 text-right"><b>CLIENT:</b></div>
-				<div class="col-md-3 text-left"><?php echo $client; ?></div>
-				<div class="col-md-1"></div>
-				<div class="col-md-3 text-right"><b>DATE SAMPLED:</b></div>
-				<div class="col-md-3 text-left"><?php echo $sampleDate; ?></div>
-			</div>
-			<div class="row pt-1">
-				<div class="col-md-2 text-right"><b>Address:</b></div>
-				<div class="col-md-3 text-left"><?php echo $address; ?></div>
-				<div class="col-md-1"></div>
-				<div class="col-md-3 text-right"><b>Lab Number:</b></div>
-				<div class="col-md-3 text-left"><?php echo $labNo; ?></div>
-			</div>
-			<div class="row pt-1">
-				<div class="col-md-2 text-right"><b></b></div>
-				<div class="col-md-3 text-left"><?php echo $state; ?></div>
-				<div class="col-md-1"></div>
-				<div class="col-md-3 text-right"><b>CROP:</b></div>
-				<div class="col-md-3 text-left"><?php echo $crop; ?></div>
-			</div>
-			<div class="row pt-1">
-				<div class="col-md-2 text-right"><b></b></div>
-				<div class="col-md-3 text-left"><?php echo $email; ?></div>
-				<div class="col-md-1"></div>
-				<div class="col-md-3 text-right"><b></b></div>
-				<div class="col-md-3 text-left"></div>
-			</div>
-			*/
-            ?>
-
-            <!-- Graph Button -->
-            <div class="d-print-none">
-                <div class="row p-2">
-                    <div class="col">
-                        [[!soilAnalysisGraphButton]]
-                    </div>
-                    <div class="col">
-                        <div class="form-status-holder"></div>
-                    </div>
-                </div>
+        <div class="row bg-dark text-white p-2 mt-3">
+            <div class="text-center col-md-12 h5">
+                <input type="text" class="text-center form-control-plaintext text-white border-dark bg-dark"
+                       name="header1" id="header1" value="Foliar Program">
             </div>
-            <!-- GRAPH BANNER -->
-
-            <div class="row">
-                <div class="col-md-12 text-center font-weight-bold h4">Soil Analysis Summary</div>
+        </div>
+        <div class="row">
+            <div class="col-md-12 p-0">
+                <textarea class="form-control rounded-0" rows="5"
+                          id="foliar_Details" name="foliar_Details"></textarea>
             </div>
+        </div>
 
-
-            <!-- CHART HEADER  -->
-
-            <div class="row bg-dark text-white p-2 mt-3">
-                <div class="text-center col-md-12 h5">Total kilograms per hectare of each element needed to balance soil in this test</div>
+        <div class="row bg-dark text-white p-2 mt-3">
+            <div class="text-center col-md-12 h5">Microbe Program</div>
+        </div>
+        <div class="row">
+            <div class="col-md-12 p-0">
+                <textarea class="form-control rounded-0" rows="5"
+                          id="microbe_Program" name="microbe_Program"></textarea>
             </div>
+        </div>
 
-            <div class="row">
-                <div class="">
-                    [[!soilAnalysisReportCalcs? &symbol=`Ca` &element=`BS_ca_ppm` &min=`ca_ppm_min` &max=`ca_ppm_max` &nutrient=`Calcium` &type=`kg` &class=`col`]]
-                    [[!soilAnalysisReportCalcs? &symbol=`Mg` &element=`BS_mg_ppm`  &min=`mg_ppm_min` &max=`mg_ppm_max` &nutrient=`Magnesium` &type=`kg` &class=`col`]]
-                    [[!soilAnalysisReportCalcs? &symbol=`K`  &element=`BS_k_ppm`  &min=`k_ppm_min` &max=`k_ppm_max` &nutrient=`Potasium` &type=`kg` &class=`col`]]
-                    [[!soilAnalysisReportCalcs? &symbol=`Na` &element=`BS_na_ppm`  &min=`na_ppm_min` &max=`na_ppm_max` &nutrient=`Sodium` &type=`kg` &class=`col`]]
-                    [[!soilAnalysisReportCalcs? &symbol=`P`  &element=`p_colwell`  &min=`` &max=`` &nutrient=`Phosphate` &type=`kg` &class=`col`]]
-                    [[!soilAnalysisReportCalcs? &symbol=`S` &element=`s_morgan`  &min=`` &max=`` &nutrient=`Sulfur` &type=`kg &class=`col`]]
-                    [[!soilAnalysisReportCalcs? &symbol=`Mn` &element=`mn_dtpa`  &min=`` &max=`` &nutrient=`Manganese` &type=`kg` &class=`col`]]
-                    [[!soilAnalysisReportCalcs? &symbol=`Fe` &element=`fe_dtpa`  &min=`` &max=`` &nutrient=`Iron` &type=`kg` &class=`col`]]
-                    [[!soilAnalysisReportCalcs? &symbol=`Zn` &element=`zn_dtpa`  &min=`` &max=`` &nutrient=`Zinc` &type=`kg` &class=`col`]]
-                    [[!soilAnalysisReportCalcs? &symbol=`Cu` &element=`cu_dtpa`  &min=`` &max=`` &nutrient=`Copper` &type=`kg` &class=`col`]]
-                    [[!soilAnalysisReportCalcs? &symbol=`AmN` &element=`NH3_N`  &min=`` &max=`` &nutrient=`AmNitrogen` &type=`kg` &class=`col`]]
-                    [[!soilAnalysisReportCalcs? &symbol=`B` &element=`b_cacl2`  &min=`` &max=`` &nutrient=`Boron` &type=`kg` &class=`col`]]
-                    [[!soilAnalysisReportCalcs? &symbol=`NN` &element=`NO3_N`  &min=`` &max=`` &nutrient=`NNitrogen` &type=`kg` &class=`col`]]
-                </div>
-            </div>
+        <div class="row mt-2">
+            <p class="small fst-italic text-muted">
+                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>
+        </div>
 
-            <hr>
-
-            <!-- **************** START OF FORM DATA **************** -->
-            <form class="report-form" method="post">
-
-                <input class="" hidden type="text" name="id" id="id" value="[[+modx.user.id]]" >
-
-                <!-- **************** OVERVIEW SECTION **************** -->
-                <div class="row bg-dark text-white p-2 mt-3">
-                    <div class="text-center col-md-12 h5 ">Overview</div>
-                </div>
-                <div class="row">
-                    <div class="col-md-12 p-0" >
-                        <textarea class="form-control rounded-0" rows="5" id="overview" name="overview" ></textarea>
-                    </div>
-                </div>
-
-                <hr>
-
-                <div class="row bg-dark text-white p-2 mt-3">
-                    <div class="text-center col-md-12 h5 ">Ideal Soil Balancing Program for One Season of a FIVE YEAR Plan</div>
-                </div>
-
-                <div class="">
-                    [[!soilProgramCalcs? &symbol=`Ca` &element=`BS_ca_ppm` &min=`ca_ppm_min` &max=`ca_ppm_max` &nutrient=`Calcium` &type=`kg`]]
-                    [[!soilProgramCalcs? &symbol=`Ca` &element=`BS_ca_ppm` &min=`ca_ppm_min` &max=`ca_ppm_max` &nutrient=`Calcium` &type=`kg`]]
-                    [[!soilProgramCalcs? &symbol=`Ca` &element=`BS_ca_ppm` &min=`ca_ppm_min` &max=`ca_ppm_max` &nutrient=`Calcium` &type=`kg`]]
-                    [[!soilProgramCalcs? &symbol=`Ca` &element=`BS_ca_ppm` &min=`ca_ppm_min` &max=`ca_ppm_max` &nutrient=`Calcium` &type=`kg`]]
-                    [[!soilProgramCalcs? &symbol=`Ca` &element=`BS_ca_ppm` &min=`ca_ppm_min` &max=`ca_ppm_max` &nutrient=`Calcium` &type=`kg`]]
-                </div> 
-
-                <hr>
-
-                <!-- **************** FOLIAR PROGRAM SECTION **************** -->
-                <div class="row bg-dark text-white p-2 mt-3">
-                    <div class="text-center col-md-12 h5" ><input type="text" class="text-center form-control-plaintext text-white border-dark bg-dark" name="header1" id="header1" value="Foliar Program"></div>
-                </div>
-                <div class="row">
-                    <div class="col-md-12 p-0" >
-                        <textarea class="form-control rounded-0" rows="5" id="foliar_Details" name="foliar_Details"></textarea>
-                    </div>
-                </div>
-
-                <!-- **************** MICROBE PROGRAM SECTION **************** -->
-                <div class="row bg-dark text-white p-2 mt-3">
-                    <div class="text-center col-md-12 h5">Microbe Program</div>
-                </div>
-                <div class="row">
-                    <div class="col-md-12 p-0" >
-                        <textarea class="form-control rounded-0" rows="5" id="microbe_Program" name="microbe_Program" ></textarea>
-                    </div>
-                </div>
-
-                <div class="row">
-                    <p style="font-style: italic; font-size: 9px;">Any recommendations provided by Cropmonitor are advice only, We are not paid consultants and we are not covered to accept responsibiliy for any of our suggestions. As no control can be exercised over storage, handling, mixing application or use, or weather, plant or soil conditions before, during or after application (all of which may affect the preformance of our program), no responsibility for, or liability for any failure in performance, losses, damage or injuries consequential or otherwise, arisiing form such storage mixng application or use will be accepted under any circumstances whatsoever. The buyer assumes all responsibility for the use of any of our products.</p>
-                </div>
-
-            </form>
-        </div>    
-
-
-
-        <script type="text/javascript">
-            $(document).ready(function(){
-                var timeoutId;
-                $('form textarea, form input').on('input propertychange change', function() {
-                    console.log('Textarea Change');
-
-                    clearTimeout(timeoutId);
-                    timeoutId = setTimeout(function() {
-                        // Runs 1 second (1000 ms) after the last change    
-                        saveToDB();
-                    }, 1000);
-                });
-
-                function saveToDB() {
-                    console.log('Saving to the db');
-                    form = $('.report-form');
-                    $.ajax({
-                        url: "[[~58]]",
-                        type: "POST",
-                        data: form.serialize(), // serializes the form's elements.
-                        beforeSend: function(xhr) {
-                            // Let them know we are saving
-                            $('.form-status-holder').html('Saving...');
-                        },
-                        success: function(data) {
-                            var jqObj = jQuery(data); // You can get data returned from your ajax call here. ex. jqObj.find('.returned-data').html()
-                            // Now show them we saved and when we did
-                            var d = new Date();
-                            $('.form-status-holder').html('Saved! Last: ' + d.toLocaleTimeString());
-                        },
-                    });
-                }
-
-                // This is just so we don't go anywhere  
-                // and still save if you submit the form
-                $('.report-form').submit(function(e) {
-                    saveToDB();
-                    e.preventDefault();
-                });
-            });
-        </script>
+    </form>
 
-        </div>
+    <?php endif; ?>
 
-    <!-- 
-<script src="https://cloud.tinymce.com/stable/tinymce.min.js?apiKey=xcotawi18mg1imp8im144buq68h9g3ndd3c9c8215w8qu3ld"></script>
+</div><!-- /container -->
+
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
 <script>
-tinymce.init({
-selector: 'textarea',
-menubar: false,
-toolbar: 'bold italic  | alignleft aligncenter alignright alignjustify | bullist numlist | removeformat',
-plugins: 'autosave',
-autosave_interval: '20s'
+$(document).ready(function () {
+    var timeoutId;
+    var saveUrl = '/dashboard/crop-analysis/updatecomment.php'
+                + '?rid=<?= (int) $recordId ?>&rand=<?= (float) $randId ?>';
+
+    $('form textarea, form input[name="header1"]').on('input propertychange change', function () {
+        clearTimeout(timeoutId);
+        timeoutId = setTimeout(saveToDB, 1000);
+    });
+
+    function saveToDB() {
+        var form = $('.report-form');
+        $.ajax({
+            url: saveUrl,
+            type: 'POST',
+            data: form.serialize(),
+            beforeSend: function () { $('.form-status-holder').html('Saving...'); },
+            success: function (data) {
+                var d = new Date();
+                $('.form-status-holder').html('Saved! Last: ' + d.toLocaleTimeString());
+            }
+        });
+    }
+
+    $('.report-form').submit(function (e) {
+        saveToDB();
+        e.preventDefault();
+    });
+
+    // PDF download
+    $('.downloadPDF').click(function () {
+        var element = document.getElementById('content');
+        var opt = {
+            margin:      3,
+            filename:    'soil-analysis.pdf',
+            image:       { type: 'jpeg', quality: 1.0 },
+            html2canvas: { scale: 2, letterRendering: true, windowWidth: 1024 },
+            jsPDF:       { orientation: 'portrait', unit: 'mm', format: 'a4' }
+        };
+        html2pdf().from(element).set(opt).save();
+    });
 });
 </script>
--->
-
-    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
-    <script type="text/javascript" src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js" integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU=" crossorigin="anonymous"></script>
-    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
-    <script type="text/javascript" src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>	
-
-    <script type="text/javascript">
-        addEventListener("load", function() { 
-            setTimeout(hideURLbar, 0);
-        }, false);
-        function hideURLbar(){ 
-            window.scrollTo(0,1);
-        }
-    </script>
-
-    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js" integrity="sha384-xrRywqdh3PHs8keKZN+8zzc5TX0GRTLCcmivcbNJWm2rs5C8PRhcEn3czEjhAO9o" crossorigin="anonymous"></script>
-
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.1/jquery.easing.js"></script>
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/magnific-popup.js/1.1.0/jquery.magnific-popup.js"></script>
-
-    <script>
-        //https://github.com/eKoopmans/html2pdf.js
-        $('.downloadPDF').click(function () {
-            var element = document.getElementById('content'); //document.createElement("body");
-            element.classList.remove('screen');
-            element.classList.add('print');
-            var opt = {
-                margin:       3,
-                filename:     'soil-analysis.pdf',
-                image:        { type: 'jpeg', quality: 1.0 },
-                html2canvas:  { scale: 2, letterRendering: true, windowWidth: 1024 },  //, windowWidth: 1024
-                jsPDF:        { orientation: 'portrait', unit: 'mm', format: 'a4', putOnlyUsedFonts: true, floatPrecision: 'smart', }
-            };
-            html2pdf()
-                .from(element)
-                .toPdf()
-                .set(opt)
-                .save()
-                .then(function(){
-                element.classList.remove('print');
-                element.classList.add('screen');
-            });
 
-        });
-    </script>
-    </body>
-</html>
+</body>
+</html>

+ 6 - 3
dashboard/crop-analysis/soil-test-data/soil-report.php

@@ -130,7 +130,10 @@ include __DIR__.'/../../layouts/navbar.php';
     <div class="d-print-none">
         <div class="row p-2">
             <div class="col">
-                [[!soilAnalysisGraphButton]]
+                <a href="/dashboard/crop-analysis/soil-test-data/soil-report-pdf.php?rid=<?= (int)$record_id ?>&rand=<?= (float)$rand_id ?>"
+                   class="btn btn-success btn-sm" target="_blank">
+                    <i class="fas fa-file-pdf me-1"></i>View PDF Report
+                </a>
             </div>
             <div class="col">
                 <div class="form-status-holder"></div>
@@ -165,7 +168,7 @@ include __DIR__.'/../../layouts/navbar.php';
             echo soilAnalysisReportCalcs('NN', 'NO3_N', '', '', 'NNitrogen', 'kg', 'col', $record_id, $rand_id);
             ?>
     <form class="report-form" method="post">
-        <input class="" hidden type="text" name="id" id="id" value="[[+modx.user.id]]" >
+        <input class="" hidden type="text" name="id" id="id" value="<?= (int)getCurrentUserId() ?>">
     
         <!-- Overview Module  -->
         <div class="overview-module py-2">
@@ -220,7 +223,7 @@ include __DIR__.'/../../layouts/navbar.php';
                     console.log('Saving to the db');
                     form = $('.report-form');
                 	$.ajax({
-                		url: "[[~58]]",
+                		url: "/dashboard/crop-analysis/updatecomment.php?rid=<?= (int)$record_id ?>&rand=<?= (float)$rand_id ?>",
                 		type: "POST",
                 		data: form.serialize(), // serializes the form's elements.
                 		beforeSend: function(xhr) {

+ 5 - 17
dashboard/crop-analysis/soil-test-data/soil-test-data.php

@@ -1,16 +1,14 @@
 <?php
-error_reporting(E_ALL);
-ini_set('display_errors', 1);
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
+require_once __DIR__ . '/../../../lib/csrf.php';
 
-
-// dashboard/crop-analysis/soil-test-data.php
-// New include-based layout (migrating from modX snippets)
-
-// Start session for CSRF protection
 if (session_status() === PHP_SESSION_NONE) {
     session_start();
 }
 
+requireLogin();
+
 $pageTitle = 'Soil Test Analysis Report';
 $siteName = 'Crop Management Platform';
 $activeItem = 'Soil Analysis';
@@ -76,16 +74,6 @@ include __DIR__ . '/../../../layouts/navbar.php';
             </div>
         </main>
 
-        <footer class="py-4 bg-light mt-auto">
-            <div class="container-fluid px-4">
-                <div class="d-flex align-items-center justify-content-between small">
-                    <div class="text-muted">&copy; <?= date('Y') ?> Crop Management Platform. All Rights Reserved.</div>
-                    <div>
-                        <a href="/privacy-policy.php">Privacy Policy</a> &middot; <a href="/terms.php">Terms &amp; Conditions</a>
-                    </div>
-                </div>
-            </div>
-        </footer>
     </div>
 </div>
 

+ 56 - 148
dashboard/crop-analysis/updatecomment.php

@@ -1,160 +1,68 @@
-<!doctype html>
-<html lang="en">
-
-<head>
-	<title>[[*longtitle]] | [[++site_name]]</title>
-	<base href="[[!++site_url]]">
-	<meta charset="[[++modx_charset]]">
-	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
-	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-	<meta name="keywords" content="[[*introtext]]">
-	<meta name="description" content="[[*description]]">
-	<link rel="icon" href="client-assets/images/favicon.ico?v=2" type="image/x-icon"> [[!Profile]]
-	
-	<script type="text/javascript">
-    	window.dataLayer = window.dataLayer || [];
-    
-    	function gtag() {
-    		dataLayer.push(arguments);
-    	}
-    	gtag('js', new Date());
-    	gtag('set', {
-    		'user_id': '[[+modx.user.id]]'
-    	}); // Set the user ID using signed-in user_id.
-    	gtag('config', 'UA-133963301-1');
-	</script>
-	
-	<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" />
-	<link href="client-assets/css/dashboard-2021.css" rel="stylesheet" />
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js" crossorigin="anonymous"></script>
-	<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
-	<script src="client-assets/js/skycons.js"></script>
-	<style>
-    	.btn-append {
-    		color: #495057;
-    		background-color: #e9ecef;
-    		border: 1px solid #ced4da;
-    	}
-    	.footer {
-    		position: absolute;
-    		bottom: 0;
-    		width: 100%;
-    		height: 60px;
-    		line-height: 60px;
-    	}
-	</style>
-</head>
-
-<body class="sb-nav-fixed" id="page-top"> 
-    <?php include __DIR__.'/../../components/navigation.php'; ?>
-    
-	<div id="layoutSidenav">
-	    <div id="layoutSidenav_nav">
-    		<!-- Sidebar -->
-    		<?php renderSidebar(); ?>
-        </div>
-        </div>
-		
-		<div id="layoutSidenav_content">
-			<main>
+<?php
+/**
+ * dashboard/crop-analysis/updatecomment.php
+ *
+ * AJAX endpoint: auto-saves soil report comments to the reports table.
+ * Called by the auto-save JS in soil-report-pdf.php via POST.
+ *
+ * POST params: overview, foliar_Details, microbe_Program, header1
+ * GET params:  rid (soil_records.id), rand (soil_records.rand)
+ */
 
-				<div class="container-fluid px-4">
-				    <h1 class="mt-4">[[*pagetitle]]</h1>
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
 
-					<ol class="breadcrumb mb-4"> [[$dash-breadcrumbs]] </ol>
-					
-					<div class="row">
-<?php
-error_reporting(E_ALL);
-	ini_set('display_errors', 1);
-	
-    //Database connection
-    //$con = mysqli_connect("localhost", "root", "R3M0T31", "cropmonitor");
-    $con = mysqli_connect("localhost", "cropmonitor", "brvnCcaEYxlPCS3", "cropmonitor");
-    
-    // Check connection
-    if (mysqli_connect_errno()) {
-        echo "Failed to connect to MySQL: " . mysqli_connect_error();
-    }
-    
-    $modx->log(modX::LOG_LEVEL_ERROR, $_POST['header1'] );
-	
-	$id = mysqli_real_escape_string($con, $_POST['id']); //modx.user.id
-	$overview = mysqli_real_escape_string($con, $_POST['overview']);    //overview text area
-	$foliar_Details = mysqli_real_escape_string($con, $_POST['foliar_Details']);    //foliar_Details text area
-	$microbe_Program = mysqli_real_escape_string($con, $_POST['microbe_Program']);    //microbe_Program text area
-	
-	$header1 = mysqli_real_escape_string($con, $_POST['header1']); 
-	//$overview = mysqli_real_escape_string($con, $_POST['']); 
-	//$overview = mysqli_real_escape_string($con, $_POST['']); 
+require_once __DIR__ . '/../../config/database.php';
+require_once __DIR__ . '/../../lib/auth.php';
 
-	
-	$record_id = (isset($_GET["rid"])) ? $_GET["rid"] : ""; // record number
-    $rand_id = (isset($_GET["rand"])) ? $_GET["rand"] : ""; // random number for security
+if (!isLoggedIn()) {
+    http_response_code(403);
+    echo json_encode(['success' => false, 'message' => 'Unauthorised']);
+    exit;
+}
 
+header('Content-Type: application/json');
 
-	$result = mysqli_query($con, "INSERT INTO reports($column ) VALUES ('$editval') ON DUPLICATE KEY UPDATE rand = '$rand_id' AND id = '$record_id' AND modx_user_id = '$id' ");
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
 
-	if(mysqli_query($con, $result))
-	{
-		echo $result;
-	}
-?>
+$recordId = (int)   ($_GET['rid']  ?? 0);
+$randId   = (float) ($_GET['rand'] ?? 0);
 
-</div>
+if ($recordId <= 0) {
+    http_response_code(400);
+    echo json_encode(['success' => false, 'message' => 'Missing record ID']);
+    exit;
+}
 
-				</div>
+// Verify the soil record belongs to this user (ownership check)
+$check = $pdo->prepare(
+    'SELECT id FROM soil_records WHERE id = ? AND rand = ? AND modx_user_id = ? LIMIT 1'
+);
+$check->execute([$recordId, $randId, $userId]);
+if (!$check->fetch()) {
+    http_response_code(403);
+    echo json_encode(['success' => false, 'message' => 'Record not found or access denied']);
+    exit;
+}
 
-			</main>
-			
-			<footer class="py-4 bg-light mt-auto">
-                <div class="container-fluid px-4">
-                    <div class="d-flex align-items-center justify-content-between small">
-                        <div class="text-muted">[[SimpleCopyright? &startYear=`2003`]]. All Rights Reserved <a href="[[~1]]"></a></div>
-                        <div>
-                            <a href="[[~39~]]">Privacy Policy</a>
-                            &middot;
-                            <a href="[[~39~]]">Terms &amp; Conditions</a>
-                        </div>
-                    </div>
-                </div>
-            </footer>
-            
-		</div>
+// Collect and sanitise comment fields
+$data = [
+    'overview'        => trim($_POST['overview']        ?? ''),
+    'foliar_details'  => trim($_POST['foliar_Details']  ?? ''),
+    'microbe_program' => trim($_POST['microbe_Program'] ?? ''),
+    'header1'         => trim($_POST['header1']         ?? 'Foliar Program'),
+];
 
-	</div>
+$comment = json_encode($data, JSON_UNESCAPED_UNICODE);
 
-	<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-	<script>
-    	/*!
-            * Start Bootstrap - SB Admin v7.0.3 (https://startbootstrap.com/template/sb-admin)
-            * Copyright 2013-2021 Start Bootstrap
-            * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-sb-admin/blob/master/LICENSE)
-            */
-            // 
-        // Scripts
-        // 
-        
-        window.addEventListener('DOMContentLoaded', event => {
-        
-            // Toggle the side navigation
-            const sidebarToggle = document.body.querySelector('#sidebarToggle');
-            if (sidebarToggle) {
-                // Uncomment Below to persist sidebar toggle between refreshes
-                // if (localStorage.getItem('sb|sidebar-toggle') === 'true') {
-                //     document.body.classList.toggle('sb-sidenav-toggled');
-                // }
-                sidebarToggle.addEventListener('click', event => {
-                    event.preventDefault();
-                    document.body.classList.toggle('sb-sidenav-toggled');
-                    localStorage.setItem('sb|sidebar-toggle', document.body.classList.contains('sb-sidenav-toggled'));
-                });
-            }
-        
-        });
+// Upsert: update if record exists for this soil record + user, else insert
+$stmt = $pdo->prepare('
+    INSERT INTO reports (modx_user_id, record_id, rand, comment, dateTime)
+    VALUES (?, ?, ?, ?, CURDATE())
+    ON DUPLICATE KEY UPDATE comment = VALUES(comment), dateTime = CURDATE()
+');
+$stmt->execute([$userId, $recordId, (int) $randId, $comment]);
 
-	</script>
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js" crossorigin="anonymous"></script>
-	<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest" crossorigin="anonymous"></script>
-</body>
-</html>
+echo json_encode(['success' => true, 'saved' => date('H:i:s')]);

+ 150 - 202
dashboard/crop-analysis/uploadsubmit.php

@@ -1,206 +1,154 @@
 <?php
-require('/client-assets/php/spreadsheet/php-excel-reader/excel_reader2.php');
-require('/client-assets/php/spreadsheet/SpreadsheetReader.php');
-
-    $dbHost = "localhost";
-	$dbDatabase = "cropmonitor";
-	$dbPasswrod = "brvnCcaEYxlPCS3";
-	$dbUser = "cropmonitor";
-	$mysqli = new mysqli($dbHost, $dbUser, $dbPasswrod, $dbDatabase);
-	//$con = mysqli_connect("localhost", "cropmonitor", "brvnCcaEYxlPCS3", "cropmonitor");
-
-
-if(isset($_POST['Submit'])){
-
-  $mimes = ['application/vnd.ms-excel','text/xls','text/xlsx','text/csv','application/vnd.oasis.opendocument.spreadsheet'];
-  if(in_array($_FILES["file"]["type"],$mimes)){
-
-    $uploadFilePath = '/client-assets/uploads/'.basename($_FILES['file']['name']);  //need to add individual folders for clients.
-    move_uploaded_file($_FILES['file']['tmp_name'], $uploadFilePath);
-
-    $Reader = new SpreadsheetReader($uploadFilePath);
-
-    $totalSheet = count($Reader->sheets());
-
-    echo "You have total ".$totalSheet." sheets".
-
-    $html="<table border='1'>";
-
-    /* For Loop for all sheets */
-    for($i=0;$i<$totalSheet;$i++){
-
-      $Reader->ChangeSheet($i);
-
-      foreach ($Reader as $Row)
-      {
-        $html.="<tr>";
-        $title = isset($Row[0]) ? $Row[0] : '';
-        $description = isset($Row[1]) ? $Row[1] : '';
-        $html.="<td>".id."</td>";
-        $html.="<td>".analysis_type."</td>";
-        $html.="<td>".lab_no."</td>";
-        $html.="<td>".batch_no."</td>";
-        $html.="<td>".sample_id."</td>";
-        $html.="<td>".site_id."</td>";
-        $html.="<td>".crop."</td>";
-        $html.="<td>".date_sampled."</td>";
-        $html.="<td>".lab_no."</td>";
-        $html.="<td>".batch_no."</td>";
-        $html.="<td>".sample_id."</td>";
-        $html.="<td>".site_id."</td>";
-        $html.="<td>".tec."</td>";
-        $html.="<td>".cec."</td>";
-        $html.="<td>".texture."</td>";
-        $html.="<td>".gravel."</td>";
-        $html.="<td>".colour."</td>";
-        $html.="<td>".NO3_N."</td>";
-        $html.="<td>".NH3_N."</td>";
-        $html.="<td>".p_mehlick."</td>";
-        $html.="<td>".p_bray2."</td>";
-        $html.="<td>".p_morgan."</td>";
-        $html.="<td>".k_morgan."</td>";
-        $html.="<td>".ca_morgan."</td>";
-        $html.="<td>".mg_morgan."</td>";
-        $html.="<td>".na_morgan."</td>";
-        $html.="<td>".ch_h2o."</td>";
-        $html.="<td>".ocarbon."</td>";
-        $html.="<td>".omatter."</td>";
-        $html.="<td>".fe."</td>";
-        $html.="<td>".ec."</td>";
-        $html.="<td>".ph_cacl2."</td>";
-        $html.="<td>".ph_h2o."</td>";
-        $html.="<td>".paramag."</td>";
-        $html.="<td>".s_morgan."</td>";
-        $html.="<td>".b_cacl2."</td>";
-        $html.="<td>".mn_dtpa."</td>";
-        $html.="<td>".zn_dtpa."</td>";
-        $html.="<td>".fe_dtpa."</td>";
-        $html.="<td>".cu_dtpa."</td>";
-        $html.="<td>".al."</td>";
-        $html.="<td>".sl_cacl2."</td>";
-        $html.="<td>".m_dtpa."</td>";
-        $html.="<td>".co_dtpa."</td>";
-        $html.="<td>".se."</td>";
-        $html.="<td>".ca_mehlick3."</td>";
-        $html.="<td>".mg_mehlick3."</td>";
-        $html.="<td>".k_mehlick3."</td>";
-        $html.="<td>".na_mehlick3."</td>";
-        $html.="<td>".al_mehlick3."</td>";
-        $html.="</tr>";
-
-        $query = "insert into soil_records(
-            analysis_type,
-            lab_no,
-            batch_no,
-            sample_id,
-            site_id,
-            crop,
-            date_sampled,
-            lab_no,
-            batch_no,
-            sample_id,
-            site_id,
-            tec,
-            cec,
-            texture,
-            gravel,
-            colour,
-            NO3_N,
-            NH3_N,
-            p_mehlick,
-            p_bray2,
-            p_morgan,
-            k_morgan,
-            ca_morgan,
-            mg_morgan,
-            na_morgan,
-            ch_h2o,
-            ocarbon,
-            omatter,
-            fe,
-            ec,
-            ph_cacl2,
-            ph_h2o,
-            paramag,
-            s_morgan,
-            b_cacl2,
-            mn_dtpa,
-            zn_dtpa,
-            fe_dtpa,
-            cu_dtpa,
-            al,
-            sl_cacl2,
-            m_dtpa,
-            co_dtpa,
-            se,
-            ca_mehlick3,
-            mg_mehlick3,
-            k_mehlick3,
-            na_mehlick3,
-            al_mehlick3
-        ) values(
-            '" . $analysis_type . "',
-            '" . $lab_no . "',
-            '" . $batch_no . "',
-            '" . $sample_id . "',
-            '" . $site_id . "',
-            '" . $crop . "',
-            '" . $date_sampled . "',
-            '" . $lab_no . "',
-            '" . $batch_no . "',
-            '" . $sample_id . "',
-            '" . $site_id . "',
-            '" . $tec . "',
-            '" . $cec . "',
-            '" . $texture . "',
-            '" . $gravel . "',
-            '" . $colour . "',
-            '" . $NO3_N . "',
-            '" . $NH3_N . "',
-            '" . $p_mehlick . "',
-            '" . $p_bray2 . "',
-            '" . $p_morgan . "',
-            '" . $k_morgan . "',
-            '" . $ca_morgan . "',
-            '" . $mg_morgan . "',
-            '" . $na_morgan . "',
-            '" . $ch_h2o . "',
-            '" . $ocarbon . "',
-            '" . $omatter . "',
-            '" . $fe . "',
-            '" . $ec . "',
-            '" . $ph_cacl2 . "',
-            '" . $ph_h2o . "',
-            '" . $paramag . "',
-            '" . $s_morgan . "',
-            '" . $b_cacl2 . "',
-            '" . $mn_dtpa . "',
-            '" . $zn_dtpa . "',
-            '" . $fe_dtpa . "',
-            '" . $cu_dtpa . "',
-            '" . $al . "',
-            '" . $sl_cacl2 . "',
-            '" . $m_dtpa . "',
-            '" . $co_dtpa . "',
-            '" . $se . "',
-            '" . $ca_mehlick3 . "',
-            '" . $mg_mehlick3 . "',
-            '" . $k_mehlick3 . "',
-            '" . $na_mehlick3 . "',
-            '" . $al_mehlick3 . "'
-        )";
-
-        $mysqli->query($query);
-       }
-
+/**
+ * dashboard/crop-analysis/uploadsubmit.php
+ *
+ * Spreadsheet bulk-upload handler for soil records.
+ * Requires the SpreadsheetReader library.
+ *
+ * NOTE: Column index → DB field mapping below must match the actual
+ * spreadsheet format provided by the lab. Update $colMap as needed.
+ */
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+require_once __DIR__ . '/../../config/database.php';
+require_once __DIR__ . '/../../lib/auth.php';
+
+requireLogin();
+
+$readerBase = __DIR__ . '/../../client-assets/php/spreadsheet';
+$readerFile = $readerBase . '/php-excel-reader/excel_reader2.php';
+$spreadFile = $readerBase . '/SpreadsheetReader.php';
+
+if (!file_exists($readerFile) || !file_exists($spreadFile)) {
+    http_response_code(500);
+    exit('<p class="text-danger">Spreadsheet reader library not found.</p>');
+}
+
+require_once $readerFile;
+require_once $spreadFile;
+
+if (!isset($_POST['Submit'])) {
+    exit;
+}
+
+$allowedMimes = [
+    'application/vnd.ms-excel',
+    'text/xls', 'text/xlsx', 'text/csv',
+    'application/vnd.oasis.opendocument.spreadsheet',
+    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+];
+
+if (!in_array($_FILES['file']['type'] ?? '', $allowedMimes, true)) {
+    exit('<p class="text-danger">Invalid file type. Please upload an Excel or CSV file.</p>');
+}
+
+$uploadDir  = __DIR__ . '/../../client-assets/uploads/';
+$uploadPath = $uploadDir . basename($_FILES['file']['name']);
+
+if (!move_uploaded_file($_FILES['file']['tmp_name'], $uploadPath)) {
+    exit('<p class="text-danger">Failed to upload file.</p>');
+}
+
+$reader     = new SpreadsheetReader($uploadPath);
+$totalSheet = count($reader->sheets());
+
+echo '<p>File uploaded — ' . (int) $totalSheet . ' sheet(s) found.</p>';
+
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+
+/**
+ * Map spreadsheet column indices to soil_records column names.
+ * Adjust indices to match your actual spreadsheet layout.
+ */
+$colMap = [
+    0  => 'analysis_type',
+    1  => 'lab_no',
+    2  => 'batch_no',
+    3  => 'sample_id',
+    4  => 'site_id',
+    5  => 'crop',
+    6  => 'date_sampled',
+    7  => 'tec',
+    8  => 'cec',
+    9  => 'texture',
+    10 => 'gravel',
+    11 => 'colour',
+    12 => 'NO3_N',
+    13 => 'NH3_N',
+    14 => 'p_mehlick',
+    15 => 'p_bray2',
+    16 => 'p_morgan',
+    17 => 'k_morgan',
+    18 => 'ca_morgan',
+    19 => 'mg_morgan',
+    20 => 'na_morgan',
+    21 => 'ch_h2o',
+    22 => 'ocarbon',
+    23 => 'omatter',
+    24 => 'fe',
+    25 => 'ec',
+    26 => 'ph_cacl2',
+    27 => 'ph_h2o',
+    28 => 'paramag',
+    29 => 's_morgan',
+    30 => 'b_cacl2',
+    31 => 'mn_dtpa',
+    32 => 'zn_dtpa',
+    33 => 'fe_dtpa',
+    34 => 'cu_dtpa',
+    35 => 'al',
+    36 => 'sl_cacl2',
+    37 => 'm_dtpa',
+    38 => 'co_dtpa',
+    39 => 'se',
+    40 => 'ca_mehlick3',
+    41 => 'mg_mehlick3',
+    42 => 'k_mehlick3',
+    43 => 'na_mehlick3',
+    44 => 'al_mehlick3',
+];
+
+$columns  = array_values($colMap);
+$placeholders = implode(', ', array_fill(0, count($columns) + 2, '?'));
+$colList  = 'modx_user_id, rand, ' . implode(', ', array_map(fn($c) => "`$c`", $columns));
+
+$stmt = $pdo->prepare(
+    "INSERT INTO soil_records ($colList) VALUES ($placeholders)"
+);
+
+$inserted = 0;
+$skipped  = 0;
+
+for ($i = 0; $i < $totalSheet; $i++) {
+    $reader->ChangeSheet($i);
+    $firstRow = true;
+    foreach ($reader as $row) {
+        if ($firstRow) { $firstRow = false; continue; } // skip header row
+
+        $rand   = mt_rand(10000, 99999);
+        $values = [$userId, $rand];
+
+        foreach ($colMap as $idx => $colName) {
+            $val = isset($row[$idx]) ? trim((string) $row[$idx]) : null;
+            $values[] = ($val === '') ? null : $val;
+        }
+
+        try {
+            $stmt->execute($values);
+            $inserted++;
+        } catch (\PDOException $e) {
+            $skipped++;
+        }
     }
+}
 
-    $html.="</table>";
-    echo $html;
-    echo "<br />Data Inserted in dababase";
-
-  }else { 
-    die("<br/>Sorry, Incorrect file."); 
-  }
+// Remove uploaded file after processing
+@unlink($uploadPath);
 
-}
+echo '<p class="text-success">Import complete: '
+   . (int) $inserted . ' record(s) inserted, '
+   . (int) $skipped  . ' skipped.</p>';

+ 3 - 0
dashboard/crop-analysis/water-test-data/index.php

@@ -0,0 +1,3 @@
+<?php
+header('Location: /dashboard/crop-analysis/water-test-data/water-test-data.php');
+exit;

+ 259 - 271
dashboard/crop-analysis/water-test-data/water-analysis-pdf.php

@@ -1,292 +1,280 @@
+<?php
+/**
+ * water-analysis-pdf.php
+ *
+ * Displays water quality analysis results for a single water_records row.
+ * Standalone PDF-printable page. Accessed via rid + rand URL params.
+ */
+
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+requireLogin();
+
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+
+$recordId = (int)   ($_GET['rid']  ?? 0);
+$randId   = (float) ($_GET['rand'] ?? 0);
+
+$row   = null;
+$specs = [];
+
+if ($recordId > 0) {
+    $stmt = $pdo->prepare('SELECT * FROM water_records WHERE id = ? AND rand = ? LIMIT 1');
+    $stmt->execute([$recordId, $randId]);
+    $row = $stmt->fetch();
+}
+
+if ($row) {
+    $type = $row['analysis_type'] ?? '';
+    $stmt = $pdo->prepare('SELECT * FROM water_specifications WHERE type = ? LIMIT 1');
+    $stmt->execute([$type]);
+    $specs = $stmt->fetch() ?: [];
+}
+
+$h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
+
+function waterBar(float $found, float $min, float $max): string {
+    if ($max <= 0) return '<td colspan="3" class="text-muted text-center small">N/A</td>';
+    $pct = ($max > $min) ? min(100, max(0, ($found - $min) / ($max - $min) * 100)) : 0;
+    if ($found < $min) {
+        return '<td class="text-center"><div class="progress"><div class="progress-bar bg-danger" style="width:100%"></div></div></td>'
+             . '<td></td><td></td>';
+    } elseif ($found > $max) {
+        return '<td></td><td></td>'
+             . '<td class="text-center"><div class="progress"><div class="progress-bar bg-warning" style="width:100%"></div></div></td>';
+    } else {
+        return '<td></td>'
+             . '<td class="text-center"><div class="progress"><div class="progress-bar bg-success" style="width:' . round($pct) . '%"></div></div></td>'
+             . '<td></td>';
+    }
+}
+?>
 <!doctype html>
 <html lang="en">
-
 <head>
-	<title>[[*longtitle]] | [[++site_name]]</title>
-	<base href="[[!++site_url]]">
-	<meta charset="[[++modx_charset]]">
-	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
-	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-	<meta name="keywords" content="[[*introtext]]">
-	<meta name="description" content="[[*description]]">
-	<link rel="icon" href="client-assets/images/favicon.ico?v=2" type="image/x-icon"> [[!Profile]]
-	
-	<script type="text/javascript">
-    	window.dataLayer = window.dataLayer || [];
-    
-    	function gtag() {
-    		dataLayer.push(arguments);
-    	}
-    	gtag('js', new Date());
-    	gtag('set', {
-    		'user_id': '[[+modx.user.id]]'
-    	}); // Set the user ID using signed-in user_id.
-    	gtag('config', 'UA-133963301-1');
-	</script>
-	
-	<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" />
-	<link href="client-assets/css/dashboard-2021.css" rel="stylesheet" />
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js" crossorigin="anonymous"></script>
-	<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
-	<script src="client-assets/js/skycons.js"></script>
-	<style>
-    	.btn-append {
-    		color: #495057;
-    		background-color: #e9ecef;
-    		border: 1px solid #ced4da;
-    	}
-    	.footer {
-    		position: absolute;
-    		bottom: 0;
-    		width: 100%;
-    		height: 60px;
-    		line-height: 60px;
-    	}
-	</style>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Water Analysis | Crop Monitor</title>
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
+    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.css" crossorigin="anonymous" rel="stylesheet">
+    <link href="/client-assets/css/dashboard.css" rel="stylesheet">
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js" crossorigin="anonymous"></script>
+    <style>
+        @media print {
+            @page { size: A4 portrait; margin: 0.5cm; }
+            .d-print-none { display: none !important; }
+        }
+        .progress { border-radius: 0 !important; }
+        table.chart { width: 100%; border-collapse: collapse; }
+        table.chart th, table.chart td { padding: 3px 6px; font-size: 0.85rem; }
+        .chart-header th { background: #343a40; color: white; }
+        .chart-header-sub th { background: #6c757d; color: white; }
+        .lightgreen  { background: #d4edda !important; color: #155724 !important; }
+        .lightred    { background: #f8d7da !important; color: #721c24 !important; }
+        .lightblue   { background: #cce5ff !important; color: #004085 !important; }
+        .lightpurple { background: #e2d9f3 !important; color: #432874 !important; }
+        .stripe-1    { background: #f8f9fa; }
+        .border-left   { border-left:   1px solid #dee2e6; }
+        .border-right  { border-right:  1px solid #dee2e6; }
+        .border-bottom { border-bottom: 1px solid #dee2e6; }
+        .border-top    { border-top:    1px solid #dee2e6; }
+    </style>
 </head>
+<body>
+<div class="container" id="content">
+
+    <?php if (!$row): ?>
+        <div class="alert alert-danger mt-4">Record not found or access denied.</div>
+    <?php else: ?>
 
-<body class="sb-nav-fixed" id="page-top"> 
-    [[!Personalize?
-        &yesChunk=`navHeaderLogged`
-        &noChunk=`navHeader`
-        &allowedGroups=`basicClients,bacicAdmin,companyClients,companyManagers`
-    ]]
-    
-	<div id="layoutSidenav">
-	    <div id="layoutSidenav_nav">
-    		<!-- Sidebar -->
-    		[[Wayfinder?
-    		    &startId=`2` 
-        		&displayStart=`0` 
-        		&startitemTpl=`startitemTpl` 
-        		&selfClass=`show` 
-        		&level=`2`
-        		&outerTpl=`outer`
-        		    &outerClass=`sb-sidenav accordion sb-sidenav-dark`
-        		&rowTpl=`row`
-        		    &rowClass=`nav-link`
-        		&parentRowTpl=`parentRow`
-        		    &parentClass=`nav-link collapsed`
-        		&innerTpl=`inner`
-        		    &innerClass=`collapse`
-        		&innerRowTpl=`secondInner`
-        		    &innerRowClass=`nav-link`
-    		]]
+    <div class="row mb-3">
+        <div class="col-md-3">
+            <img class="img-fluid" src="/client-assets/images/crop-monitor.png" alt="Crop Monitor">
         </div>
-		
-		<div id="layoutSidenav_content">
-			<main>
+    </div>
 
-				<div class="container-fluid px-4">
-				    <h1 class="mt-4">[[*pagetitle]]</h1>
+    <div class="d-print-none mb-3">
+        <button class="btn btn-success btn-sm downloadPDF">
+            <i class="fas fa-download me-1"></i>Download PDF
+        </button>
+    </div>
 
-					<ol class="breadcrumb mb-4"> [[$dash-breadcrumbs]] </ol>
-					
-					<div class="row">
-					    [[!pdf-header]]
+    <!-- Client info -->
+    <table class="table table-sm table-bordered mb-3" style="font-size:0.85rem;">
+        <tbody>
+            <tr>
+                <th class="w-25">Client</th>
+                <td><?= $h($row['client_name'] ?? '') ?></td>
+                <th class="w-25">Lab No</th>
+                <td><?= $h($row['lab_no'] ?? '') ?></td>
+            </tr>
+            <tr>
+                <th>Sample ID</th>
+                <td><?= $h($row['sample_id'] ?? '') ?></td>
+                <th>Site ID</th>
+                <td><?= $h($row['site_id'] ?? '') ?></td>
+            </tr>
+            <tr>
+                <th>Analysis Type</th>
+                <td><?= $h($row['analysis_type'] ?? '') ?></td>
+                <th>Date Sampled</th>
+                <td><?= $h($row['date_sampled'] ?? '') ?></td>
+            </tr>
+        </tbody>
+    </table>
 
-<div class="grid">
-    [[!logoHeader]]
-    [[!waterAnalysisClient]]
-    
-    <div class="clearfix"></div>
-    
-    
-    <!-- GRAPH BANNER -->
-    
-    <div class="nav-wrap">
-        <div class="graph-header text-center" style="width: 100%;">ANALYSIS RESULTS</div>
+    <div class="row">
+        <div class="col-md-12 text-center fw-bold h4">ANALYSIS RESULTS</div>
     </div>
-    
-    <div class="clearfix"></div>
-    <hr>
-    
-    <!-- CHART HEADER  -->
-    
+    <hr class="p-1 m-1">
+
     <table class="chart">
         <tbody>
-          <tr class="chart-header">
-            <th colspan=3 class="black text-center col-50 border-left border-right border-top">ELEMENT</th>
-            <th colspan=3 class="black text-center col-50 border-right border-top">STATUS</th>
-          </tr>
-          
-          <tr class="chart-header-sub">
-            <th class="black text-center col-16 border-bottom border-left"></th>
-            <th class="black text-center col-16 border-bottom">DESIRED</th>
-            <th class="black text-center col-16 border-bottom">FOUND</th>
-            <th class="text-center col-16 border-left stripe-1">LIGHT</th>
-            <th class="text-center col-16 border-left stripe-1">MEDIUM</th>
-            <th class="text-center col-16 border-left border-right stripe-1">HEAVY</th>
-          </tr>
-          
-          <tr>
-            <td class="border-left"></td>
-            <td class="border-left"></td>
-            <td class="border-left nutrient-balance"></td>
-            <td class="border-left"></td>
-            <td class="border-left"></td>
-            <td class="border-left border-right"></td>
-          </tr>
-          
-          [[!waterAnalysisCalcs? &element=cec  &nutrient=`CEC` &graph=lightorangeGraph]]
-          [[!waterAnalysisCalcs? &element=tec  &nutrient=`TEC` &graph=lightorangeGraph]]
-          
-          <tr class="chart-header-sub">
-            <th class="text-center col-16 border-left white"></th>
-            <th class="text-center col-16 border-left white"></th>
-            <th class="text-center col-16 border-left nutrient-balance"></th>
-            <th class="text-center col-16 border-left stripe-1">DEFICIT</th>
-            <th class="text-center col-16 border-left stripe-1">IDEAL</th>
-            <th class="text-center col-16 border-left border-right stripe-1">HIGH</th>
-          </tr>
-          
-          [[!waterAnalysisCalcs? &element=ph_h2o  &nutrient=`pH-level (H20)` &graph=lightorangeGraph]]
-          [[!waterAnalysisCalcs? &element=ph_cacl2  &nutrient=`pH-level (CaCl2)` &graph=lightorangeGraph]]
-          [[!waterAnalysisCalcs? &element=ocarbon  &nutrient=`Organic Carbon` &graph=lightorangeGraph]]
-          [[!waterAnalysisCalcs? &element=omatter  &nutrient=`Organic Matter` &graph=lightorangeGraph]]
-        
-          <tr class="chart-header-sub">
-            <th colspan=3 class="col-16 border-left text-center lightblue">RATIOS</th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left border-right stripe-1"></th>
-          </tr>
-          
-          [[!soilAnalysisRatio? &element=`ca_mehlick3` &elementTwo=`mg_mehlick3`  &rec=`ca_mg_ratio` &nutrient=`Ca:Mg Ratio` &graph=lightblueGraph]]
-          [[!waterAnalysisCalcs? &element=NH3_N  &nutrient=`Total Nitrogen` &type=`%` &graph=lightblueGraph]]
-          [[!waterAnalysisCalcs? &element=p_mehlick  &nutrient=`Total Carbon` &type=`%` &graph=lightblueGraph]]
-          [[!soilAnalysisRatio? &element=`p_bray2` &elementTwo=`NO3_N` &rec=`c_n_ratio` &nutrient=`C:N Ratio` &graph=lightblueGraph]]
-          
-         
-          <tr class="chart-header-sub">
-            <th colspan=3 class="col-16 border-left text-center lightgreen">MAJOR ELEMENTS</th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left border-right stripe-1"></th>
-          </tr>
-          
-          [[!waterAnalysisCalcs? &element=NO3_N  &nutrient=`Nitrate Nitrogen` &type=ppm &graph=lightgreenGraph]]
-          [[!waterAnalysisCalcs? &element=NH3_N  &nutrient=`Ammonium Nitrogen` &type=ppm &graph=lightgreenGraph]]
-          [[!waterAnalysisCalcs? &element=p_mehlick  &nutrient=`Phosphorus (mehlick III)` &type=ppm &graph=lightgreenGraph]]
-          [[!waterAnalysisCalcs? &element=p_bray2  &nutrient=`Phosphorus (Bray 2)` &type=ppm &graph=lightgreenGraph]]
-          [[!waterAnalysisCalcs? &element=p_morgan  &nutrient=`Phosphate (morgan)` &type=ppm &graph=lightgreenGraph]]
-        
-          <tr class="chart-header-sub">
-            <th colspan=3 class="col-16 border-left text-center lightred">TRACE ELEMENTS</th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left border-right stripe-1"></th>
-          </tr>
-          
-          [[!waterAnalysisCalcs? &element=s_morgan  &nutrient=`Sulfur` &type=ppm &graph=lightredGraph]]
-          [[!waterAnalysisCalcs? &element=b_cacl2  &nutrient=`Boron` &type=ppm &graph=lightredGraph]]
-          [[!waterAnalysisCalcs? &element=mn_dtpa  &nutrient=`Manganese` &type=ppm &graph=lightredGraph]]
-          [[!waterAnalysisCalcs? &element=cu_dtpa  &nutrient=`Copper` &type=ppm &graph=lightredGraph]]
-          [[!waterAnalysisCalcs? &element=zn_dtpa  &nutrient=`Zinc` &type=ppm &graph=lightredGraph]]
-          [[!waterAnalysisCalcs? &element=fe_dtpa  &nutrient=`Iron` &type=ppm &graph=lightredGraph]]
-          [[!waterAnalysisCalcs? &element=al  &nutrient=`Aluminium` &type=ppm &graph=lightredGraph]]
-          [[!waterAnalysisCalcs? &element=sl_cacl2  &nutrient=`Silicon` &type=ppm &graph=lightredGraph]]
-        
-          <tr class="chart-header-sub">
-            <th colspan=3 class="col-16 border-left text-center lightpurple">BASE SATURATION</th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left border-right stripe-1"></th>
-          </tr>
-          
-          [[!waterAnalysisCalcs? &element=`BS_ca2`  &nutrient=`Calcium` &type=`%` &graph=lightpurpleGraph]]
-          [[!waterAnalysisCalcs? &element=`BS_mg2`  &nutrient=`Magnesium` &type=`%` &graph=lightpurpleGraph]]
-          [[!waterAnalysisCalcs? &element=`BS_k`  &nutrient=`Potassium` &type=`%` &graph=lightpurpleGraph]]
-          [[!waterAnalysisCalcs? &element=`BS_na`  &nutrient=`Sodium` &type=`%` &graph=lightpurpleGraph]]
-          [[!waterAnalysisCalcs? &element=`BS_al3`  &nutrient=`Other Bases` &type=`%` &graph=lightpurpleGraph]]
-          [[!waterAnalysisCalcs? &element=`BS_h`  &nutrient=`Hydrogen` &type=`%` &graph=lightpurpleGraph]]
-          
-          <tr class="chart-header-sub">
-            <th colspan=3 class="col-16 border-left text-center lightgrey">SOLUBLE MORGAN 2 EXTRACT</th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left stripe-1"></th>
-            <th class="text-center col-16 border-left border-right stripe-1"></th>
-          </tr>
-          
-          [[!waterAnalysisCalcs? &element=s_morgan  &nutrient=`Calcium` &type=`%` &graph=lightgreyGraph]]
-          [[!waterAnalysisCalcs? &element=b_cacl2  &nutrient=`Magnesium` &type=`%` &graph=lightgreyGraph]]
-          [[!waterAnalysisCalcs? &element=mn_dtpa  &nutrient=`Potassium` &type=`%` &graph=lightgreyGraph]]
+            <tr class="chart-header">
+                <th colspan="3" class="text-center border-left border-right border-top">ELEMENT</th>
+                <th colspan="3" class="text-center border-right border-top">STATUS</th>
+            </tr>
+            <tr class="chart-header-sub">
+                <th class="text-center border-bottom border-left">Parameter</th>
+                <th class="text-center border-bottom">Desired</th>
+                <th class="text-center border-bottom">Found</th>
+                <th class="text-center stripe-1">Deficit</th>
+                <th class="text-center stripe-1">Ideal</th>
+                <th class="text-center border-right stripe-1">High</th>
+            </tr>
 
-          <tr class="chart-header-sub">
-            <th colspan=3 class="col-16 border-left text-center lightgrey">ADDITIONAL DATA</th>
-            <th class="text-center col-16 border-left stripe-1">LOW</th>
-            <th class="text-center col-16 border-left stripe-1">IDEAL</th>
-            <th class="text-center col-16 border-left border-right stripe-1">EXCELLENT</th>
-          </tr>
-          
-          [[!waterAnalysisCalcs? &element=s_morgan  &nutrient=`Calcium` &type=`%` &graph=lightgreyGraph]]
+            <tr><td class="border-left" colspan="6"></td></tr>
+            <tr class="chart-header-sub">
+                <th colspan="3" class="border-left text-center lightblue">GENERAL PARAMETERS</th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left border-right stripe-1"></th>
+            </tr>
 
-          <tr>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left nutrient-balance"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left"></td>
-            <td class="border-bottom border-left border-right"></td>
-          </tr>
-        </tbody>
-    </table>
+            <?php
+            $generalParams = [
+                ['ph',       'pH',                          ''],
+                ['cond_dsm', 'Conductivity',                'dS/m'],
+                ['hco3-',    'Bicarbonate (HCO₃)',          'ppm'],
+            ];
+            foreach ($generalParams as [$col, $label, $unit]):
+                $found = (float)($row[$col] ?? 0);
+                $min   = (float)($specs[$col . '_min'] ?? 0);
+                $max   = (float)($specs[$col . '_max'] ?? 0);
+                $desired = ($min > 0 || $max > 0) ? number_format($min, 2) . '–' . number_format($max, 2) : '—';
+                $display = $unit ? $label . ' (' . $unit . ')' : $label;
+            ?>
+            <tr>
+                <td class="border-left"><?= $h($display) ?></td>
+                <td><?= $h($desired) ?></td>
+                <td><?= $found > 0 ? number_format($found, 3) : '—' ?></td>
+                <?= waterBar($found, $min, $max) ?>
+            </tr>
+            <?php endforeach; ?>
+
+            <tr><td class="border-left" colspan="6"></td></tr>
+            <tr class="chart-header-sub">
+                <th colspan="3" class="border-left text-center lightgreen">MAJOR ELEMENTS</th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left border-right stripe-1"></th>
+            </tr>
+
+            <?php
+            $majorElements = [
+                ['nh4', 'Ammonium Nitrogen (NH₄)', 'ppm'],
+                ['no3', 'Nitrate Nitrogen (NO₃)',  'ppm'],
+                ['p',   'Phosphorus',               'ppm'],
+                ['k',   'Potassium',                'ppm'],
+                ['s',   'Sulphur',                  'ppm'],
+                ['ca',  'Calcium',                  'ppm'],
+                ['mg',  'Magnesium',                'ppm'],
+                ['na',  'Sodium',                   'ppm'],
+            ];
+            foreach ($majorElements as [$col, $label, $unit]):
+                $found = (float)($row[$col] ?? 0);
+                $min   = (float)($specs[$col . '_min'] ?? 0);
+                $max   = (float)($specs[$col . '_max'] ?? 0);
+                $desired = ($min > 0 || $max > 0) ? number_format($min, 1) . '–' . number_format($max, 1) : '—';
+            ?>
+            <tr>
+                <td class="border-left"><?= $h($label) ?> (<?= $h($unit) ?>)</td>
+                <td><?= $h($desired) ?></td>
+                <td><?= $found > 0 ? number_format($found, 2) : '—' ?></td>
+                <?= waterBar($found, $min, $max) ?>
+            </tr>
+            <?php endforeach; ?>
 
-    <div class="clearfix"></div>
-					</div>
+            <tr><td class="border-left" colspan="6"></td></tr>
+            <tr class="chart-header-sub">
+                <th colspan="3" class="border-left text-center lightred">TRACE ELEMENTS</th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left stripe-1"></th>
+                <th class="text-center border-left border-right stripe-1"></th>
+            </tr>
 
-				</div>
+            <?php
+            $traceElements = [
+                ['fe', 'Iron',      'ppm'],
+                ['mn', 'Manganese', 'ppm'],
+                ['zn', 'Zinc',      'ppm'],
+                ['cu', 'Copper',    'ppm'],
+                ['b',  'Boron',     'ppm'],
+                ['m',  'Molybdenum','ppm'],
+                ['co', 'Cobalt',    'ppm'],
+            ];
+            foreach ($traceElements as [$col, $label, $unit]):
+                $found = (float)($row[$col] ?? 0);
+                $min   = (float)($specs[$col . '_min'] ?? 0);
+                $max   = (float)($specs[$col . '_max'] ?? 0);
+                $desired = ($min > 0 || $max > 0) ? number_format($min, 2) . '–' . number_format($max, 2) : '—';
+            ?>
+            <tr>
+                <td class="border-left"><?= $h($label) ?> (<?= $h($unit) ?>)</td>
+                <td><?= $h($desired) ?></td>
+                <td><?= $found > 0 ? number_format($found, 3) : '—' ?></td>
+                <?= waterBar($found, $min, $max) ?>
+            </tr>
+            <?php endforeach; ?>
 
-			</main>
-			
-			<footer class="py-4 bg-light mt-auto">
-                <div class="container-fluid px-4">
-                    <div class="d-flex align-items-center justify-content-between small">
-                        <div class="text-muted">[[SimpleCopyright? &startYear=`2003`]]. All Rights Reserved <a href="[[~1]]"></a></div>
-                        <div>
-                            <a href="[[~39~]]">Privacy Policy</a>
-                            &middot;
-                            <a href="[[~39~]]">Terms &amp; Conditions</a>
-                        </div>
-                    </div>
-                </div>
-            </footer>
-            
-		</div>
+            <tr>
+                <td class="border-bottom border-left"></td>
+                <td class="border-bottom"></td>
+                <td class="border-bottom"></td>
+                <td class="border-bottom"></td>
+                <td class="border-bottom"></td>
+                <td class="border-bottom border-right"></td>
+            </tr>
+        </tbody>
+    </table>
 
-	</div>
+    <div class="mt-4 small text-muted">
+        <p><i class="fa fa-tint" style="color:#007bff"></i> Water analysis results should be interpreted in the context of intended use (irrigation, drinking, stock water).</p>
+        <p><i class="fa fa-tint" style="color:#007bff"></i> Talk to your qualified consultant to make a plan for correction or maintenance of the found parameter levels.</p>
+        <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>
+    </div>
 
-	<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-	<script>
-    	/*!
-            * Start Bootstrap - SB Admin v7.0.3 (https://startbootstrap.com/template/sb-admin)
-            * Copyright 2013-2021 Start Bootstrap
-            * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-sb-admin/blob/master/LICENSE)
-            */
-            // 
-        // Scripts
-        // 
-        
-        window.addEventListener('DOMContentLoaded', event => {
-        
-            // Toggle the side navigation
-            const sidebarToggle = document.body.querySelector('#sidebarToggle');
-            if (sidebarToggle) {
-                // Uncomment Below to persist sidebar toggle between refreshes
-                // if (localStorage.getItem('sb|sidebar-toggle') === 'true') {
-                //     document.body.classList.toggle('sb-sidenav-toggled');
-                // }
-                sidebarToggle.addEventListener('click', event => {
-                    event.preventDefault();
-                    document.body.classList.toggle('sb-sidenav-toggled');
-                    localStorage.setItem('sb|sidebar-toggle', document.body.classList.contains('sb-sidenav-toggled'));
-                });
-            }
-        
-        });
+    <?php endif; ?>
+</div>
 
-	</script>
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js" crossorigin="anonymous"></script>
-	<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest" crossorigin="anonymous"></script>
+<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
+<script>
+document.querySelector('.downloadPDF') && document.querySelector('.downloadPDF').addEventListener('click', function () {
+    var opt = {
+        margin: 3,
+        filename: 'water-analysis.pdf',
+        image: { type: 'jpeg', quality: 1.0 },
+        html2canvas: { scale: 2, letterRendering: true, windowWidth: 1024 },
+        jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' }
+    };
+    html2pdf().from(document.getElementById('content')).set(opt).save();
+});
+</script>
 </body>
-</html>
+</html>

+ 229 - 474
dashboard/crop-analysis/water-test-data/water-test-data.php

@@ -1,507 +1,262 @@
-<!doctype html>
-<html lang="en">
+<?php
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
+require_once __DIR__ . '/../../../lib/csrf.php';
 
-<head>
-	<title>[[*longtitle]] | [[++site_name]]</title>
-	<base href="[[!++site_url]]">
-	<meta charset="[[++modx_charset]]">
-	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
-	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-	<meta name="keywords" content="[[*introtext]]">
-	<meta name="description" content="[[*description]]">
-	<link rel="icon" href="client-assets/images/favicon.ico?v=2" type="image/x-icon"> [[!Profile]]
-	
-	<script type="text/javascript">
-    	window.dataLayer = window.dataLayer || [];
-    
-    	function gtag() {
-    		dataLayer.push(arguments);
-    	}
-    	gtag('js', new Date());
-    	gtag('set', {
-    		'user_id': '[[+modx.user.id]]'
-    	}); // Set the user ID using signed-in user_id.
-    	gtag('config', 'UA-133963301-1');
-	</script>
-	
-	<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" />
-	<link href="client-assets/css/dashboard-2021.css" rel="stylesheet" />
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js" crossorigin="anonymous"></script>
-	<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
-	<script src="client-assets/js/skycons.js"></script>
-	<style>
-    	.btn-append {
-    		color: #495057;
-    		background-color: #e9ecef;
-    		border: 1px solid #ced4da;
-    	}
-    	.footer {
-    		position: absolute;
-    		bottom: 0;
-    		width: 100%;
-    		height: 60px;
-    		line-height: 60px;
-    	}
-	</style>
-</head>
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
 
-<body class="sb-nav-fixed" id="page-top"> 
-    [[!Personalize?
-        &yesChunk=`navHeaderLogged`
-        &noChunk=`navHeader`
-        &allowedGroups=`basicClients,bacicAdmin,companyClients,companyManagers`
-    ]]
-    
-	<div id="layoutSidenav">
-	    <div id="layoutSidenav_nav">
-    		<!-- Sidebar -->
-    		[[Wayfinder?
-    		    &startId=`2` 
-        		&displayStart=`0` 
-        		&startitemTpl=`startitemTpl` 
-        		&selfClass=`show` 
-        		&level=`2`
-        		&outerTpl=`outer`
-        		    &outerClass=`sb-sidenav accordion sb-sidenav-dark`
-        		&rowTpl=`row`
-        		    &rowClass=`nav-link`
-        		&parentRowTpl=`parentRow`
-        		    &parentClass=`nav-link collapsed`
-        		&innerTpl=`inner`
-        		    &innerClass=`collapse`
-        		&innerRowTpl=`secondInner`
-        		    &innerRowClass=`nav-link`
-    		]]
-        </div>
-		
-		<div id="layoutSidenav_content">
-			<main>
+requireLogin();
 
-				<div class="container-fluid px-4">
-				    <h1 class="mt-4">[[*pagetitle]]</h1>
+$pageTitle = 'Water Test Analysis';
+$siteName  = 'Crop Monitor';
 
-					<ol class="breadcrumb mb-4"> [[$dash-breadcrumbs]] </ol>
-					
-					<div class="row">
-					    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js" integrity="sha256-gfQwA6PlkZsLqWu4bU4hXPrbTqzixm0B5MdvBLI+Oas=" crossorigin="anonymous"></script>
+include __DIR__ . '/../../../layouts/header.php';
+include __DIR__ . '/../../../layouts/navbar.php';
+?>
 
-<div class="grid-form1">
-<h3 id="forms-example" class="">[[*longtitle]]</h3>
-
-<span class="error">* required fields.</span>
-
-<form method="post" action="[[~34~]]" id="WatercsvForm"class="needs-validation" novalidate >
+<style>
+    .btn-append {
+        color: #495057;
+        background-color: #e9ecef;
+        border: 1px solid #ced4da;
+    }
+</style>
 
-    [[!Personalize?
-        &yesChunk=`analysisLogged_Clientdetails`
-        &noChunk=`analysis_Clientdetails`
-    ]]
-    
-    <hr>
-    
-    <div class="row">
-    
-    <div class="col ">
-        <small id="lab_no" class="form-text text-muted">Lab No</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="lab_no" id="lab_no" placeholder="Lab No"></input>
-        
-        </div>
-    </div>
-    
-    <div class="col ">
-        <small id="batch_no" class="form-text text-muted">Batch No</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="batch_no" id="batch_no" placeholder="Batch No"></input>
-            
-        </div>
-    </div>
-    <div class="col ">
-        <small id="date_sampled" class="form-text text-muted">Date Sampled</small>
-        <input data-provide="datepicker" class="form-control form-control-sm" name="date_sampled" id="datepicker" placeholder="Date Sampled" data-date-format="dd/mm/yyyy" data-date-end-date="0d" ></input>
-        <script>
-            $('#datepicker').datepicker({
-                uiLibrary: 'bootstrap4',
-                calendarWeeks: 'true',
-                todayHighlight: 'true'
-            });
-        </script>
+<div id="layoutSidenav">
+    <div id="layoutSidenav_nav">
+        <?php include __DIR__ . '/../../../layouts/sidebar.php'; ?>
     </div>
-    
-    <!-- Hidden Fields for Database -->
-    <input type="hidden" class="form-control form-control-sm" name="m_user" id="m_user" placeholder="[[+modx.user.id]]" required>
-    <input type="hidden" class="form-control form-control-sm" name="client_id" id="client_id" placeholder="[[+modx.user.username]]" required>
+    <div id="layoutSidenav_content">
+        <main>
+            <div class="container-fluid px-4">
+                <h1 class="mt-4"><?= htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8') ?></h1>
+                <ol class="breadcrumb mb-4">
+                    <li class="breadcrumb-item"><a href="/dashboard/dashboard.php">Dashboard</a></li>
+                    <li class="breadcrumb-item active">Water Test Analysis</li>
+                </ol>
 
-    <div class="col ">
-        <small id="lab_no" class="form-text text-muted">Sample ID<span class="error">*</span></small>
-        <input type="text" class="form-control form-control-sm" name="sample_id" id="sample_id" placeholder="Sample Id" required></input>
-    </div>
-    
-    <div class="col ">
-        <small id="lab_no" class="form-text text-muted">Site ID</small>
-        <input type="text" class="form-control form-control-sm" name="site_id" id="site_id" placeholder="Paddock Id"></input>
-    </div>
+                <div class="row">
+                    <div class="container">
+                        <h3>Water Test Details</h3>
+                        <span class="text-danger small">* required fields.</span>
 
-</div>
+                        <?php include __DIR__ . '/../../../components/clientDetailsForm.php'; ?>
 
-<hr>
+                        <form method="post" action="/controllers/waterTestSubmit.php" id="WatercsvForm" class="needs-validation" novalidate>
+                            <input type="hidden" name="csrf_token" value="<?= htmlspecialchars(generateCsrfToken(), ENT_QUOTES, 'UTF-8') ?>">
 
-<label class="col"><b>Analysis Inputs</b></label>
-		
-<div class="row">
-	<div class=" col-md-2">
-	<small id="phSmall" class="form-text text-muted">pH</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="ph" id="ph" placeholder="pH" required></input>
-	    <div class="input-group-append">
-                <input class="btn btn-append" type="button" value="pH" ></input>
-            </div>
-        </div>
-    </div>
-    
-    <div class=" col-md-2">
-	<small id="phSmall" class="form-text text-muted">Conductivity</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="ph" id="ph" placeholder="pH" required></input>
-	    <div class="input-group-append">
-                <input class="btn btn-append" type="button" value="pH" ></input>
-            </div>
-        </div>
-    </div>
-</div>
+                            <hr>
+                            <div class="row">
+                                <div class="col">
+                                    <small class="form-text text-muted">Lab No</small>
+                                    <input type="text" class="form-control form-control-sm" name="lab_no" id="lab_no" placeholder="Lab No">
+                                </div>
 
-<div class="row">    
-	<div class=" col-md-2">
-	<small id="nSmall" class="form-text text-muted">Nitrogen</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="n" id="n" placeholder="N - Nitrogen" required></input>
-	    <div class="input-group-append">
-                <input class="btn btn-append" type="button" id="nitrogen" value="ppm" onClick="conversion(this, nitrogen, n, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-
-	<div class=" col-md-2">
-	<small id="p" class="form-text text-muted">Phosphorus</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="p" id="p" placeholder="P - Phosphorus" required></input>
-	    <div class="input-group-append">
-                <input class="btn btn-append" type="button" id="phosphorus" value="ppm" onClick="conversion(this, phosphorus, p, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-	<small id="k" class="form-text text-muted">Postassium</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="k" id="k" placeholder="K - Postassium" required></input>
-	    <div class="input-group-append">
-                <input class="btn btn-append" type="button" id="postassium" value="ppm" onClick="conversion(this, postassium, k, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-	<small id="s" class="form-text text-muted">Sulphur</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="s" id="s" placeholder="S - Sulphur"></input>
-	    <div class="input-group-append">
-                <input class="btn btn-append" type="button" id="sulphur" value="ppm" onClick="conversion(this, sulphur, s, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-	<small id="mg" class="form-text text-muted">Magnesium</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="mg" id="mg" placeholder="Mg - Magnesium"></input>
-	    <div class="input-group-append">
-                <input class="btn btn-append" type="button" id="magnesium" value="ppm" onClick="conversion(this, magnesium, mg, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-    	<small id="ca" class="form-text text-muted">Calcium</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="ca" id="ca" placeholder="Ca - Calcium"></input>
-	    <div class="input-group-append">
-                <input class="btn btn-append" type="button" id="calcium" value="ppm" onClick="conversion(this, calcium, ca, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-</div>
+                                <div class="col">
+                                    <small class="form-text text-muted">Batch No</small>
+                                    <input type="text" class="form-control form-control-sm" name="batch_no" id="batch_no" placeholder="Batch No">
+                                </div>
 
+                                <div class="col">
+                                    <small class="form-text text-muted">Date Sampled</small>
+                                    <input type="date" class="form-control form-control-sm" name="date_sampled" id="date_sampled" placeholder="Date Sampled">
+                                </div>
 
-	
-<div class="row">
-	<div class=" col-md-2">
-		<small id="na" class="form-text text-muted">Soduim</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="na" id="na" placeholder="Na - Soduim"></input>
-    	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="soduim" value="ppm" onClick="conversion(this, soduim, na, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-		<small id="fe" class="form-text text-muted">Iron</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="fe" id="fe" placeholder="Fe - Iron"></input>
-    	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="iron" value="ppm" onClick="conversion(this, iron, fe, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-		<small id="mn" class="form-text text-muted">Manganese</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="mn" id="mn" placeholder="Mn - Manganese"></input>
-    	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="manganese" value="ppm" onClick="conversion(this, manganese, mn, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-		<small id="zn" class="form-text text-muted">Zinc</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="zn" id="zn" placeholder="Zn - Zinc"></input>
-    	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="zinc" value="ppm" onClick="conversion(this, zinc, zn, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-		<small id="cu" class="form-text text-muted">Copper</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="cu" id="cu" placeholder="Cu - Copper"></input>
-    	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="copper" value="ppm" onClick="conversion(this, copper, cu, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-		<small id="b" class="form-text text-muted">Boron</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="b" id="b" placeholder="B - Boron"></input>
-    	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="boron" value="ppm" onClick="conversion(this, boron, b, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-</div>
+                                <div class="col">
+                                    <small class="form-text text-muted">Sample ID <span class="text-danger">*</span></small>
+                                    <input type="text" class="form-control form-control-sm" name="sample_id" id="sample_id" placeholder="Sample Id" required>
+                                </div>
 
-<div class="row">
-	<div class=" col-md-2">
-		<small id="m" class="form-text text-muted">Molybdenum</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="m" id="m" placeholder="M - Molybdenum"></input>
-     	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="molybdenum" value="ppm" onClick="conversion(this, molybdenum, m, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-		<small id="co" class="form-text text-muted">Cobalt</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="co" id="co" placeholder="Co - Cobalt"></input>
-	 	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="cobalt" value="ppm" onClick="conversion(this, cobalt, co, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-		<small id="se" class="form-text text-muted">Selenium</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="se" id="se" placeholder="Se - Selenium"></input>
-	 	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="selenium" value="ppm" onClick="conversion(this, selenium, se, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-	
-	<div class=" col-md-2">
-		<small id="ch" class="form-text text-muted">Chloride</small>
-        <div class="input-group input-group-sm mb-3">
-            <input type="text" class="form-control form-control-sm" name="ch" id="ch" placeholder="Ch - Chloride" required></input>
-	 	<div class="input-group-append">
-                <input class="btn btn-append" type="button" id="chloride" value="ppm" onClick="conversion(this, chloride, cg, 10000)" ></input>
-            </div>
-        </div>
-    </div>
-    
-</div>
+                                <div class="col">
+                                    <small class="form-text text-muted">Site ID</small>
+                                    <input type="text" class="form-control form-control-sm" name="site_id" id="site_id" placeholder="Paddock Id">
+                                </div>
+                            </div>
 
+                            <hr>
+                            <label class="col"><b>Analysis Inputs</b></label>
 
-<script type="text/javascript">
-    function conversion(value, input, element, conversion) {
-        var Nvalue = document.getElementById(element.id).value;
-        var el = document.getElementById(input.id);
-        if ( el.value === "%" ) {
-            el.value = "ppm";
-            document.getElementById(element.id).value = Nvalue*conversion;
-        } else {
-            el.value = "%";
-            document.getElementById(element.id).value = Nvalue/conversion;
-        }
-    }
-</script>
-		
- <button id="WatercsvForm" type="submit" class="btn btn-success">Submit</button>
-</form>
+                            <div class="row mt-2">
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">pH <span class="text-danger">*</span></small>
+                                    <div class="input-group input-group-sm mb-3">
+                                        <input type="text" class="form-control form-control-sm" name="ph" id="ph" placeholder="pH" required>
+                                        <span class="input-group-text">pH</span>
+                                    </div>
+                                </div>
 
-    [[$waterAnalysisUpload]]
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Conductivity</small>
+                                    <div class="input-group input-group-sm mb-3">
+                                        <input type="text" class="form-control form-control-sm" name="cond_dsm" id="cond_dsm" placeholder="Conductivity">
+                                        <span class="input-group-text">dS/m</span>
+                                    </div>
+                                </div>
 
-<hr>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted">Bicarbonate (HCO3-)</small>
+                                    <div class="input-group input-group-sm mb-3">
+                                        <input type="text" class="form-control form-control-sm" name="hco3" id="hco3" placeholder="HCO3-">
+                                        <span class="input-group-text">mg/L</span>
+                                    </div>
+                                </div>
+                            </div>
 
-<div class="card">
-    <div class="card-body">
-        <h5 class="card-title">Excel/CSV Upload</h5>
-        <p class="card-text">Download a csv of this form for easy filling or upload a filled form to pre-populate.</p>
-        <div class="input-group mt-3">
-            <div class="custom-file">
-                <input type="file" class="custom-file-input" id="upload">
-                <label class="custom-file-label border-success" for="upload">Choose file</label>
-            </div>
-            <div class="input-group-append">
-                <button class="btn btn-success" type="button" id="download">Download</button>
-            </div>
-        </div>
-    </div>
-</div>
+                            <div class="row">
+                                <?php
+                                $elements = [
+                                    ['n',  'Nitrogen',   10000],
+                                    ['p',  'Phosphorus', 10000],
+                                    ['k',  'Potassium',  10000],
+                                    ['s',  'Sulphur',    10000],
+                                    ['mg', 'Magnesium',  10000],
+                                    ['ca', 'Calcium',    10000],
+                                ];
+                                foreach ($elements as [$id, $label, $factor]): ?>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted"><?= htmlspecialchars($label, ENT_QUOTES, 'UTF-8') ?></small>
+                                    <div class="input-group input-group-sm mb-3">
+                                        <input type="text" class="form-control form-control-sm"
+                                               name="<?= $id ?>" id="<?= $id ?>"
+                                               placeholder="<?= htmlspecialchars($id . ' - ' . $label, ENT_QUOTES, 'UTF-8') ?>">
+                                        <input class="btn btn-append" type="button"
+                                               id="btn_<?= $id ?>" value="ppm"
+                                               onclick="conversion(this, document.getElementById('<?= $id ?>'), <?= $factor ?>)">
+                                    </div>
+                                </div>
+                                <?php endforeach; ?>
+                            </div>
 
-</div>
+                            <div class="row">
+                                <?php
+                                $elements2 = [
+                                    ['na', 'Sodium',    10000],
+                                    ['fe', 'Iron',      10000],
+                                    ['mn', 'Manganese', 10000],
+                                    ['zn', 'Zinc',      10000],
+                                    ['cu', 'Copper',    10000],
+                                    ['b',  'Boron',     10000],
+                                ];
+                                foreach ($elements2 as [$id, $label, $factor]): ?>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted"><?= htmlspecialchars($label, ENT_QUOTES, 'UTF-8') ?></small>
+                                    <div class="input-group input-group-sm mb-3">
+                                        <input type="text" class="form-control form-control-sm"
+                                               name="<?= $id ?>" id="<?= $id ?>"
+                                               placeholder="<?= htmlspecialchars($id . ' - ' . $label, ENT_QUOTES, 'UTF-8') ?>">
+                                        <input class="btn btn-append" type="button"
+                                               id="btn_<?= $id ?>" value="ppm"
+                                               onclick="conversion(this, document.getElementById('<?= $id ?>'), <?= $factor ?>)">
+                                    </div>
+                                </div>
+                                <?php endforeach; ?>
+                            </div>
 
+                            <div class="row">
+                                <?php
+                                $elements3 = [
+                                    ['m',  'Molybdenum', 10000],
+                                    ['co', 'Cobalt',     10000],
+                                    ['se', 'Selenium',   10000],
+                                    ['ch', 'Chloride',   10000],
+                                ];
+                                foreach ($elements3 as [$id, $label, $factor]): ?>
+                                <div class="col-md-2">
+                                    <small class="form-text text-muted"><?= htmlspecialchars($label, ENT_QUOTES, 'UTF-8') ?></small>
+                                    <div class="input-group input-group-sm mb-3">
+                                        <input type="text" class="form-control form-control-sm"
+                                               name="<?= $id ?>" id="<?= $id ?>"
+                                               placeholder="<?= htmlspecialchars($id . ' - ' . $label, ENT_QUOTES, 'UTF-8') ?>">
+                                        <input class="btn btn-append" type="button"
+                                               id="btn_<?= $id ?>" value="ppm"
+                                               onclick="conversion(this, document.getElementById('<?= $id ?>'), <?= $factor ?>)">
+                                    </div>
+                                </div>
+                                <?php endforeach; ?>
+                            </div>
 
+                            <button type="submit" class="btn btn-success">Submit</button>
+                        </form>
 
-<!-- ************************ Download Form as CSV ************************ -->
-<script>
-    document.getElementById("upload").addEventListener("change", upload, false);
-    document.getElementById("download").addEventListener("click", download, false);
-    
-    function upload(e) {
-        var data = null;
-        var file = e.target.files[0];
-        var reader = new FileReader();
-        reader.readAsText(file);
-        reader.onload = function (event) {
-            var csvData = event.target.result;
-            var parsedCSV = d3.csv.parseRows(csvData);
-            parsedCSV.forEach(function (d, i) {
-                if (i == 0) return true; // skip the header
-                document.getElementById(d[0]).value = d[1];
-            });
-        }
-    }
-    
-    function download(e) {
-        data = [ ["id","value"]];
-        var f = d3.selectAll("#WatercsvForm input, select")[0];
-        f.forEach(function(d,i){
-          	data.push([d.id, d.value]);
-        });
-        console.log(data);
-        var csvContent = "data:text/csv;charset=utf-8,";
-        data.forEach(function (d, i) {
-            dataString = d.join(",");
-            csvContent += i < data.length ? dataString + "\n" : dataString;
-        });
-        var url = window.location.pathname;
-        var filename = url.substring(url.lastIndexOf('/')+1);
-        var fname = filename.split(".")[0];
-        var today = new Date();
-        var date = today.getDate()+''+(today.getMonth()+1)+''+today.getFullYear();
-        var csvname = date+''+"-water.csv";
-    
-        var encodedUri = encodeURI(csvContent);
-        var link = document.createElement("a");
-        link.setAttribute("href", encodedUri);
-        link.setAttribute("download", csvname);
-        link.click();
-    }
-</script>
-<script type="text/javascript">
-    // JavaScript for disabling form submission if there are invalid fields
-    (function() {
-      'use strict';
-      window.addEventListener('load', function() {
-        // Fetch all the forms we want to apply custom Bootstrap validation styles to
-        var forms = document.getElementsByClassName('needs-validation');
-        // Loop over them and prevent submission
-        var validation = Array.prototype.filter.call(forms, function(form) {
-          form.addEventListener('submit', function(event) {
-            if (form.checkValidity() === false) {
-              event.preventDefault();
-              event.stopPropagation();
-            }
-            form.classList.add('was-validated');
-          }, false);
-        });
-      }, false);
-    })();
-    
-</script>
-					</div>
+                        <?php include __DIR__ . '/../../../components/newClientModal.php'; ?>
 
-				</div>
+                        <hr>
 
-			</main>
-			
-			<footer class="py-4 bg-light mt-auto">
-                <div class="container-fluid px-4">
-                    <div class="d-flex align-items-center justify-content-between small">
-                        <div class="text-muted">[[SimpleCopyright? &startYear=`2003`]]. All Rights Reserved <a href="[[~1]]"></a></div>
-                        <div>
-                            <a href="[[~39~]]">Privacy Policy</a>
-                            &middot;
-                            <a href="[[~39~]]">Terms &amp; Conditions</a>
+                        <div class="card mt-3">
+                            <div class="card-body">
+                                <h5 class="card-title">Excel/CSV Upload</h5>
+                                <p class="card-text">Download a CSV of this form for easy filling or upload a filled form to pre-populate.</p>
+                                <div class="input-group mt-3">
+                                    <input type="file" class="form-control" id="upload" accept=".csv">
+                                    <button class="btn btn-success" type="button" id="download">Download</button>
+                                </div>
+                            </div>
                         </div>
+
                     </div>
                 </div>
-            </footer>
-            
-		</div>
+            </div>
+        </main>
 
-	</div>
+        <?php include __DIR__ . '/../../../layouts/footer.php'; ?>
+    </div>
+</div>
 
-	<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-	<script>
-    	/*!
-            * Start Bootstrap - SB Admin v7.0.3 (https://startbootstrap.com/template/sb-admin)
-            * Copyright 2013-2021 Start Bootstrap
-            * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-sb-admin/blob/master/LICENSE)
-            */
-            // 
-        // Scripts
-        // 
-        
-        window.addEventListener('DOMContentLoaded', event => {
-        
-            // Toggle the side navigation
-            const sidebarToggle = document.body.querySelector('#sidebarToggle');
-            if (sidebarToggle) {
-                // Uncomment Below to persist sidebar toggle between refreshes
-                // if (localStorage.getItem('sb|sidebar-toggle') === 'true') {
-                //     document.body.classList.toggle('sb-sidenav-toggled');
-                // }
-                sidebarToggle.addEventListener('click', event => {
-                    event.preventDefault();
-                    document.body.classList.toggle('sb-sidenav-toggled');
-                    localStorage.setItem('sb|sidebar-toggle', document.body.classList.contains('sb-sidenav-toggled'));
-                });
-            }
-        
+<script>
+function conversion(btnEl, element, factor) {
+    var val = parseFloat(element.value) || 0;
+    if (btnEl.value === 'ppm') {
+        btnEl.value = '%';
+        element.value = (val / factor).toFixed(4);
+    } else {
+        btnEl.value = 'ppm';
+        element.value = (val * factor).toFixed(2);
+    }
+}
+
+// CSV download
+document.getElementById('download').addEventListener('click', function () {
+    var data = [['id', 'value']];
+    document.querySelectorAll('#WatercsvForm input:not([type=hidden]):not([type=button]):not([type=submit]), #WatercsvForm select').forEach(function (el) {
+        if (el.id) data.push([el.id, el.value]);
+    });
+    var csv = data.map(function (r) { return r.join(','); }).join('\n');
+    var today = new Date();
+    var fname = today.getDate() + '' + (today.getMonth() + 1) + '' + today.getFullYear() + '-water.csv';
+    var link = document.createElement('a');
+    link.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv));
+    link.setAttribute('download', fname);
+    link.click();
+});
+
+// CSV upload
+document.getElementById('upload').addEventListener('change', function (e) {
+    var file = e.target.files[0];
+    if (!file) return;
+    var reader = new FileReader();
+    reader.onload = function (ev) {
+        ev.target.result.split('\n').slice(1).forEach(function (line) {
+            var parts = line.split(',');
+            var el = document.getElementById(parts[0]);
+            if (el) el.value = parts[1] || '';
         });
+    };
+    reader.readAsText(file);
+});
 
-	</script>
-	<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js" crossorigin="anonymous"></script>
-	<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest" crossorigin="anonymous"></script>
-</body>
-</html>
+// Bootstrap validation
+(function () {
+    'use strict';
+    document.querySelectorAll('.needs-validation').forEach(function (form) {
+        form.addEventListener('submit', function (event) {
+            if (!form.checkValidity()) {
+                event.preventDefault();
+                event.stopPropagation();
+            }
+            form.classList.add('was-validated');
+        }, false);
+    });
+})();
+</script>

+ 148 - 201
dashboard/crop-analysis/water-test-data/water-uploadsubmit.php

@@ -1,207 +1,154 @@
 <?php
-require('client-assets/php/spreadsheet/php-excel-reader/excel_reader2.php');
-require('client-assets/php/spreadsheet/SpreadsheetReader.php');
-
-    $dbHost = "localhost";
-	$dbDatabase = "cropmonitor";
-	$dbPasswrod = "brvnCcaEYxlPCS3";
-	$dbUser = "cropmonitor";
-	$mysqli = new mysqli($dbHost, $dbUser, $dbPasswrod, $dbDatabase);
-	//$con = mysqli_connect("localhost", "cropmonitor", "brvnCcaEYxlPCS3", "cropmonitor");
-
-
-if(isset($_POST['Submit'])){
-
-  $mimes = ['application/vnd.ms-excel','text/xls','text/xlsx','text/csv','application/vnd.oasis.opendocument.spreadsheet'];
-  if(in_array($_FILES["file"]["type"],$mimes)){
-
-    $uploadFilePath = 'client-assets/uploads/'.basename($_FILES['file']['name']);  //need to add individual folders for clients.
-    move_uploaded_file($_FILES['file']['tmp_name'], $uploadFilePath);
-
-    $Reader = new SpreadsheetReader($uploadFilePath);
-
-    $totalSheet = count($Reader->sheets());
-
-    echo "You have total ".$totalSheet." sheets".
-
-    $html="<table border='1'>";
-
-    /* For Loop for all sheets */
-    for($i=0;$i<$totalSheet;$i++){
-
-      $Reader->ChangeSheet($i);
-
-      foreach ($Reader as $Row)
-      {
-        $html.="<tr>";
-        $title = isset($Row[0]) ? $Row[0] : '';
-        $description = isset($Row[1]) ? $Row[1] : '';
-        $html.="<td>".id."</td>";
-        $html.="<td>".analysis_type."</td>";
-        $html.="<td>".lab_no."</td>";
-        $html.="<td>".batch_no."</td>";
-        $html.="<td>".sample_id."</td>";
-        $html.="<td>".site_id."</td>";
-        $html.="<td>".crop."</td>";
-        $html.="<td>".date_sampled."</td>";
-        $html.="<td>".lab_no."</td>";
-        $html.="<td>".batch_no."</td>";
-        $html.="<td>".sample_id."</td>";
-        $html.="<td>".site_id."</td>";
-        $html.="<td>".tec."</td>";
-        $html.="<td>".cec."</td>";
-        $html.="<td>".texture."</td>";
-        $html.="<td>".gravel."</td>";
-        $html.="<td>".colour."</td>";
-        $html.="<td>".NO3_N."</td>";
-        $html.="<td>".NH3_N."</td>";
-        $html.="<td>".p_mehlick."</td>";
-        $html.="<td>".p_bray2."</td>";
-        $html.="<td>".p_morgan."</td>";
-        $html.="<td>".k_morgan."</td>";
-        $html.="<td>".ca_morgan."</td>";
-        $html.="<td>".mg_morgan."</td>";
-        $html.="<td>".na_morgan."</td>";
-        $html.="<td>".ch_h2o."</td>";
-        $html.="<td>".ocarbon."</td>";
-        $html.="<td>".omatter."</td>";
-        $html.="<td>".fe."</td>";
-        $html.="<td>".ec."</td>";
-        $html.="<td>".ph_cacl2."</td>";
-        $html.="<td>".ph_h2o."</td>";
-        $html.="<td>".paramag."</td>";
-        $html.="<td>".s_morgan."</td>";
-        $html.="<td>".b_cacl2."</td>";
-        $html.="<td>".mn_dtpa."</td>";
-        $html.="<td>".zn_dtpa."</td>";
-        $html.="<td>".fe_dtpa."</td>";
-        $html.="<td>".cu_dtpa."</td>";
-        $html.="<td>".al."</td>";
-        $html.="<td>".sl_cacl2."</td>";
-        $html.="<td>".m_dtpa."</td>";
-        $html.="<td>".co_dtpa."</td>";
-        $html.="<td>".se."</td>";
-        $html.="<td>".ca_mehlick3."</td>";
-        $html.="<td>".mg_mehlick3."</td>";
-        $html.="<td>".k_mehlick3."</td>";
-        $html.="<td>".na_mehlick3."</td>";
-        $html.="<td>".al_mehlick3."</td>";
-        $html.="</tr>";
-
-        $query = "insert into soil_records(
-            analysis_type,
-            lab_no,
-            batch_no,
-            sample_id,
-            site_id,
-            crop,
-            date_sampled,
-            lab_no,
-            batch_no,
-            sample_id,
-            site_id,
-            tec,
-            cec,
-            texture,
-            gravel,
-            colour,
-            NO3_N,
-            NH3_N,
-            p_mehlick,
-            p_bray2,
-            p_morgan,
-            k_morgan,
-            ca_morgan,
-            mg_morgan,
-            na_morgan,
-            ch_h2o,
-            ocarbon,
-            omatter,
-            fe,
-            ec,
-            ph_cacl2,
-            ph_h2o,
-            paramag,
-            s_morgan,
-            b_cacl2,
-            mn_dtpa,
-            zn_dtpa,
-            fe_dtpa,
-            cu_dtpa,
-            al,
-            sl_cacl2,
-            m_dtpa,
-            co_dtpa,
-            se,
-            ca_mehlick3,
-            mg_mehlick3,
-            k_mehlick3,
-            na_mehlick3,
-            al_mehlick3
-        ) values(
-            '" . $analysis_type . "',
-            '" . $lab_no . "',
-            '" . $batch_no . "',
-            '" . $sample_id . "',
-            '" . $site_id . "',
-            '" . $crop . "',
-            '" . $date_sampled . "',
-            '" . $lab_no . "',
-            '" . $batch_no . "',
-            '" . $sample_id . "',
-            '" . $site_id . "',
-            '" . $tec . "',
-            '" . $cec . "',
-            '" . $texture . "',
-            '" . $gravel . "',
-            '" . $colour . "',
-            '" . $NO3_N . "',
-            '" . $NH3_N . "',
-            '" . $p_mehlick . "',
-            '" . $p_bray2 . "',
-            '" . $p_morgan . "',
-            '" . $k_morgan . "',
-            '" . $ca_morgan . "',
-            '" . $mg_morgan . "',
-            '" . $na_morgan . "',
-            '" . $ch_h2o . "',
-            '" . $ocarbon . "',
-            '" . $omatter . "',
-            '" . $fe . "',
-            '" . $ec . "',
-            '" . $ph_cacl2 . "',
-            '" . $ph_h2o . "',
-            '" . $paramag . "',
-            '" . $s_morgan . "',
-            '" . $b_cacl2 . "',
-            '" . $mn_dtpa . "',
-            '" . $zn_dtpa . "',
-            '" . $fe_dtpa . "',
-            '" . $cu_dtpa . "',
-            '" . $al . "',
-            '" . $sl_cacl2 . "',
-            '" . $m_dtpa . "',
-            '" . $co_dtpa . "',
-            '" . $se . "',
-            '" . $ca_mehlick3 . "',
-            '" . $mg_mehlick3 . "',
-            '" . $k_mehlick3 . "',
-            '" . $na_mehlick3 . "',
-            '" . $al_mehlick3 . "'
-        )";
-
-        $mysqli->query($query);
-       }
+/**
+ * dashboard/crop-analysis/water-test-data/water-uploadsubmit.php
+ *
+ * Spreadsheet bulk-upload handler for water test records.
+ * Requires the SpreadsheetReader library.
+ *
+ * NOTE: Column index → DB field mapping below must match the actual
+ * spreadsheet format provided by the lab. Update $colMap as needed.
+ */
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
 
-    }
+require_once __DIR__ . '/../../../config/database.php';
+require_once __DIR__ . '/../../../lib/auth.php';
+
+requireLogin();
+
+$readerBase = __DIR__ . '/../../../client-assets/php/spreadsheet';
+$readerFile = $readerBase . '/php-excel-reader/excel_reader2.php';
+$spreadFile = $readerBase . '/SpreadsheetReader.php';
+
+if (!file_exists($readerFile) || !file_exists($spreadFile)) {
+    http_response_code(500);
+    exit('<p class="text-danger">Spreadsheet reader library not found.</p>');
+}
+
+require_once $readerFile;
+require_once $spreadFile;
+
+if (!isset($_POST['Submit'])) {
+    exit;
+}
+
+$allowedMimes = [
+    'application/vnd.ms-excel',
+    'text/xls', 'text/xlsx', 'text/csv',
+    'application/vnd.oasis.opendocument.spreadsheet',
+    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+];
+
+if (!in_array($_FILES['file']['type'] ?? '', $allowedMimes, true)) {
+    exit('<p class="text-danger">Invalid file type. Please upload an Excel or CSV file.</p>');
+}
 
-    $html.="</table>";
-    echo $html;
-    echo "<br />Data Inserted in dababase";
+$uploadDir  = __DIR__ . '/../../../client-assets/uploads/';
+$uploadPath = $uploadDir . basename($_FILES['file']['name']);
 
-  }else { 
-    die("<br/>Sorry, Incorrect file."); 
-  }
+if (!move_uploaded_file($_FILES['file']['tmp_name'], $uploadPath)) {
+    exit('<p class="text-danger">Failed to upload file.</p>');
+}
 
+$reader     = new SpreadsheetReader($uploadPath);
+$totalSheet = count($reader->sheets());
+
+echo '<p>File uploaded — ' . (int) $totalSheet . ' sheet(s) found.</p>';
+
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+
+/**
+ * Map spreadsheet column indices to soil_records column names.
+ * Water test data is stored in soil_records pending a dedicated water_records table.
+ * Adjust indices to match the lab's spreadsheet layout.
+ */
+$colMap = [
+    0  => 'analysis_type',
+    1  => 'lab_no',
+    2  => 'batch_no',
+    3  => 'sample_id',
+    4  => 'site_id',
+    5  => 'crop',
+    6  => 'date_sampled',
+    7  => 'tec',
+    8  => 'cec',
+    9  => 'texture',
+    10 => 'gravel',
+    11 => 'colour',
+    12 => 'NO3_N',
+    13 => 'NH3_N',
+    14 => 'p_mehlick',
+    15 => 'p_bray2',
+    16 => 'p_morgan',
+    17 => 'k_morgan',
+    18 => 'ca_morgan',
+    19 => 'mg_morgan',
+    20 => 'na_morgan',
+    21 => 'ch_h2o',
+    22 => 'ocarbon',
+    23 => 'omatter',
+    24 => 'fe',
+    25 => 'ec',
+    26 => 'ph_cacl2',
+    27 => 'ph_h2o',
+    28 => 'paramag',
+    29 => 's_morgan',
+    30 => 'b_cacl2',
+    31 => 'mn_dtpa',
+    32 => 'zn_dtpa',
+    33 => 'fe_dtpa',
+    34 => 'cu_dtpa',
+    35 => 'al',
+    36 => 'sl_cacl2',
+    37 => 'm_dtpa',
+    38 => 'co_dtpa',
+    39 => 'se',
+    40 => 'ca_mehlick3',
+    41 => 'mg_mehlick3',
+    42 => 'k_mehlick3',
+    43 => 'na_mehlick3',
+    44 => 'al_mehlick3',
+];
+
+$columns      = array_values($colMap);
+$placeholders = implode(', ', array_fill(0, count($columns) + 2, '?'));
+$colList      = 'modx_user_id, rand, ' . implode(', ', array_map(fn($c) => "`$c`", $columns));
+
+$stmt = $pdo->prepare(
+    "INSERT INTO soil_records ($colList) VALUES ($placeholders)"
+);
+
+$inserted = 0;
+$skipped  = 0;
+
+for ($i = 0; $i < $totalSheet; $i++) {
+    $reader->ChangeSheet($i);
+    $firstRow = true;
+    foreach ($reader as $row) {
+        if ($firstRow) { $firstRow = false; continue; } // skip header row
+
+        $rand   = mt_rand(10000, 99999);
+        $values = [$userId, $rand];
+
+        foreach ($colMap as $idx => $colName) {
+            $val = isset($row[$idx]) ? trim((string) $row[$idx]) : null;
+            $values[] = ($val === '') ? null : $val;
+        }
+
+        try {
+            $stmt->execute($values);
+            $inserted++;
+        } catch (\PDOException $e) {
+            $skipped++;
+        }
+    }
 }
-?>
+
+@unlink($uploadPath);
+
+echo '<p class="text-success">Import complete: '
+   . (int) $inserted . ' record(s) inserted, '
+   . (int) $skipped  . ' skipped.</p>';

+ 240 - 487
dashboard/inbox.php

@@ -1,506 +1,259 @@
-<div class="container-fluid">
-    <div class="row">
-        <div class="col">
-            <h2>Report History</h2>
-        </div>
+<?php
+require_once __DIR__ . '/../config/database.php';
+require_once __DIR__ . '/../lib/auth.php';
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+requireLogin();
+
+$pageTitle = 'Report History';
+$siteName  = 'Crop Monitor';
+
+$pdo    = getDBConnection();
+$userId = getCurrentUserId();
+
+function countRecords(\PDO $pdo, string $table, int $userId, int $status = 0): int {
+    $stmt = $pdo->prepare("SELECT COUNT(*) FROM `$table` WHERE modx_user_id = ? AND status = ?");
+    $stmt->execute([$userId, $status]);
+    return (int) $stmt->fetchColumn();
+}
+
+$counts = [
+    'soil'    => countRecords($pdo, 'soil_records',   $userId, 0),
+    'plant'   => countRecords($pdo, 'plant_records',  $userId, 0),
+    'water'   => countRecords($pdo, 'water_records',  $userId, 0),
+    'animal'  => countRecords($pdo, 'animal_records', $userId, 0),
+    'archived'=> countRecords($pdo, 'soil_records',   $userId, 2),
+    'deleted' => countRecords($pdo, 'soil_records',   $userId, 3),
+];
+
+function fetchHistory(\PDO $pdo, string $table, int $userId): array {
+    $stmt = $pdo->prepare(
+        "SELECT id, rand, lab_no, sample_id, site_id, crop, date_sampled
+         FROM `$table`
+         WHERE modx_user_id = ? AND status = 0
+         ORDER BY id DESC LIMIT 100"
+    );
+    $stmt->execute([$userId]);
+    return $stmt->fetchAll();
+}
+
+$soilRows   = fetchHistory($pdo, 'soil_records',   $userId);
+$plantRows  = fetchHistory($pdo, 'plant_records',  $userId);
+$waterRows  = fetchHistory($pdo, 'water_records',  $userId);
+$animalRows = fetchHistory($pdo, 'animal_records', $userId);
+
+$h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
+
+include __DIR__ . '/../layouts/header.php';
+include __DIR__ . '/../layouts/navbar.php';
+?>
+
+<div id="layoutSidenav">
+    <div id="layoutSidenav_nav">
+        <?php include __DIR__ . '/../layouts/sidebar.php'; ?>
     </div>
-    <!--grid-->
-    <div class="row inbox-mail">
-        <div class="col-12 col-md-3 compose">
-            <form action="#" method="GET">
-                <div class="input-group mb-3">
-                    <input type="text" class="form-control" placeholder="Search..." aria-label="Search" aria-describedby="basic-addon2">
-                    <span class="input-group-text" id="basic-addon2"><i class="fa fa-search"></i></span>
-                </div>
-                <!-- Input Group -->
-            </form>
-
-            <div class="alert alert-danger py-0" role="alert">
-                <h4>Reports</h4>
-            </div>
-
-            <div class="d-flex align-items-start list-group" id="v-pills-tab" role="tablist" aria-orientation="vertical">
-                <a id="new-reports-tab" data-bs-toggle="pill" data-bs-target="#new-reports" type="button" role="tab" aria-controls="new-reports" aria-selected="true" class="text-secondary list-group-item list-group-item-action list-group-item-light">
-                    <i class="fas fa-globe-asia nav_icon" aria-hidden="true"></i> New Reports
-                    <span class="badge badge-secondary badge-pill float-right">[[!analysisCount? &analysis=`reports_view` &cid=`[[+id]]` &status=`0` ]]</span>
-                </a>
-                <a id="soil-reports-tab" data-bs-toggle="pill" data-bs-target="#soil-reports" type="button" role="tab" aria-controls="soil-reports" aria-selected="false" class="text-secondary list-group-item list-group-item-action list-group-item-light">
-                    <i class="fas fa-icicles fa-rotate-180 nav_icon" aria-hidden="true"></i> Soil Analysis
-                    <span class="badge badge-secondary badge-pill float-right">[[!analysisCount? &analysis=`soil_records` &cid=`[[+id]]` &status=`0` ]]</span>
-                </a>
-                <a id="plant-reports-tab" data-bs-toggle="pill" data-bs-target="#plant-reports" type="button" role="tab" aria-controls="plant-reports" aria-selected="false" class="text-secondary list-group-item list-group-item-action list-group-item-light">
-                    <i class="fab fa-pagelines nav_icon" aria-hidden="true"></i> Plant Analysis
-                    <span class="badge badge-success badge-pill float-right">[[!analysisCount? &analysis=`plant_records` &cid=`[[+id]]` &status=`0` ]]</span>
-                </a>
-                <a id="water-reports-tab" data-bs-toggle="pill" data-bs-target="#water-reports" type="button" role="tab" aria-controls="water-reports" aria-selected="false" class="text-secondary list-group-item list-group-item-action list-group-item-light">
-                    <i class="fa fa-tint nav_icon" aria-hidden="true"></i> Water Analysis
-                    <span class="badge badge-primary badge-pill float-right">[[!analysisCount? &analysis=`water_records` &cid=`[[+id]]` &status=`0` ]]</span>
-                </a>
-                <a id="dietary-reports-tab" data-bs-toggle="pill" data-bs-target="#dietary-reports" type="button" role="tab" aria-controls="dietary-reports" aria-selected="false" class="text-secondary list-group-item list-group-item-action list-group-item-light">
-                    <i class="fas fa-dog nav_icon" aria-hidden="true"></i> Animal Dietary Balance
-                    <span class="badge badge-success badge-pill float-right">[[!analysisCount? &analysis=`animal_records` &cid=`[[+id]]` &status=`0` ]]</span>
-                </a>
-                <a id="compost-reports-tab" data-bs-toggle="pill" data-bs-target="#compost-reports" type="button" role="tab" aria-controls="compost-reports" aria-selected="false" class="text-secondary list-group-item list-group-item-action list-group-item-light">
-                    <i class="fas fa-cloud nav_icon" aria-hidden="true"></i> Compost Test Data
-                    <span class="badge badge-success badge-pill float-right"><!-- [ [!analysisCount? &analysis=`compost_records` &cid=`[[+id]]` &status=`0` ]] --></span>
-                </a>
-            </div>
-
-            <br>
-
-            <div class="list-group">
-                <a href="#" class="list-group-item list-group-item-action"> Folders
-                </a>
-                <a href="#" class="list-group-item list-group-item-action list-group-item-light text-warning">
-                    <i class="fa fa-folder text-warning" aria-hidden="true"></i> Archived
-                    <span class="badge badge-warning badge-pill float-right">[[!analysisCount? &analysis=`reports_view` &cid=`[[+id]]` &status=`2` ]]</span>
-                </a>
-                <a href="#" class="list-group-item list-group-item-action list-group-item-light text-danger">
-                    <i class="fa fa-folder text-danger" aria-hidden="true"></i> Deleted
-                    <span class="badge badge-danger badge-pill float-right">[[!analysisCount? &analysis=`reports_view` &cid=`[[+id]]` &status=`3` ]]</span>
-                </a>
-            </div>
-        </div>
-
-
-        <!-- tab content -->
-        <div class="col-12 col-md-9 tab-content tab-content-in">
-            <!-- tab content -->
-
-            <div class="tab-pane fade show active text-style" id="new-reports" role="tabpanel" aria-labelledby="new-reports-tab">
-                <div class="inbox-right">
-                    <div class="mailbox-content">
-                        <div class="mail-toolbar clearfix">
-                            <div class="float-left">
-                                <div class="btn-group m-r-sm mail-hidden-options" style="display: inline-block;">
-
-                                    <div class="btn-group">
-                                        <div class="dropdown">
-                                            <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-folder"></i> <span class="caret"></span></button>
-                                            <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                                                <a class="dropdown-item" href="#">Social</a>
-
-                                            </div>
-                                        </div>
-
-                                        <div class="dropdown">
-                                            <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-tags"></i> <span class="caret"></span></button>
-                                            <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                                                <a class="dropdown-item" href="#">Work</a>
-
-                                            </div>
-                                        </div>
-                                    </div>
-
-                                </div>
-                            </div>
-                            <div class="float-right">
-                                <div class="dropdown">
-                                    <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-cog "></i><i class="fafa-chevron-down "></i></button>
-                                    <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                                        <a class="dropdown-item" href="#"><i class="fa fa-pencil-square-o icon_9"></i> Edit</a>
-
-                                    </div>
-                                </div>
-
-                                <div class="btn-group">
-                                    <a class="btn btn-default"><i class="fa fa-angle-left"></i></a>
-                                    <a class="btn btn-default"><i class="fa fa-angle-right"></i></a>
-                                </div>
-                            </div>
+    <div id="layoutSidenav_content">
+        <main>
+            <div class="container-fluid px-4">
+                <h1 class="mt-4"><?= $h($pageTitle) ?></h1>
+                <ol class="breadcrumb mb-4">
+                    <li class="breadcrumb-item"><a href="/dashboard/dashboard.php">Dashboard</a></li>
+                    <li class="breadcrumb-item active">Report History</li>
+                </ol>
+
+                <div class="row inbox-mail">
+                    <!-- Left sidebar -->
+                    <div class="col-12 col-md-3">
+                        <div class="input-group mb-3">
+                            <input type="text" class="form-control" placeholder="Search...">
+                            <span class="input-group-text"><i class="fa fa-search"></i></span>
                         </div>
-                        <table class="table table-hover">
-                            <thead>
-                                <tr>
-                                    <th scope="col"></th>
-                                    <th scope="col"></th>
-                                    <th scope="col">Report</th>
-                                    <th scope="col">Details</th>
-                                    <th scope="col">Lab Number</th>
-                                    <th scope="col">Block ID</th>
-                                    <th scope="col">Crop</th>
-                                    <th scope="col">Date Processed</th>
-                                    <th scope="col" class='text-right'>Actions</th>
-                                </tr>
-                            </thead>
-                            <tbody>
-                                [[!reportHistory? &analysis=`reports_view` &cid=`[[+id]]` &icon=`text-secondary fas fa-globe-asia` ]]
-
-                            </tbody>
-                        </table>
-                    </div>
-                </div>
-            </div>
-
-            <div class="tab-pane fade text-style" id="soil-reports" role="tabpanel" aria-labelledby="soil-reports-tab">
-                <div class="inbox-right">
-                    <div class="mailbox-content">
-                        <div class="mail-toolbar clearfix">
-                            <div class="float-left">
-                                <div class="btn-group m-r-sm mail-hidden-options" style="display: inline-block;">
-
-                                    <div class="btn-group">
-                                        <div class="dropdown">
-                                            <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-folder"></i> <span class="caret"></span></button>
-                                            <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                                                <a class="dropdown-item" href="#">Social</a>
-
-                                            </div>
-                                        </div>
 
-                                        <div class="dropdown">
-                                            <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-tags"></i> <span class="caret"></span></button>
-                                            <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                                                <a class="dropdown-item" href="#">Work</a>
-
-                                            </div>
-                                        </div>
-                                    </div>
-
-                                </div>
-                            </div>
-                            <div class="float-right">
-                                <div class="dropdown">
-                                    <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-cog "></i><i class="fafa-chevron-down "></i></button>
-                                    <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                                        <a class="dropdown-item" href="#"><i class="fa fa-pencil-square-o icon_9"></i> Edit</a>
-
-                                    </div>
-                                </div>
-
-                                <div class="btn-group">
-                                    <a class="btn btn-default"><i class="fa fa-angle-left"></i></a>
-                                    <a class="btn btn-default"><i class="fa fa-angle-right"></i></a>
-                                </div>
-                            </div>
+                        <div class="alert alert-danger py-0"><h4>Reports</h4></div>
+
+                        <div class="d-flex align-items-start list-group" id="v-pills-tab" role="tablist" aria-orientation="vertical">
+                            <a id="soil-reports-tab" data-bs-toggle="pill" data-bs-target="#soil-reports"
+                               type="button" role="tab"
+                               class="text-secondary list-group-item list-group-item-action list-group-item-light active">
+                                <i class="fas fa-icicles fa-rotate-180"></i> Soil Analysis
+                                <span class="badge bg-secondary float-end"><?= $counts['soil'] ?></span>
+                            </a>
+                            <a id="plant-reports-tab" data-bs-toggle="pill" data-bs-target="#plant-reports"
+                               type="button" role="tab"
+                               class="text-secondary list-group-item list-group-item-action list-group-item-light">
+                                <i class="fab fa-pagelines"></i> Plant Analysis
+                                <span class="badge bg-success float-end"><?= $counts['plant'] ?></span>
+                            </a>
+                            <a id="water-reports-tab" data-bs-toggle="pill" data-bs-target="#water-reports"
+                               type="button" role="tab"
+                               class="text-secondary list-group-item list-group-item-action list-group-item-light">
+                                <i class="fa fa-tint"></i> Water Analysis
+                                <span class="badge bg-primary float-end"><?= $counts['water'] ?></span>
+                            </a>
+                            <a id="dietary-reports-tab" data-bs-toggle="pill" data-bs-target="#dietary-reports"
+                               type="button" role="tab"
+                               class="text-secondary list-group-item list-group-item-action list-group-item-light">
+                                <i class="fas fa-dog"></i> Animal Dietary Balance
+                                <span class="badge bg-warning text-dark float-end"><?= $counts['animal'] ?></span>
+                            </a>
+                            <a id="compost-reports-tab" data-bs-toggle="pill" data-bs-target="#compost-reports"
+                               type="button" role="tab"
+                               class="text-secondary list-group-item list-group-item-action list-group-item-light">
+                                <i class="fas fa-cloud"></i> Compost Test Data
+                                <span class="badge bg-secondary float-end">0</span>
+                            </a>
                         </div>
-                        <table class="table table-hover">
-                            <thead>
-                                <tr>
-                                    <th scope="col"></th>
-                                    <th scope="col"></th>
-                                    <th scope="col">Report</th>
-                                    <th scope="col">Details</th>
-                                    <th scope="col">Lab Number</th>
-                                    <th scope="col">Block ID</th>
-                                    <th scope="col">Crop</th>
-                                    <th scope="col">Date Processed</th>
-                                    <th scope="col" class='text-right'>Actions</th>
-                                </tr>
-                            </thead>
-                            <tbody>
-                                [[!reportHistory? &analysis=`soil_records` &cid=`[[+id]]` &icon=`text-secondary fas fa-globe-asia` ]]
-
-                            </tbody>
-                        </table>
-                    </div>
-                </div>
-            </div>
-
-            <div class="tab-pane fade text-style" id="plant-reports" role="tabpanel" aria-labelledby="plant-reports-tab">
-                <div class="inbox-right">
-                    <div class="mailbox-content">
-                        <div class="mail-toolbar clearfix">
-                            <div class="float-left">
-                                <div class="btn-group m-r-sm mail-hidden-options" style="display: inline-block;">
-                                    <div class="btn-group">
-                                        <a class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><i class="fa fa-folder"></i> <span class="caret"></span></a>
-                                        <ul class="dropdown-menu dropdown-menu-right" role="menu">
-                                            <li><a href="#">Social</a></li>
 
-                                        </ul>
-                                    </div>
-                                    <div class="btn-group">
-                                        <a class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><i class="fa fa-tags"></i> <span class="caret"></span></a>
-                                        <ul class="dropdown-menu dropdown-menu-right" role="menu">
-                                            <li><a href="#">Work</a></li>
-
-                                        </ul>
-                                    </div>
-                                </div>
-                            </div>
-                            <div class="float-right">
-                                <div class="dropdown">
-                                    <a href="#" title="" class="btn btn-default" data-toggle="dropdown" aria-expanded="false">
-                                        <i class="fa fa-cog icon_8"></i>
-                                        <i class="fa fa-chevron-down icon_8"></i>
-                                        <div class="ripple-wrapper"></div>
-                                    </a>
-                                    <ul class="dropdown-menu float-right">
-                                        <li>
-                                            <a href="#" title="">
-                                                <i class="fa fa-pencil-square-o icon_9"></i>
-                                                Edit
-                                            </a>
-                                        </li>
-                                        <li>
-                                            <a href="#" title="">
-                                                <i class="fa fa-calendar icon_9"></i>
-                                                Schedule
-                                            </a>
-                                        </li>
-                                        <li>
-                                            <a href="#" title="">
-                                                <i class="fa fa-download icon_9"></i>
-                                                Download
-                                            </a>
-                                        </li>
-                                        <li>
-                                            <a href="#" class="font-red" title="">
-                                                <i class="fa fa-times" icon_9=""></i>
-                                                Delete
-                                            </a>
-                                        </li>
-                                    </ul>
-                                </div>
-                                <div class="btn-group">
-                                    <a class="btn btn-default"><i class="fa fa-angle-left"></i></a>
-                                    <a class="btn btn-default"><i class="fa fa-angle-right"></i></a>
-                                </div>
-                            </div>
+                        <br>
+
+                        <div class="list-group">
+                            <span class="list-group-item fw-bold">Folders</span>
+                            <a href="#" class="list-group-item list-group-item-action list-group-item-light text-warning">
+                                <i class="fa fa-folder text-warning"></i> Archived
+                                <span class="badge bg-warning text-dark float-end"><?= $counts['archived'] ?></span>
+                            </a>
+                            <a href="#" class="list-group-item list-group-item-action list-group-item-light text-danger">
+                                <i class="fa fa-folder text-danger"></i> Deleted
+                                <span class="badge bg-danger float-end"><?= $counts['deleted'] ?></span>
+                            </a>
                         </div>
-                        <table class="table">
-                            <thead>
-                                <tr>
-                                    <th scope="col"></th>
-                                    <th scope="col"></th>
-                                    <th scope="col">Details</th>
-                                    <th scope="col">Lab Number</th>
-                                    <th scope="col">Block ID</th>
-                                    <th scope="col">Crop</th>
-                                    <th scope="col">Date Processed</th>
-                                    <th scope="col" class='text-right'>Actions</th>
-                                </tr>
-                            </thead>
-                            <tbody>
-                                [[!reportHistory? &analysis=`plant_records` &cid=`[[+id]]` &icon=`text-success fab fa-pagelines` ]]
-                            </tbody>
-                        </table>
                     </div>
-                </div>
-            </div>
 
-            <div class="tab-pane fade text-style" id="water-reports" role="tabpanel" aria-labelledby="waterports-tab">
-                <div class="inbox-right">
-                    <div class="mailbox-content">
-                        <div class="mail-toolbar clearfix">
-                            <div class="float-left">
-                                <div class="btn-group m-r-sm mail-hidden-options" style="display: inline-block;">
-                                    <div class="btn-group">
-                                        <a class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><i class="fa fa-folder"></i> <span class="caret"></span></a>
-                                        <ul class="dropdown-menu dropdown-menu-right" role="menu">
-                                            <li><a href="#">Social</a></li>
-
-                                        </ul>
-                                    </div>
-                                    <div class="btn-group">
-                                        <a class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><i class="fa fa-tags"></i> <span class="caret"></span></a>
-                                        <ul class="dropdown-menu dropdown-menu-right" role="menu">
-                                            <li><a href="#">Work</a></li>
-
-                                        </ul>
-                                    </div>
-                                </div>
-                            </div>
-                            <div class="float-right">
-                                <div class="dropdown">
-                                    <a href="#" title="" class="btn btn-default" data-toggle="dropdown" aria-expanded="false">
-                                        <i class="fa fa-cog icon_8"></i>
-                                        <i class="fa fa-chevron-down icon_8"></i>
-                                        <div class="ripple-wrapper"></div>
-                                    </a>
-                                    <ul class="dropdown-menu float-right">
-                                        <li>
-                                            <a href="#" title="">
-                                                <i class="fa fa-pencil-square-o icon_9"></i>
-                                                Edit
-                                            </a>
-                                        </li>
-                                        <li>
-                                            <a href="#" title="">
-                                                <i class="fa fa-calendar icon_9"></i>
-                                                Schedule
-                                            </a>
-                                        </li>
-                                        <li>
-                                            <a href="#" title="">
-                                                <i class="fa fa-download icon_9"></i>
-                                                Download
-                                            </a>
-                                        </li>
-                                        <li>
-                                            <a href="#" class="font-red" title="">
-                                                <i class="fa fa-times" icon_9=""></i>
-                                                Delete
-                                            </a>
-                                        </li>
-                                    </ul>
-                                </div>
-                                <div class="btn-group">
-                                    <a class="btn btn-default"><i class="fa fa-angle-left"></i></a>
-                                    <a class="btn btn-default"><i class="fa fa-angle-right"></i></a>
-                                </div>
-                            </div>
+                    <!-- Tab content -->
+                    <div class="col-12 col-md-9 tab-content">
+
+                        <!-- Soil -->
+                        <div class="tab-pane fade show active" id="soil-reports" role="tabpanel">
+                            <h5 class="mt-2">Soil Analysis Records</h5>
+                            <table class="table table-hover table-sm">
+                                <thead class="table-dark">
+                                    <tr>
+                                        <th>Lab No</th><th>Sample ID</th><th>Site ID</th>
+                                        <th>Crop</th><th>Date Sampled</th><th class="text-end">Actions</th>
+                                    </tr>
+                                </thead>
+                                <tbody>
+                                <?php if (empty($soilRows)): ?>
+                                    <tr><td colspan="6" class="text-center text-muted">No records found.</td></tr>
+                                <?php else: foreach ($soilRows as $r): ?>
+                                    <tr>
+                                        <td><?= $h($r['lab_no']) ?></td>
+                                        <td><?= $h($r['sample_id']) ?></td>
+                                        <td><?= $h($r['site_id']) ?></td>
+                                        <td><?= $h($r['crop']) ?></td>
+                                        <td><?= $h($r['date_sampled']) ?></td>
+                                        <td class="text-end">
+                                            <a href="/dashboard/crop-analysis/soil-test-data/soil-analysis.php?rid=<?= (int)$r['id'] ?>&rand=<?= (float)$r['rand'] ?>"
+                                               class="btn btn-sm btn-outline-success">View</a>
+                                        </td>
+                                    </tr>
+                                <?php endforeach; endif; ?>
+                                </tbody>
+                            </table>
                         </div>
-                        <table class="table">
-                            <thead>
-                                <tr>
-                                    <th scope="col"></th>
-                                    <th scope="col"></th>
-                                    <th scope="col">Details</th>
-                                    <th scope="col">Lab Number</th>
-                                    <th scope="col">Block ID</th>
-                                    <th scope="col">Date Processed</th>
-                                    <th scope="col" class='text-right'>Actions</th>
-                                </tr>
-                            </thead>
-                            <tbody>
-                                [[!reportHistory? &analysis=`water_records` &cid=`[[+id]]` &icon=`text-primary fa fa-tint` ]]
-                            </tbody>
-                        </table>
-                    </div>
-                </div>
-            </div>
-            <div class="tab-pane fade text-style" id="dietary-reports" role="tabpanel" aria-labelledby="dietary-reports-tab">
-                <div class="inbox-right">
-                    <div class="mailbox-content">
-                        <div class="mail-toolbar clearfix">
-                            <div class="float-left">
-                                <div class="btn-group m-r-sm mail-hidden-options" style="display: inline-block;">
-                                    <div class="btn-group">
-                                        <a class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><i class="fa fa-folder"></i> <span class="caret"></span></a>
-                                        <ul class="dropdown-menu dropdown-menu-right" role="menu">
-                                            <li><a href="#">Social</a></li>
 
-                                        </ul>
-                                    </div>
-                                    <div class="btn-group">
-                                        <a class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><i class="fa fa-tags"></i> <span class="caret"></span></a>
-                                        <ul class="dropdown-menu dropdown-menu-right" role="menu">
-                                            <li><a href="#">Work</a></li>
+                        <!-- Plant -->
+                        <div class="tab-pane fade" id="plant-reports" role="tabpanel">
+                            <h5 class="mt-2">Plant Analysis Records</h5>
+                            <table class="table table-hover table-sm">
+                                <thead class="table-dark">
+                                    <tr>
+                                        <th>Lab No</th><th>Sample ID</th><th>Site ID</th>
+                                        <th>Crop</th><th>Date Sampled</th><th class="text-end">Actions</th>
+                                    </tr>
+                                </thead>
+                                <tbody>
+                                <?php if (empty($plantRows)): ?>
+                                    <tr><td colspan="6" class="text-center text-muted">No records found.</td></tr>
+                                <?php else: foreach ($plantRows as $r): ?>
+                                    <tr>
+                                        <td><?= $h($r['lab_no']) ?></td>
+                                        <td><?= $h($r['sample_id']) ?></td>
+                                        <td><?= $h($r['site_id']) ?></td>
+                                        <td><?= $h($r['crop']) ?></td>
+                                        <td><?= $h($r['date_sampled']) ?></td>
+                                        <td class="text-end">
+                                            <a href="/dashboard/crop-analysis/plant-test-data/plant-analysis.php?rid=<?= (int)$r['id'] ?>&rand=<?= (float)$r['rand'] ?>"
+                                               class="btn btn-sm btn-outline-success">View</a>
+                                        </td>
+                                    </tr>
+                                <?php endforeach; endif; ?>
+                                </tbody>
+                            </table>
+                        </div>
 
-                                        </ul>
-                                    </div>
-                                </div>
-                            </div>
-                            <div class="float-right">
-                                <div class="dropdown">
-                                    <a href="#" title="" class="btn btn-default" data-toggle="dropdown" aria-expanded="false">
-                                        <i class="fa fa-cog icon_8"></i>
-                                        <i class="fa fa-chevron-down icon_8"></i>
-                                        <div class="ripple-wrapper"></div>
-                                    </a>
-                                    <ul class="dropdown-menu float-right">
-                                        <li>
-                                            <a href="#" title="">
-                                                <i class="fa fa-pencil-square-o icon_9"></i>
-                                                Edit
-                                            </a>
-                                        </li>
-                                        <li>
-                                            <a href="#" title="">
-                                                <i class="fa fa-calendar icon_9"></i>
-                                                Schedule
-                                            </a>
-                                        </li>
-                                        <li>
-                                            <a href="#" title="">
-                                                <i class="fa fa-download icon_9"></i>
-                                                Download
-                                            </a>
-                                        </li>
-                                        <li>
-                                            <a href="#" class="font-red" title="">
-                                                <i class="fa fa-times" icon_9=""></i>
-                                                Delete
-                                            </a>
-                                        </li>
-                                    </ul>
-                                </div>
-                                <div class="btn-group">
-                                    <a class="btn btn-default"><i class="fa fa-angle-left"></i></a>
-                                    <a class="btn btn-default"><i class="fa fa-angle-right"></i></a>
-                                </div>
-                            </div>
+                        <!-- Water -->
+                        <div class="tab-pane fade" id="water-reports" role="tabpanel">
+                            <h5 class="mt-2">Water Analysis Records</h5>
+                            <table class="table table-hover table-sm">
+                                <thead class="table-dark">
+                                    <tr>
+                                        <th>Lab No</th><th>Sample ID</th><th>Site ID</th>
+                                        <th>Date Sampled</th><th class="text-end">Actions</th>
+                                    </tr>
+                                </thead>
+                                <tbody>
+                                <?php if (empty($waterRows)): ?>
+                                    <tr><td colspan="5" class="text-center text-muted">No records found.</td></tr>
+                                <?php else: foreach ($waterRows as $r): ?>
+                                    <tr>
+                                        <td><?= $h($r['lab_no']) ?></td>
+                                        <td><?= $h($r['sample_id']) ?></td>
+                                        <td><?= $h($r['site_id']) ?></td>
+                                        <td><?= $h($r['date_sampled']) ?></td>
+                                        <td class="text-end">
+                                            <a href="/dashboard/crop-analysis/water-test-data/water-analysis-pdf.php?rid=<?= (int)$r['id'] ?>&rand=<?= (float)$r['rand'] ?>"
+                                               class="btn btn-sm btn-outline-success">View</a>
+                                        </td>
+                                    </tr>
+                                <?php endforeach; endif; ?>
+                                </tbody>
+                            </table>
                         </div>
-                        <table class="table">
-                            <tbody>
-                                <!-- /* ******************************************************************************** */ -->
-                            </tbody>
-                        </table>
-                    </div>
-                </div>
-            </div>
-            <div class="tab-pane fade text-style" id="compost-reports" role="tabpanel" aria-labelledby="compost-reports-tab">
-                <div class="inbox-right">
-                    <div class="mailbox-content">
-                        <div class="mail-toolbar clearfix">
-                            <div class="float-left">
-                                <div class="btn-group m-r-sm mail-hidden-options" style="display: inline-block;">
-                                    <div class="btn-group">
-                                        <a class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><i class="fa fa-folder"></i> <span class="caret"></span></a>
-                                        <ul class="dropdown-menu dropdown-menu-right" role="menu">
-                                            <li><a href="#">Social</a></li>
 
-                                        </ul>
-                                    </div>
-                                    <div class="btn-group">
-                                        <a class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><i class="fa fa-tags"></i> <span class="caret"></span></a>
-                                        <ul class="dropdown-menu dropdown-menu-right" role="menu">
-                                            <li><a href="#">Work</a></li>
+                        <!-- Animal Dietary -->
+                        <div class="tab-pane fade" id="dietary-reports" role="tabpanel">
+                            <h5 class="mt-2">Animal Dietary Balance Records</h5>
+                            <table class="table table-hover table-sm">
+                                <thead class="table-dark">
+                                    <tr>
+                                        <th>Lab No</th><th>Sample ID</th><th>Site ID</th>
+                                        <th>Date Sampled</th><th class="text-end">Actions</th>
+                                    </tr>
+                                </thead>
+                                <tbody>
+                                <?php if (empty($animalRows)): ?>
+                                    <tr><td colspan="5" class="text-center text-muted">No records found.</td></tr>
+                                <?php else: foreach ($animalRows as $r): ?>
+                                    <tr>
+                                        <td><?= $h($r['lab_no']) ?></td>
+                                        <td><?= $h($r['sample_id']) ?></td>
+                                        <td><?= $h($r['site_id']) ?></td>
+                                        <td><?= $h($r['date_sampled']) ?></td>
+                                        <td class="text-end">
+                                            <a href="/dashboard/crop-analysis/animal-dietary-balance/animal-dietary-balance.php?rid=<?= (int)$r['id'] ?>&rand=<?= (float)$r['rand'] ?>"
+                                               class="btn btn-sm btn-outline-success">View</a>
+                                        </td>
+                                    </tr>
+                                <?php endforeach; endif; ?>
+                                </tbody>
+                            </table>
+                        </div>
 
-                                        </ul>
-                                    </div>
-                                </div>
-                            </div>
-                            <div class="float-right">
-                                <div class="dropdown">
-                                    <a href="#" title="" class="btn btn-default" data-toggle="dropdown" aria-expanded="false">
-                                        <i class="fa fa-cog icon_8"></i>
-                                        <i class="fa fa-chevron-down icon_8"></i>
-                                        <div class="ripple-wrapper"></div>
-                                    </a>
-                                    <ul class="dropdown-menu float-right">
-                                        <li>
-                                            <a href="#" title="">
-                                                <i class="fa fa-pencil-square-o icon_9"></i>
-                                                Edit
-                                            </a>
-                                        </li>
-                                        <li>
-                                            <a href="#" title="">
-                                                <i class="fa fa-calendar icon_9"></i>
-                                                Schedule
-                                            </a>
-                                        </li>
-                                        <li>
-                                            <a href="#" title="">
-                                                <i class="fa fa-download icon_9"></i>
-                                                Download
-                                            </a>
-                                        </li>
-                                        <li>
-                                            <a href="#" class="font-red" title="">
-                                                <i class="fa fa-times" icon_9=""></i>
-                                                Delete
-                                            </a>
-                                        </li>
-                                    </ul>
-                                </div>
-                                <div class="btn-group">
-                                    <a class="btn btn-default"><i class="fa fa-angle-left"></i></a>
-                                    <a class="btn btn-default"><i class="fa fa-angle-right"></i></a>
-                                </div>
-                            </div>
+                        <!-- Compost -->
+                        <div class="tab-pane fade" id="compost-reports" role="tabpanel">
+                            <h5 class="mt-2">Compost Test Records</h5>
+                            <p class="text-muted">Compost records pending migration.</p>
                         </div>
-                        <table class="table">
-                            <tbody>
-                                <!-- /* ******************************************************************************** */ -->
-                            </tbody>
-                        </table>
+
                     </div>
                 </div>
             </div>
-        </div>
+        </main>
+
+        <?php include __DIR__ . '/../layouts/footer.php'; ?>
     </div>
-</div>
+</div>

+ 128 - 150
dashboard/inbox_email.php

@@ -1,158 +1,136 @@
-[[!profile]]
-
-<div class="container-fluid">
-
-<form class="needs-validation" id="email-form" method="post" action="" role="form" enctype="multipart/form-data" novalidate><!-- ~[*id*]~] -->
-	
-	<div class="input-group mb-3">
-		<div class="input-group-prepend">
-			<span class="input-group-text" id="basic-addon1"><i class="far fa-paper-plane"></i></span>
-		</div>
-		<input type="email" class="form-control font-weight-bold" name="emailFrom" id="emailFrom" required="required" value='[[+fullname]] <[[+email]]>' readonly>
-	</div>
-	
-	<input type="hidden" hidden class="form-control" name="emailFrom" id="emailFrom" required="required" value='[[+email]]' >
-	<input type="hidden" hidden class="form-control" name="emailFromName" id="emailFromName" required="required" value='[[+fullname]]' >
-	<input type="hidden" class="form-control" name="emailReply" id="emailReply" required="required" value='[[+email]]' >
-	<input type="hidden" hidden class="form-control" name="emailReplyName" id="emailReplyName" required="required" value='[[+fullname]]' >
-	
-	<div class="input-group mb-3">
-		<div class="input-group-prepend">
-			<span class="input-group-text" id="emailAddress"><i class="fas fa-envelope-open"></i></span>
-		</div>
-		<input type="email" class="form-control" name="emailAddress" id="emailAddress" aria-describedby="emailAddress" placeholder="To" required="required">
-		<div id="emailAddress" class="invalid-feedback">Please provide a valid email address</div>
-	</div>
-	
-	<div class="input-group mb-3">
-		<div class="input-group-prepend">
-			<span class="input-group-text" id="carboncopy"><i class="far fa-envelope-open"></i></span>
-		</div>
-		<input type="email" class="form-control" name="carboncopy" id="carboncopy" aria-describedby="carboncopy" placeholder="CC" >
-	</div>
-	
-	<div class="input-group mb-3">
-		<div class="input-group-prepend">
-			<span class="input-group-text" id="subject"><i class="far fa-comment"></i></span>
-		</div>
-		<input type="text" class="form-control" name="subject" id="subject" aria-describedby="emailHelp" placeholder="Email Subject" required="required">
-		<div id="emailAddress" class="invalid-feedback">Please provide a email subject</div>
-	</div>
-
-	<div class="input-group mb-3">
-		<div class="input-group-prepend">
-			<span class="input-group-text" id="attachment"><i class="fas fa-paperclip"></i></span>
-		</div>
-		<div class="custom-file attachment-row">
-			<input type="file" class="custom-file-input" name="attachment[]" id="attachment" aria-describedby="attachment">
-			<label class="custom-file-label" for="inputGroupFile01"></label>
-		</div>
-		<!--
-		<div onClick="addMoreAttachment();" class="icon-add-more-attachemnt" title="Add More Attachments">
-			<i class="fas fa-folder-plus"></i>
-		</div>
-		-->
-	</div>
-	<div class="form-group">
-		<label for="exampleFormControlTextarea1">Email Message</label>
-		<textarea class="form-control" name="emailBody" id="emailBody" rows="10">
-		    
-		    <br>Regards<br>
-		    <b>[[+fullname]]</b><br>
-		    <span class="text-success">Crop Monitor Consultant</span><br>
-		    <b>Mob:</b> 0417 728 061 | <b>Email:</b> [[+email]]<br><br>
-		    <img src="client-assets/images/crop-monitor.png" alt="Crop Monitor" style="width:10%;"></img><br> <!-- src="[[+photo]]" -->
-		 
-		 </textarea>
-		<div class="valid-feedback"></div>
-	</div>
-	<button name="send" id="send" type="submit" class="btn btn-primary">Submit</button>
-	
-</form>
-
-[[!formSubmit]]
+<?php
+require_once __DIR__ . '/../config/database.php';
+require_once __DIR__ . '/../lib/auth.php';
+require_once __DIR__ . '/../lib/csrf.php';
 
-</div>
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
 
-<script type="text/javascript">
-    // Example starter JavaScript for disabling form submissions if there are invalid fields
-    (function() {
-      'use strict';
-      window.addEventListener('load', function() {
-        // Fetch all the forms we want to apply custom Bootstrap validation styles to
-        var forms = document.getElementsByClassName('needs-validation');
-        // Loop over them and prevent submission
-        var validation = Array.prototype.filter.call(forms, function(form) {
-          form.addEventListener('submit', function(event) {
-            if (form.checkValidity() === false) {
-              event.preventDefault();
-              event.stopPropagation();
-            }
-            form.classList.add('was-validated');
-          }, false);
-        });
-      }, false);
-    })();
-</script>
+requireLogin();
 
+$pageTitle = 'Compose Email';
+$siteName  = 'Crop Monitor';
 
-<script type="text/javascript">
-    
-    function validate() {
-        var valid = true;
-
-        $(".valid-feedback").html("");
-        var userName = document.forms["email-form"]["emailAddress"].value;
-        var subject = document.forms["email-form"]["subject"].value;
-        var userMessage = document.forms["email-form"]["emailBody"].value;
-        
-        if (emailAddress == "") {
-            $("#emailAddress").html("(required)");
-            $("#emailAddress").css('background-color', '#FFFFDF');
-            valid = false;
-        }
-        if (!emailAddress.match(/^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/))
-        {
-            $("#emailAddress").html("(invalid)");
-            $("#emailAddress").css('background-color', '#FFFFDF');
-            valid = false;
-        }
-
-        if (subject == "") {
-            $("#subject").html("(required)");
-            $("#subject").css('background-color', '#FFFFDF');
-            valid = false;
-        }
-        if (v == "") {
-            $("#emailBody").html("(required)");
-            $("#emailBody").css('background-color', '#FFFFDF');
-            valid = false;
-        }
-        return valid;
-    }
-    /*
-    function addMoreAttachment() {
-        $(".attachment-row:last").clone().insertAfter(".attachment-row:last");
-        $(".attachment-row:last").find("input").val("");
+$user     = getCurrentUser() ?? [];
+$fullname = $user['fullname'] ?? '';
+$email    = $user['email']    ?? '';
+
+$errors  = [];
+$success = false;
+
+// Email sending requires PHPMailer — currently a stub
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['send'])) {
+    if (!verifyCsrfToken($_POST['csrf_token'] ?? '')) {
+        $errors[] = 'Invalid CSRF token.';
+    } else {
+        // TODO: configure PHPMailer / SMTP and send email
+        $errors[] = 'Email sending is not yet configured. Please set up SMTP in lib/mailer.php.';
     }
-    */
-</script>
+}
+
+$h = fn($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8');
+
+include __DIR__ . '/../layouts/header.php';
+include __DIR__ . '/../layouts/navbar.php';
+?>
+
+<div id="layoutSidenav">
+    <div id="layoutSidenav_nav">
+        <?php include __DIR__ . '/../layouts/sidebar.php'; ?>
+    </div>
+    <div id="layoutSidenav_content">
+        <main>
+            <div class="container-fluid px-4">
+                <h1 class="mt-4"><?= $h($pageTitle) ?></h1>
+                <ol class="breadcrumb mb-4">
+                    <li class="breadcrumb-item"><a href="/dashboard/dashboard.php">Dashboard</a></li>
+                    <li class="breadcrumb-item active">Compose Email</li>
+                </ol>
+
+                <?php foreach ($errors as $err): ?>
+                    <div class="alert alert-danger"><?= $h($err) ?></div>
+                <?php endforeach; ?>
+                <?php if ($success): ?>
+                    <div class="alert alert-success">Email sent successfully.</div>
+                <?php endif; ?>
 
+                <div class="row">
+                    <div class="col-md-8">
+                        <form class="needs-validation" id="email-form" method="post"
+                              action="" enctype="multipart/form-data" novalidate>
+                            <input type="hidden" name="csrf_token" value="<?= $h(generateCsrfToken()) ?>">
 
-<script src="https://cloud.tinymce.com/stable/tinymce.min.js?apiKey=xcotawi18mg1imp8im144buq68h9g3ndd3c9c8215w8qu3ld"></script>
-    <script>
-        tinymce.init({
-            selector: 'textarea',
-            menubar: false,
-            plugins: [
-    "advlist autolink lists link image charmap print preview anchor",
-    "searchreplace visualblocks code fullscreen",
-    "insertdatetime media table paste imagetools wordcount"
-  ],
-  toolbar: 'insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link ',
-  content_css: [
-    '//fonts.googleapis.com/css?family=Lato:300,300i,400,400i',
-    '//www.tiny.cloud/css/codepen.min.css'
-  ]
+                            <!-- From -->
+                            <div class="input-group mb-3">
+                                <span class="input-group-text"><i class="far fa-paper-plane"></i></span>
+                                <input type="text" class="form-control fw-bold"
+                                       value="<?= $h($fullname) ?> <<?= $h($email) ?>>" readonly>
+                            </div>
+                            <input type="hidden" name="emailFrom"     value="<?= $h($email) ?>">
+                            <input type="hidden" name="emailFromName" value="<?= $h($fullname) ?>">
+                            <input type="hidden" name="emailReply"    value="<?= $h($email) ?>">
+                            <input type="hidden" name="emailReplyName" value="<?= $h($fullname) ?>">
+
+                            <!-- To -->
+                            <div class="input-group mb-3">
+                                <span class="input-group-text"><i class="fas fa-envelope-open"></i></span>
+                                <input type="email" class="form-control" name="emailAddress"
+                                       placeholder="To" required>
+                                <div class="invalid-feedback">Please provide a valid email address.</div>
+                            </div>
+
+                            <!-- CC -->
+                            <div class="input-group mb-3">
+                                <span class="input-group-text"><i class="far fa-envelope-open"></i></span>
+                                <input type="email" class="form-control" name="carboncopy" placeholder="CC">
+                            </div>
+
+                            <!-- Subject -->
+                            <div class="input-group mb-3">
+                                <span class="input-group-text"><i class="far fa-comment"></i></span>
+                                <input type="text" class="form-control" name="subject"
+                                       placeholder="Email Subject" required>
+                                <div class="invalid-feedback">Please provide an email subject.</div>
+                            </div>
+
+                            <!-- Attachment -->
+                            <div class="input-group mb-3">
+                                <span class="input-group-text"><i class="fas fa-paperclip"></i></span>
+                                <input type="file" class="form-control" name="attachment[]">
+                            </div>
+
+                            <!-- Body -->
+                            <div class="mb-3">
+                                <label class="form-label">Email Message</label>
+                                <textarea class="form-control" name="emailBody" id="emailBody" rows="10"><?= "\n\nRegards\n" . $h($fullname) . "\nCrop Monitor Consultant\n" . $h($email) ?></textarea>
+                            </div>
+
+                            <button name="send" id="send" type="submit" class="btn btn-primary">Submit</button>
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </main>
+
+        <?php include __DIR__ . '/../layouts/footer.php'; ?>
+    </div>
+</div>
+
+<script>
+(function () {
+    'use strict';
+    window.addEventListener('load', function () {
+        var forms = document.getElementsByClassName('needs-validation');
+        Array.prototype.forEach.call(forms, function (form) {
+            form.addEventListener('submit', function (event) {
+                if (!form.checkValidity()) {
+                    event.preventDefault();
+                    event.stopPropagation();
+                }
+                form.classList.add('was-validated');
+            }, false);
         });
-    </script>
+    }, false);
+})();
+</script>

+ 132 - 0
dashboard/pesticide.php

@@ -0,0 +1,132 @@
+<!doctype html>
+<html lang="en">
+
+<?php
+require_once __DIR__ . '/../config/database.php';
+require_once __DIR__ . '/../lib/auth.php';
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+requireLogin();
+
+$pageTitle = 'Pesticide';
+$siteName  = 'Crop Monitor';
+$userId    = getCurrentUserId();
+
+include __DIR__ . '/../layouts/header.php';
+include __DIR__ . '/../layouts/navbar.php';
+?>
+    
+	
+        
+        
+    <div class="col-md-6 form-group">
+
+        <div class="row">
+            
+            <div class="col-md-4 form-group">
+                <label class="form-top">Product Concentration</label>
+                <input class="form-control" type="number" name="concentration" id="concentration" placeholder="concentration">
+            </div>
+    
+            <div class="col-md-2 form-group">
+                <label class="form-top">Unit of Rate</label>
+                <div class="input-group">
+                    <select class="form-control" id="concentration_unit" name="concentration_unit">
+                        <option value="1">kg/ha</option>
+                        <option value="2">l/ha</option>
+                        <option value="3">dl/ha</option>
+                        <option value="4">dag/ha</option>
+                        <option value="5">cl/ha</option>
+                        <option value="6">g/ha</option>
+                        <option value="7">ml/ha</option>
+                        <option value="8">%</option>
+                    </select>
+                </div>
+            </div>
+
+            <div class="col-md-4 form-group">
+                <label class="form-top">Water amount</label>
+                <input type="number" name="water" id="water" class="form-control" placeholder="water amount" value="400">
+            </div>
+            
+            <div class="col-md-2 form-group">
+                <label class="form-top">Rate</label>
+                <select class="form-control" id="water_unit" name="quantity_unit">
+                    <option value="1">l/ha</option>
+                </select>
+            </div>
+        </div>
+        
+        <div class="row">
+            <div class="col-md-10 form-group">
+                <label class="form-top">Terrain</label>
+                <input type="number" name="terrain_size" id="terrain_size" class="form-control" placeholder="terrain size">
+            </div>
+            
+            <div class="col-md-2 form-group">
+                <label class="form-top">Area</label>
+                <select class="form-control" id="terrain_unit" name="terrain_unit">
+                    <option value="1">ha</option>
+                    <option value="2">a</option>
+                    <option value="3">m2</option>
+                </select>
+            </div>
+        </div>
+    
+        <div class="row">
+            <div class="col-md-12">
+                <p class="text-center" id="text">You can also calculate how much of plant protector you need per each sprayer</p>
+            </div>
+        </div>
+        
+        <div class="row">
+            <div class="col-md-10 form-group">
+                <label class="form-top">Sprayer / Applicator</label>
+                <input type="number" name="sprayer" id="sprayer" class="form-control" placeholder="sprayer capacity">
+            </div>
+            
+            <div class="col-md-2 form-group">
+                <label class="form-top">Rate</label>
+                <select class="form-control" id="sprayer_unit" name="sprayer_unit">
+                    <option value="1">l</option>
+                </select>
+            </div>
+        </div>
+        
+        <div class="row">
+            <div class="col-md-12">
+            <input type="button" class="btn btn-success" role="button" id="calc_button" onclick="javascript:getQuantity()" value="Calculate">
+            </div>
+        </div>
+    
+    </div>
+    
+    <div class="col-md-6 form-group">
+        
+        <div class="row">
+            <div class="col-md-12">
+                <h5 id="result-sprayer-text"></h4>
+                <h4 id="result-sprayer"></h3>
+                <h5 id="result-text"></h4>
+                <h4 id="result"></h3>
+            </div>
+        </div>
+        
+    </div>
+</div>
+
+<!-- Custom Theme JavaScript -->
+<script src="/client-assets/js/pesticide.js"></script>
+
+					</div>
+
+				</div>
+
+			</main>
+			
+<?php include __DIR__ . '/../layouts/footer.php'; ?>
+</div>
+</div>

+ 37 - 7
dashboard/planning-calendar.php

@@ -1,3 +1,30 @@
+<?php
+require_once __DIR__ . '/../config/database.php';
+require_once __DIR__ . '/../lib/auth.php';
+
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+requireLogin();
+
+$pageTitle = 'Planning Calendar';
+$siteName  = 'Crop Monitor';
+$userId    = getCurrentUserId();
+
+include __DIR__ . '/../layouts/header.php';
+include __DIR__ . '/../layouts/navbar.php';
+?>
+
+<div id="layoutSidenav">
+<div id="layoutSidenav_nav"><?php include __DIR__ . '/../layouts/sidebar.php'; ?></div>
+<div id="layoutSidenav_content"><main><div class="container-fluid px-4">
+<h1 class="mt-4"><?= htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8') ?></h1>
+<ol class="breadcrumb mb-4">
+    <li class="breadcrumb-item"><a href="/dashboard/dashboard.php">Dashboard</a></li>
+    <li class="breadcrumb-item active">Planning Calendar</li>
+</ol>
+
 <!-- custom scripts --> 
 <script type="text/javascript" src='client-assets/fullcalendar/js/moment.min.js'></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/locale/en-au.js"></script>
@@ -63,7 +90,7 @@
           <label class="control-label" for="inputPatient">Event:</label>
           <div class="field desc">
             <input class="form-control" id="title" name="title" placeholder="Event" type="text" value="">
-            <input type="hidden" id="modx_user_id" value="[[+modx.user.id]]">
+            <input type="hidden" id="modx_user_id" value="<?= (int) $userId ?>">
           </div>
         </div>
         
@@ -361,7 +388,7 @@
         eventDrop: function(event, delta){ // event drag and drop
            $.ajax({
                //url: 'index.php',
-               url: '[[!calendar_index]]',
+               url: '/api/calendar.php',
                data: 'action=update&title='+event.title+'&start='+moment(event.start).format()+'&end='+moment(event.end).format()+'&id='+event.id+'&modx_user_id='+modx_user_id ,
                type: "POST",
                success: function(json) {
@@ -373,7 +400,7 @@
         eventResize: function(event) {  // resize to increase or decrease time of event
            $.ajax({
                //url: 'index.php',
-               url: '[[!calendar_index]]',
+               url: '/api/calendar.php',
                data: 'action=update&title='+event.title+'&start='+moment(event.start).format()+'&end='+moment(event.end).format()+'&id='+event.id+'&modx_user_id='+modx_user_id,
                type: "POST",
                success: function(json) {
@@ -399,7 +426,7 @@
        var modx_user_id = $('#modx_user_id').val();
        $.ajax({
            //url: 'index.php',
-           url: '[[!calendar_index]]',
+           url: '/api/calendar.php',
            data: 'action=delete&id='+eventID+'&modx_user_id='+modx_user_id,
            type: "POST",
            success: function(json) {
@@ -427,7 +454,7 @@
        
        $.ajax({
            //url: 'index.php',
-           url: '[[!calendar_index]]',
+           url: '/api/calendar.php',
            data: 'action=add&title='+title+'&start='+startTime+'&end='+endTime+'&modx_user_id='+modx_user_id,
            type: "POST",
            success: function(json) {
@@ -460,7 +487,7 @@
 	   var quid = 		$('#calendarModal #quid').val();
        $.ajax({
            //url: 'index.php',
-           url: '[[!calendar_index]]',
+           url: '/api/calendar.php',
            data: 'action=project_update&id='+eventID+'&quote='+quote+'&title='+title+'&start='+startTime+'&end='+endTime+'&eventType='+eventType+'&quid='+quid,
            type: "POST",
            success: function(json) {
@@ -500,4 +527,7 @@
 
 
 });
-</script>
+</script>
+</div></main>
+<?php include __DIR__ . '/../layouts/footer.php'; ?>
+</div></div>

+ 17 - 80
login/edit-personal-details.php

@@ -1,81 +1,18 @@
+<?php
+/**
+ * login/edit-personal-details.php
+ *
+ * Redirects to the migrated account settings page.
+ * This modX resource body has been superseded by dashboard/client-settings/index.php.
+ */
 
-<div class="container">
-    <div class="login">
-        <h1><a href="[[~1]]">[[++site_name]]</a></h1>
-        <div class="login-bottom">
-            <h2>Update Personal Details</h2>
-    
-            [[!UpdateProfile? &validate=`fullname:required,email:required:email`]]
-            
-            <div class="update-profile">
-                <div class="updprof-error">[[+error.message]]</div>
-                [[+login.update_success:is=`1`:then=`[[%login.profile_updated? &namespace=`login` &topic=`updateprofile`]]`]]
-             
-                <form class="form" action="[[~[[*id]]]]" method="post">
-                    <input type="hidden" name="nospam" value="" />
-             
-                    <label for="fullname">[[!%login.fullname? &namespace=`login` &topic=`updateprofile`]]
-                        <span class="error">[[+error.fullname]]</span>
-                    </label>
-                    <input type="text" name="fullname" id="fullname" value="[[+fullname]]" />
-             
-                    <label for="email">[[!%login.email]]
-                        <span class="error">[[+error.email]]</span>
-                    </label>
-                    <input type="text" name="email" id="email" value="[[+email]]" />
-             
-                    <label for="phone">[[!%login.phone]]
-                        <span class="error">[[+error.phone]]</span>
-                    </label>
-                    <input type="text" name="phone" id="phone" value="[[+phone]]" />
-             
-                    <label for="mobilephone">[[!%login.mobilephone]]
-                        <span class="error">[[+error.mobilephone]]</span>
-                    </label>
-                    <input type="text" name="mobilephone" id="mobilephone" value="[[+mobilephone]]" />
-             
-                    <label for="fax">[[!%login.fax]]
-                        <span class="error">[[+error.fax]]</span>
-                    </label>
-                    <input type="text" name="fax" id="fax" value="[[+fax]]" />
-             
-                    <label for="address">[[!%login.address]]
-                        <span class="error">[[+error.address]]</span>
-                    </label>
-                    <input type="text" name="address" id="address" value="[[+address]]" />
-             
-                    <label for="country">[[!%login.country]]
-                        <span class="error">[[+error.country]]</span>
-                    </label>
-                    <input type="text" name="country" id="country" value="[[+country]]" />
-             
-                    <label for="city">[[!%login.city]]
-                        <span class="error">[[+error.city]]</span>
-                    </label>
-                    <input type="text" name="city" id="city" value="[[+city]]" />
-             
-                    <label for="state">[[!%login.state]]
-                        <span class="error">[[+error.state]]</span>
-                    </label>
-                    <input type="text" name="state" id="state" value="[[+state]]" />
-             
-                    <label for="zip">[[!%login.zip]]
-                        <span class="error">[[+error.zip]]</span>
-                    </label>
-                    <input type="text" name="zip" id="zip" value="[[+zip]]" />
-             
-                    <label for="website">[[!%login.website]]
-                        <span class="error">[[+error.website]]</span>
-                    </label>
-                    <input type="text" name="website" id="website" value="[[+website]]" />
-             
-                    <br class="clear" />
-             
-                    <div class="form-buttons">
-                        <input type="submit" name="login-updprof-btn" value="[[!%login.update_profile]]" />
-                    </div>
-                </form>
-            </div>
-        </div>
-    </div>
-</div>
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
+
+require_once __DIR__ . '/../lib/auth.php';
+
+requireLogin();
+
+header('Location: /dashboard/client-settings/index.php');
+exit;

+ 16 - 11
login/thanks-for-registering.php

@@ -1,15 +1,20 @@
-<body>
-    <div class="container">
-        <div class="row">
-            <div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
-                <div class="card card-signin my-5">
-                    <div class="card-body">
-                        <h1><a class="text-success" href="[[~2]]">[[++site_name]]</a></h1>
-                        <h2>Thanks for Registering</h2>
-                        <p>A confirmation email has been sent to you. Please click on the activation link in this email.</p>
-                    </div>
+<?php
+$pageTitle = 'Thanks for Registering';
+include __DIR__ . '/_head.php';
+?>
+
+<div class="container">
+    <div class="row">
+        <div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
+            <div class="card card-signin my-5">
+                <div class="card-body">
+                    <h1><a class="text-success" href="/dashboard/dashboard.php">Crop Monitor</a></h1>
+                    <h2>Thanks for Registering</h2>
+                    <p>A confirmation email has been sent to you. Please click on the activation link in this email.</p>
                 </div>
             </div>
         </div>
     </div>
-</body>
+</div>
+
+<?php include __DIR__ . '/_foot.php'; ?>