{"id":1437,"date":"2026-04-13T09:40:14","date_gmt":"2026-04-13T09:40:14","guid":{"rendered":"https:\/\/envautomation.com\/?page_id=1437"},"modified":"2026-07-01T09:10:02","modified_gmt":"2026-07-01T09:10:02","slug":"wbgt-heat-stress-monitoring","status":"publish","type":"page","link":"https:\/\/envautomation.com\/?page_id=1437","title":{"rendered":"WBGT -Heat Stress Monitoring"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"1437\" class=\"elementor elementor-1437\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2aa5324 e-flex e-con-boxed e-con e-parent\" data-id=\"2aa5324\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-cb1c64f elementor-widget elementor-widget-html\" data-id=\"cb1c64f\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\r\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\r\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Poppins:wght@300;400;500;600;700;800;900&display=swap\" rel=\"stylesheet\">\r\n\r\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\r\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/jspdf@2.5.1\/dist\/jspdf.umd.min.js\"><\/script>\r\n\r\n<div id=\"env-wbgt-dashboard-premium\">\r\n  <style>\r\n    #env-wbgt-dashboard-premium{\r\n      --bg:#eef3f8;\r\n      --card:#ffffff;\r\n      --line:#d9e3ec;\r\n      --text:#16324a;\r\n      --muted:#6f859a;\r\n      --blue:#2f80ed;\r\n      --blue2:#5fb0ff;\r\n      --green:#63ae3f;\r\n      --yellow:#f0b61a;\r\n      --red:#ea1d1d;\r\n      --navy:#0d3170;\r\n      --shadow:0 14px 36px rgba(18,53,89,.10);\r\n      font-family:\"Poppins\",Arial,sans-serif;\r\n      background:var(--bg);\r\n      padding:18px;\r\n      color:var(--text);\r\n      width:100%;\r\n      max-width:100%;\r\n      overflow-x:hidden;\r\n    }\r\n\r\n    #env-wbgt-dashboard-premium *{\r\n      box-sizing:border-box;\r\n    }\r\n\r\n    .env-wrap{\r\n      max-width:1200px;\r\n      width:100%;\r\n      margin:0 auto;\r\n    }\r\n\r\n    .env-topbar{\r\n      display:flex;\r\n      justify-content:space-between;\r\n      align-items:center;\r\n      gap:12px;\r\n      flex-wrap:wrap;\r\n      margin-bottom:12px;\r\n    }\r\n\r\n    .env-brand{\r\n      display:flex;\r\n      align-items:center;\r\n      gap:12px;\r\n      min-width:0;\r\n    }\r\n\r\n    .env-logo{\r\n      width:42px;\r\n      height:42px;\r\n      border-radius:12px;\r\n      background:linear-gradient(135deg,var(--blue),var(--blue2));\r\n      color:#fff;\r\n      font-weight:900;\r\n      display:grid;\r\n      place-items:center;\r\n      box-shadow:var(--shadow);\r\n      font-size:18px;\r\n      flex:0 0 auto;\r\n    }\r\n\r\n    .env-brand h1{\r\n      margin:0;\r\n      font-size:18px;\r\n      line-height:1.1;\r\n      font-weight:900;\r\n      color:var(--text);\r\n    }\r\n\r\n    .env-brand p{\r\n      margin:3px 0 0;\r\n      font-size:11px;\r\n      color:var(--muted);\r\n      font-weight:600;\r\n    }\r\n\r\n    .env-pills{\r\n      display:flex;\r\n      gap:10px;\r\n      flex-wrap:wrap;\r\n      align-items:center;\r\n    }\r\n\r\n    .env-pill{\r\n      display:flex;\r\n      align-items:center;\r\n      gap:8px;\r\n      padding:9px 13px;\r\n      border-radius:999px;\r\n      background:#fff;\r\n      border:1px solid var(--line);\r\n      box-shadow:var(--shadow);\r\n      font-size:11px;\r\n      font-weight:700;\r\n      color:#425a73;\r\n      white-space:nowrap;\r\n    }\r\n\r\n    .env-dot{\r\n      width:8px;\r\n      height:8px;\r\n      border-radius:50%;\r\n      background:#27b24f;\r\n      box-shadow:0 0 0 4px rgba(39,178,79,.12);\r\n    }\r\n\r\n    .env-logout-btn{\r\n      height:36px;\r\n      padding:0 15px;\r\n      border-radius:999px;\r\n      border:0;\r\n      cursor:pointer;\r\n      background:linear-gradient(135deg,#ef4444,#dc2626);\r\n      color:#fff;\r\n      font-size:11px;\r\n      font-weight:900;\r\n      font-family:\"Poppins\",Arial,sans-serif;\r\n      box-shadow:0 10px 22px rgba(220,38,38,.18);\r\n      display:inline-flex;\r\n      align-items:center;\r\n      justify-content:center;\r\n      white-space:nowrap;\r\n    }\r\n\r\n    .env-status-row{\r\n      display:grid;\r\n      grid-template-columns:repeat(3,1fr);\r\n      gap:10px;\r\n      margin-bottom:12px;\r\n    }\r\n\r\n    .env-status-box{\r\n      color:#fff;\r\n      padding:11px 14px;\r\n      border-radius:14px 14px 0 0;\r\n      font-size:13px;\r\n      font-weight:900;\r\n      line-height:1.3;\r\n      box-shadow:var(--shadow);\r\n    }\r\n\r\n    .env-status-box small{\r\n      display:block;\r\n      font-size:12px;\r\n      font-weight:800;\r\n    }\r\n\r\n    .env-low{background:var(--green);}\r\n    .env-mid{background:var(--yellow);}\r\n    .env-high{background:var(--red);}\r\n\r\n    .env-main-card{\r\n      background:var(--card);\r\n      border:1px solid var(--line);\r\n      border-radius:22px;\r\n      box-shadow:var(--shadow);\r\n      padding:18px;\r\n      margin-bottom:14px;\r\n    }\r\n\r\n    .env-main-grid{\r\n      display:grid;\r\n      grid-template-columns:1.08fr .50fr;\r\n      gap:18px;\r\n      align-items:start;\r\n    }\r\n\r\n    .env-title{\r\n      margin:0 0 2px;\r\n      font-size:20px;\r\n      font-weight:900;\r\n      color:var(--text);\r\n    }\r\n\r\n    .env-sub{\r\n      margin:0 0 12px;\r\n      font-size:12px;\r\n      color:var(--muted);\r\n      font-weight:600;\r\n    }\r\n\r\n    .env-chart-box{\r\n      height:300px;\r\n      width:100%;\r\n      max-width:100%;\r\n    }\r\n\r\n    .env-side-panel{\r\n      display:flex;\r\n      flex-direction:column;\r\n      align-items:center;\r\n      gap:14px;\r\n      min-width:0;\r\n    }\r\n\r\n    .env-circle-card{\r\n      position:relative;\r\n      width:100%;\r\n      display:flex;\r\n      align-items:center;\r\n      justify-content:center;\r\n      padding:8px 0 2px;\r\n    }\r\n\r\n    .env-circle-link{\r\n      display:inline-flex;\r\n      align-items:center;\r\n      justify-content:center;\r\n      text-decoration:none;\r\n      cursor:pointer;\r\n    }\r\n\r\n    .env-circle-glow{\r\n      position:absolute;\r\n      width:150px;\r\n      height:150px;\r\n      border-radius:50%;\r\n      filter:blur(22px);\r\n      opacity:.18;\r\n      z-index:0;\r\n      animation:envGlowPulse 3.2s ease-in-out infinite;\r\n      pointer-events:none;\r\n    }\r\n\r\n    @keyframes envGlowPulse{\r\n      0%,100%{transform:scale(1);opacity:.16;}\r\n      50%{transform:scale(1.08);opacity:.28;}\r\n    }\r\n\r\n    .premium-circle{\r\n      width:132px;\r\n      height:132px;\r\n      border-radius:50%;\r\n      position:relative;\r\n      display:grid;\r\n      place-items:center;\r\n      box-shadow:0 18px 40px rgba(15,40,70,.14), inset 0 0 14px rgba(255,255,255,.35);\r\n      animation:envFloatCircle 3.8s ease-in-out infinite;\r\n      overflow:hidden;\r\n    }\r\n\r\n    .premium-circle::after{\r\n      content:\"\";\r\n      position:absolute;\r\n      inset:11px;\r\n      border-radius:50%;\r\n      background:linear-gradient(180deg,#ffffff 0%, #f8fbff 100%);\r\n      z-index:2;\r\n      box-shadow:inset 0 0 18px rgba(10,35,60,.06);\r\n    }\r\n\r\n    @keyframes envFloatCircle{\r\n      0%,100%{transform:translateY(0);}\r\n      50%{transform:translateY(-5px);}\r\n    }\r\n\r\n    .env-circle-center{\r\n      position:relative;\r\n      z-index:3;\r\n      text-align:center;\r\n      width:78px;\r\n    }\r\n\r\n    .env-circle-value{\r\n      font-size:31px;\r\n      line-height:1;\r\n      font-weight:900;\r\n      letter-spacing:-.6px;\r\n      color:#17324a;\r\n    }\r\n\r\n    .env-circle-unit{\r\n      margin-top:6px;\r\n      font-size:10px;\r\n      font-weight:800;\r\n      letter-spacing:1.8px;\r\n      color:#7d90a3;\r\n      text-transform:uppercase;\r\n    }\r\n\r\n    .premium-advice{\r\n      width:100%;\r\n      border-radius:18px;\r\n      overflow:hidden;\r\n      border:1px solid rgba(214,224,236,.95);\r\n      box-shadow:0 14px 30px rgba(18,53,89,.10), inset 0 1px 0 rgba(255,255,255,.4);\r\n      background:#fff;\r\n    }\r\n\r\n    .env-advice-head{\r\n      color:#fff;\r\n      font-size:12px;\r\n      font-weight:900;\r\n      line-height:1.28;\r\n      padding:11px 12px 10px;\r\n      letter-spacing:.2px;\r\n    }\r\n\r\n    .env-advice-head small{\r\n      display:block;\r\n      font-size:10px;\r\n      font-weight:800;\r\n      margin-top:3px;\r\n      opacity:.98;\r\n    }\r\n\r\n    .env-advice-body{\r\n      font-size:10.5px;\r\n      line-height:1.52;\r\n      color:#334e67;\r\n      font-weight:600;\r\n      padding:11px 12px 10px;\r\n      min-height:170px;\r\n    }\r\n\r\n    .env-advice-typing ul{\r\n      margin:0 0 8px 14px;\r\n      padding:0;\r\n    }\r\n\r\n    .env-advice-typing b{\r\n      display:block;\r\n      font-size:10.5px;\r\n      margin:0 0 3px;\r\n      color:#244058;\r\n      font-weight:900;\r\n    }\r\n\r\n    .env-alert-bar{\r\n      margin-top:10px;\r\n      padding:12px 14px;\r\n      border-radius:14px;\r\n      font-size:12px;\r\n      font-weight:800;\r\n      border:1px solid #e7d48a;\r\n      background:linear-gradient(90deg,#fff0c4,#f7df93);\r\n      color:#795600;\r\n    }\r\n\r\n    .env-controls{\r\n      display:grid;\r\n      grid-template-columns:1.3fr 1fr 1fr auto auto;\r\n      gap:8px;\r\n      align-items:end;\r\n      margin-bottom:10px;\r\n      width:100%;\r\n      max-width:100%;\r\n    }\r\n\r\n    .env-field{\r\n      display:flex;\r\n      flex-direction:column;\r\n      gap:6px;\r\n      min-width:0;\r\n      width:100%;\r\n    }\r\n\r\n    .env-label{\r\n      font-size:12px;\r\n      font-weight:800;\r\n      color:#334f68;\r\n      line-height:1.2;\r\n      min-height:16px;\r\n      white-space:nowrap;\r\n    }\r\n\r\n    .env-input{\r\n      height:42px;\r\n      padding:0 12px;\r\n      border:1px solid #ccd7e2;\r\n      border-radius:9px;\r\n      background:#fff;\r\n      font-size:13px;\r\n      color:var(--text);\r\n      font-family:\"Poppins\",Arial,sans-serif;\r\n      outline:none;\r\n      width:100%;\r\n      max-width:100%;\r\n      min-width:0;\r\n      display:block;\r\n    }\r\n\r\n    .env-btn{\r\n      height:42px;\r\n      padding:0 16px;\r\n      border-radius:9px;\r\n      border:0;\r\n      cursor:pointer;\r\n      font-size:13px;\r\n      font-weight:900;\r\n      font-family:\"Poppins\",Arial,sans-serif;\r\n      white-space:nowrap;\r\n      align-self:end;\r\n      width:auto;\r\n    }\r\n\r\n    .env-btn-search{\r\n      background:linear-gradient(135deg,var(--blue),var(--blue2));\r\n      color:#fff;\r\n    }\r\n\r\n    .env-btn-pdf{\r\n      background:#fff;\r\n      color:#236fd6;\r\n      border:2px solid #2f80ed;\r\n    }\r\n\r\n    .env-table-wrap{\r\n      background:#fff;\r\n      border:1px solid var(--line);\r\n      border-radius:16px;\r\n      overflow:hidden;\r\n      box-shadow:var(--shadow);\r\n      width:100%;\r\n      max-width:100%;\r\n    }\r\n\r\n    .env-table-scroll{\r\n      overflow:auto;\r\n      width:100%;\r\n      max-width:100%;\r\n      max-height:620px;\r\n      border-radius:0 0 16px 16px;\r\n    }\r\n\r\n    table{\r\n      width:100%;\r\n      border-collapse:collapse;\r\n      min-width:900px;\r\n    }\r\n\r\n    thead th{\r\n      background:#0d3170;\r\n      color:#fff;\r\n      font-size:12px;\r\n      font-weight:800;\r\n      text-align:left;\r\n      padding:11px 10px;\r\n      border-right:2px solid rgba(255,255,255,.12);\r\n      white-space:nowrap;\r\n      position:sticky;\r\n      top:0;\r\n      z-index:5;\r\n    }\r\n\r\n    tbody td{\r\n      font-size:12px;\r\n      font-weight:600;\r\n      color:#29445d;\r\n      padding:12px 10px;\r\n      border-right:1px solid #dbe5ef;\r\n      border-bottom:1px solid #dbe5ef;\r\n      background:#eef4fa;\r\n      vertical-align:middle;\r\n    }\r\n\r\n    tbody tr:nth-child(even) td{\r\n      background:#e7eef6;\r\n    }\r\n\r\n    .env-empty{\r\n      padding:20px;\r\n      text-align:center;\r\n      color:var(--muted);\r\n      font-size:13px;\r\n      font-weight:700;\r\n      background:#f8fbff;\r\n    }\r\n\r\n    .env-thumb-link{\r\n      display:inline-flex;\r\n      align-items:center;\r\n      justify-content:center;\r\n      text-decoration:none;\r\n    }\r\n\r\n    .env-thumb{\r\n      width:88px;\r\n      height:62px;\r\n      object-fit:cover;\r\n      border-radius:10px;\r\n      border:1px solid #cfd9e4;\r\n      box-shadow:0 4px 10px rgba(0,0,0,.08);\r\n      display:block;\r\n      background:#f5f8fb;\r\n    }\r\n\r\n    .env-no-photo{\r\n      display:inline-flex;\r\n      align-items:center;\r\n      justify-content:center;\r\n      width:88px;\r\n      height:62px;\r\n      border-radius:10px;\r\n      border:1px dashed #cfd9e4;\r\n      background:#f8fbff;\r\n      color:#8aa0b5;\r\n      font-size:11px;\r\n      font-weight:700;\r\n    }\r\n\r\n    .env-footer{\r\n      text-align:center;\r\n      margin-top:12px;\r\n      font-size:12px;\r\n      color:#8094a7;\r\n      font-weight:700;\r\n    }\r\n\r\n    @media (max-width:980px){\r\n      .env-status-row{grid-template-columns:1fr;}\r\n      .env-main-grid{grid-template-columns:1fr;}\r\n      .env-controls{grid-template-columns:minmax(0,1fr) minmax(0,1fr);}\r\n      .env-controls .env-field:first-child{grid-column:1 \/ -1;}\r\n      .env-controls .env-btn{width:100%;}\r\n    }\r\n\r\n    @media (max-width:640px){\r\n      #env-wbgt-dashboard-premium{padding:12px;}\r\n      .env-wrap{width:100%;max-width:100%;}\r\n      .env-topbar{align-items:flex-start;}\r\n      .env-brand{width:100%;}\r\n      .env-brand h1{font-size:16px;}\r\n      .env-brand p{font-size:10px;}\r\n      .env-pills{width:100%;display:grid;grid-template-columns:1fr;gap:8px;}\r\n      .env-pill{width:100%;justify-content:center;}\r\n      .env-logout-btn{width:100%;}\r\n      .env-status-box{border-radius:14px;}\r\n      .env-main-card{padding:14px;border-radius:18px;}\r\n      .env-title{font-size:17px;}\r\n      .env-chart-box{height:240px;}\r\n      .premium-circle{width:110px;height:110px;}\r\n      .env-circle-value{font-size:24px;}\r\n      .env-controls{display:grid;grid-template-columns:1fr;gap:10px;width:100%;max-width:100%;}\r\n      .env-btn{width:100%;}\r\n      .env-table-scroll{max-height:620px;}\r\n      .env-thumb,.env-no-photo{width:72px;height:52px;}\r\n    }\r\n  <\/style>\r\n\r\n  <div class=\"env-wrap\" id=\"pdfArea\">\r\n    <div class=\"env-topbar\">\r\n      <div class=\"env-brand\">\r\n        <div class=\"env-logo\">EA<\/div>\r\n        <div>\r\n          <h1>ENV AUTOMATION PTE LTD<\/h1>\r\n          <p>ENV Automation Pte Ltd \u2022 Secure live dashboard<\/p>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div class=\"env-pills\">\r\n        <div class=\"env-pill\"><span class=\"env-dot\"><\/span>System Online<\/div>\r\n        <div class=\"env-pill\">Last Update: <strong id=\"envLiveTime\">--:--:--<\/strong><\/div>\r\n        <button class=\"env-logout-btn env-no-pdf\" id=\"btnLogout\" type=\"button\">Logout<\/button>\r\n      <\/div>\r\n    <\/div>\r\n\r\n    <div class=\"env-status-row\">\r\n      <div class=\"env-status-box env-low\">LOW HEAT STRESS<small>WBGT (\u00b0C) &lt; 31<\/small><\/div>\r\n      <div class=\"env-status-box env-mid\">MODERATE HEAT STRESS<small>31 \u2264 WBGT (\u00b0C) &lt; 33<\/small><\/div>\r\n      <div class=\"env-status-box env-high\">HIGH HEAT STRESS<small>WBGT (\u00b0C) \u2265 33<\/small><\/div>\r\n    <\/div>\r\n\r\n    <div class=\"env-main-card\">\r\n      <div class=\"env-main-grid\">\r\n        <div>\r\n          <h2 class=\"env-title\">KIMLY CONSTRUCTION PTE LTD<\/h2>\r\n          <p class=\"env-sub\">WBGT HEAT STRESS MONITORING - KBGC3<\/p>\r\n          <div class=\"env-chart-box\">\r\n            <canvas id=\"envWbgtChart\"><\/canvas>\r\n          <\/div>\r\n        <\/div>\r\n\r\n        <div class=\"env-side-panel\">\r\n          <div class=\"env-circle-card\">\r\n            <div class=\"env-circle-glow\"><\/div>\r\n            <a class=\"env-circle-link\" href=\"https:\/\/envautomation.com\/?page_id=1471\" title=\"Open Temperature Monitoring Record Page\">\r\n              <div class=\"premium-circle\" id=\"wbgtCircle\">\r\n                <div class=\"env-circle-center\">\r\n                  <div class=\"env-circle-value\" id=\"wbgtValue\">--<\/div>\r\n                  <div class=\"env-circle-unit\">WBGT \u00b0C<\/div>\r\n                <\/div>\r\n              <\/div>\r\n            <\/a>\r\n          <\/div>\r\n\r\n          <div class=\"premium-advice\">\r\n            <div class=\"env-advice-head\" id=\"adviceHead\">\r\n              WBGT STATUS\r\n              <small>Waiting for live data<\/small>\r\n            <\/div>\r\n            <div class=\"env-advice-body\">\r\n              <div class=\"env-advice-typing\" id=\"adviceTyping\"><\/div>\r\n            <\/div>\r\n          <\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div class=\"env-alert-bar\" id=\"envAlertBar\">\r\n        Waiting for live WBGT data...\r\n      <\/div>\r\n    <\/div>\r\n\r\n    <div class=\"env-controls\">\r\n      <div class=\"env-field\">\r\n        <label class=\"env-label\" for=\"searchInput\">Search<\/label>\r\n        <input id=\"searchInput\" class=\"env-input\" type=\"text\" placeholder=\"Search company name \/ conducted by \/ date\">\r\n      <\/div>\r\n\r\n      <div class=\"env-field\">\r\n        <label class=\"env-label\" for=\"fromDate\">From Date<\/label>\r\n        <input id=\"fromDate\" class=\"env-input\" type=\"date\">\r\n      <\/div>\r\n\r\n      <div class=\"env-field\">\r\n        <label class=\"env-label\" for=\"toDate\">To Date<\/label>\r\n        <input id=\"toDate\" class=\"env-input\" type=\"date\">\r\n      <\/div>\r\n\r\n      <button class=\"env-btn env-btn-search\" id=\"btnSearch\" type=\"button\">Search<\/button>\r\n      <button class=\"env-btn env-btn-pdf\" id=\"btnPdf\" type=\"button\">Download PDF<\/button>\r\n    <\/div>\r\n\r\n    <div class=\"env-table-wrap\">\r\n      <div class=\"env-table-scroll\">\r\n        <table id=\"recordsTable\">\r\n          <thead>\r\n            <tr>\r\n              <th>Date<\/th>\r\n              <th>Time<\/th>\r\n              <th>Company Name<\/th>\r\n              <th>No of Workers<\/th>\r\n              <th>Conducted By<\/th>\r\n              <th>Photo<\/th>\r\n            <\/tr>\r\n          <\/thead>\r\n          <tbody id=\"recordsBody\"><\/tbody>\r\n        <\/table>\r\n      <\/div>\r\n    <\/div>\r\n\r\n    <div class=\"env-footer\">\r\n      Copyright \u00a9 2026 | ENV AUTOMATION PTE LTD\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <script>\r\n(function(){\r\n  const LOGIN_PAGE_URL = 'https:\/\/envautomation.com\/?page_id=123';\r\n\r\n  const API_LATEST  = 'https:\/\/cctv.envautomation.com\/dtu_ingest\/wbgt_kbgc3\/sensor1\/latest.json';\r\n  const API_WBGT    = 'https:\/\/cctv.envautomation.com\/dtu_ingest\/wbgt_kbgc3\/sensor1\/wbgtlatest.json';\r\n  const API_DAILY   = 'https:\/\/cctv.envautomation.com\/dtu_ingest\/wbgt_kbgc3\/wbgt1_daily.php?sensor_id=1&date=';\r\n  const API_RECORDS = 'https:\/\/cctv.envautomation.com\/dtu_ingest\/wbgt_kbgc3\/wbgt_form_records.php';\r\n  const PHOTO_BASE64 = 'https:\/\/cctv.envautomation.com\/dtu_ingest\/wbgt_kbgc3\/wbgt_photo_base64.php';\r\n\r\n  const AUTO_REFRESH_MS = 10000;\r\n\r\n  let chartInstance = null;\r\n  let typingTimer = null;\r\n  let allRecords = [];\r\n  let filteredRecords = [];\r\n  let isLoadingLive = false;\r\n  let isLoadingRecords = false;\r\n\r\n  const wbgtValueEl   = document.getElementById('wbgtValue');\r\n  const wbgtCircle    = document.getElementById('wbgtCircle');\r\n  const alertBar      = document.getElementById('envAlertBar');\r\n  const recordsBody   = document.getElementById('recordsBody');\r\n  const adviceHead    = document.getElementById('adviceHead');\r\n  const adviceTyping  = document.getElementById('adviceTyping');\r\n  const envLiveTime   = document.getElementById('envLiveTime');\r\n  const fromDateInput = document.getElementById('fromDate');\r\n  const toDateInput   = document.getElementById('toDate');\r\n  const searchInput   = document.getElementById('searchInput');\r\n  const btnLogout     = document.getElementById('btnLogout');\r\n\r\n  if(btnLogout){\r\n    btnLogout.addEventListener('click', function(){\r\n      try{\r\n        sessionStorage.clear();\r\n        localStorage.removeItem('env_logged_in');\r\n        localStorage.removeItem('env_login_time');\r\n        localStorage.removeItem('wbgt_logged_in');\r\n        localStorage.removeItem('wbgt_login_time');\r\n      }catch(e){}\r\n      window.location.href = LOGIN_PAGE_URL;\r\n    });\r\n  }\r\n\r\n  function todayYMD(){\r\n    const d = new Date();\r\n    return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');\r\n  }\r\n\r\n  function escapeHtml(str){\r\n    return String(str == null ? '' : str)\r\n      .replace(\/&\/g,'&amp;')\r\n      .replace(\/<\/g,'&lt;')\r\n      .replace(\/>\/g,'&gt;')\r\n      .replace(\/\"\/g,'&quot;')\r\n      .replace(\/'\/g,'&#039;');\r\n  }\r\n\r\n  function safeAttr(str){\r\n    return String(str == null ? '' : str)\r\n      .replace(\/&\/g,'&amp;')\r\n      .replace(\/\"\/g,'&quot;')\r\n      .replace(\/<\/g,'&lt;')\r\n      .replace(\/>\/g,'&gt;');\r\n  }\r\n\r\n  async function fetchJson(url){\r\n    const finalUrl = url + (url.includes('?') ? '&' : '?') + '_=' + Date.now();\r\n\r\n    const res = await fetch(finalUrl, {\r\n      method:'GET',\r\n      cache:'no-store',\r\n      mode:'cors',\r\n      credentials:'omit'\r\n    });\r\n\r\n    const text = await res.text();\r\n\r\n    if(!res.ok){\r\n      throw new Error('HTTP ' + res.status + ' from ' + finalUrl + ' :: ' + text.substring(0,120));\r\n    }\r\n\r\n    try{\r\n      return JSON.parse(text);\r\n    }catch(e){\r\n      throw new Error('Invalid JSON from ' + finalUrl + ' :: ' + text.substring(0,120));\r\n    }\r\n  }\r\n\r\n  function loadJsonp(url){\r\n    return new Promise(function(resolve, reject){\r\n      const callbackName = 'wbgtJsonp_' + Date.now() + '_' + Math.floor(Math.random() * 100000);\r\n      let script = null;\r\n      let done = false;\r\n\r\n      function cleanup(){\r\n        try{\r\n          delete window[callbackName];\r\n        }catch(e){\r\n          window[callbackName] = undefined;\r\n        }\r\n\r\n        if(script && script.parentNode){\r\n          script.parentNode.removeChild(script);\r\n        }\r\n      }\r\n\r\n      window[callbackName] = function(json){\r\n        if(done) return;\r\n        done = true;\r\n        cleanup();\r\n        resolve(json);\r\n      };\r\n\r\n      script = document.createElement('script');\r\n\r\n      script.onerror = function(){\r\n        if(done) return;\r\n        done = true;\r\n        cleanup();\r\n        reject(new Error('JSONP load failed: ' + url));\r\n      };\r\n\r\n      script.src =\r\n        url +\r\n        (url.includes('?') ? '&' : '?') +\r\n        'callback=' + encodeURIComponent(callbackName) +\r\n        '&_=' + Date.now();\r\n\r\n      document.head.appendChild(script);\r\n\r\n      setTimeout(function(){\r\n        if(done) return;\r\n        done = true;\r\n        cleanup();\r\n        reject(new Error('JSONP timeout: ' + url));\r\n      }, 15000);\r\n    });\r\n  }\r\n\r\n  function toNumber(v){\r\n    if(v === null || typeof v === 'undefined' || v === '') return null;\r\n    const n = Number(v);\r\n    return isNaN(n) ? null : n;\r\n  }\r\n\r\n  function getLatestValue(json){\r\n    if(!json) return null;\r\n    if(json.data && typeof json.data.wbgt !== 'undefined') return toNumber(json.data.wbgt);\r\n    if(typeof json.wbgt !== 'undefined') return toNumber(json.wbgt);\r\n    if(typeof json.value !== 'undefined') return toNumber(json.value);\r\n    return null;\r\n  }\r\n\r\n  function getLatestTime(json){\r\n    if(!json) return '--:--:--';\r\n    const raw = json.recorded_at || json.time || json.datetime || '';\r\n    if(!raw) return '--:--:--';\r\n    const s = String(raw);\r\n    if(s.includes(' ')) return s.split(' ')[1].substring(0,8);\r\n    return s.substring(0,8);\r\n  }\r\n\r\n  function updateCircleColor(value){\r\n    let mainColor = '#63ae3f';\r\n    let glowColor = 'rgba(99,174,63,.35)';\r\n\r\n    if(value < 31){\r\n      mainColor = '#63ae3f';\r\n      glowColor = 'rgba(99,174,63,.30)';\r\n    } else if(value < 33){\r\n      mainColor = '#f0b61a';\r\n      glowColor = 'rgba(240,182,26,.34)';\r\n    } else {\r\n      mainColor = '#ea1d1d';\r\n      glowColor = 'rgba(234,29,29,.30)';\r\n    }\r\n\r\n    wbgtCircle.style.background =\r\n      'radial-gradient(circle at center,#ffffff 0 58%,transparent 59%),' +\r\n      'conic-gradient(' + mainColor + ' 0deg,' + mainColor + ' 250deg,rgba(255,255,255,.22) 250deg,rgba(255,255,255,.22) 360deg)';\r\n\r\n    const glow = document.querySelector('.env-circle-glow');\r\n    if(glow) glow.style.background = glowColor;\r\n  }\r\n\r\n  function typeHtml(targetEl, text, speed){\r\n    if(typingTimer) clearTimeout(typingTimer);\r\n    let i = 0;\r\n    targetEl.innerHTML = '';\r\n\r\n    function formatText(current){\r\n      const lines = current.split('\\n');\r\n      let html = '';\r\n      let listOpen = false;\r\n\r\n      function closeList(){\r\n        if(listOpen){\r\n          html += '<\/ul>';\r\n          listOpen = false;\r\n        }\r\n      }\r\n\r\n      lines.forEach(function(line){\r\n        const trimmed = line.trim();\r\n\r\n        if(trimmed === 'Activity:' || trimmed === 'Action:' || trimmed === 'Attire:'){\r\n          closeList();\r\n          html += '<b>' + trimmed + '<\/b>';\r\n          return;\r\n        }\r\n\r\n        if(trimmed.startsWith('- ')){\r\n          if(!listOpen){\r\n            html += '<ul>';\r\n            listOpen = true;\r\n          }\r\n\r\n          html += '<li>' + escapeHtml(trimmed.substring(2)) + '<\/li>';\r\n          return;\r\n        }\r\n\r\n        if(trimmed === ''){\r\n          closeList();\r\n        }\r\n      });\r\n\r\n      closeList();\r\n      return html;\r\n    }\r\n\r\n    function step(){\r\n      const current = text.slice(0, i);\r\n      targetEl.innerHTML = formatText(current);\r\n\r\n      if(i < text.length){\r\n        i++;\r\n        typingTimer = setTimeout(step, speed);\r\n      }else{\r\n        targetEl.innerHTML = formatText(text);\r\n      }\r\n    }\r\n\r\n    step();\r\n  }\r\n\r\n  function updateAdvisoryBox(value){\r\n    let typingText = '';\r\n\r\n    if(value < 31){\r\n      adviceHead.innerHTML = 'LOW HEAT STRESS<small>WBGT (\u00b0C) &lt; 31<\/small>';\r\n      adviceHead.style.background = 'linear-gradient(135deg,#63ae3f,#7ac654)';\r\n\r\n      typingText =\r\n`Activity:\r\n- Continue normal activities\r\n\r\nAction:\r\n- Hydrate normally\r\n\r\nAttire:\r\n- Wear normal attire`;\r\n    }else if(value < 33){\r\n      adviceHead.innerHTML = 'MODERATE HEAT STRESS<small>31 \u2264 WBGT (\u00b0C) &lt; 33<\/small>';\r\n      adviceHead.style.background = 'linear-gradient(135deg,#f0b61a,#f6c63d)';\r\n\r\n      typingText =\r\n`Activity:\r\n- Reduce outdoor activities\r\n- Take regular breaks indoors or under shade\r\n\r\nAction:\r\n- Drink more fluids\r\n- Monitor body for signs and symptoms of heat-related illness\r\n\r\nAttire:\r\n- Avoid multiple layers of clothing\r\n- Use an umbrella or wear a hat`;\r\n    }else{\r\n      adviceHead.innerHTML = 'HIGH HEAT STRESS<small>WBGT (\u00b0C) \u2265 33<\/small>';\r\n      adviceHead.style.background = 'linear-gradient(135deg,#ea1d1d,#ff5b46)';\r\n\r\n      typingText =\r\n`Activity:\r\n- Minimise outdoor activities\r\n- Stay under shade where possible\r\n- Take more frequent and longer breaks indoors or under shade\r\n\r\nAction:\r\n- Drink more fluids\r\n- Monitor body for signs and symptoms of heat-related illness\r\n- Cool actively during breaks\r\n\r\nAttire:\r\n- Avoid multiple layers of clothing\r\n- Use an umbrella or wear a hat\r\n- Wear lightweight and light-coloured clothing`;\r\n    }\r\n\r\n    typeHtml(adviceTyping, typingText, 10);\r\n  }\r\n\r\n  function updateAlert(value){\r\n    if(value < 31){\r\n      alertBar.innerHTML = '\u2705 WBGT below 31\u00b0C. Normal monitoring condition.';\r\n      alertBar.style.background = 'linear-gradient(90deg,#e6f4d8,#d4eabf)';\r\n      alertBar.style.borderColor = '#c5dcae';\r\n      alertBar.style.color = '#356726';\r\n    }else if(value < 33){\r\n      alertBar.innerHTML = '\u26a0 WBGT above 31\u00b0C. Contractor should contact water parade and submit the required form.';\r\n      alertBar.style.background = 'linear-gradient(90deg,#fff0c4,#f7df93)';\r\n      alertBar.style.borderColor = '#e7d48a';\r\n      alertBar.style.color = '#795600';\r\n    }else{\r\n      alertBar.innerHTML = '\u26d4 WBGT above 33\u00b0C. High heat stress. Immediate action required.';\r\n      alertBar.style.background = 'linear-gradient(90deg,#ffe0d8,#f6c0ae)';\r\n      alertBar.style.borderColor = '#ecb09d';\r\n      alertBar.style.color = '#8b1e1e';\r\n    }\r\n  }\r\n\r\n  function renderChart(labels, values){\r\n    const ctx = document.getElementById('envWbgtChart');\r\n    const safeLabels = labels.length ? labels : ['Now'];\r\n    const safeValues = values.length ? values : [0];\r\n\r\n    const config = {\r\n      type:'line',\r\n      data:{\r\n        labels:safeLabels,\r\n        datasets:[\r\n          {\r\n            label:'WBGT (\u00b0C)',\r\n            data:safeValues,\r\n            borderColor:'#111111',\r\n            backgroundColor:'#111111',\r\n            pointBackgroundColor:'#111111',\r\n            pointBorderColor:'#111111',\r\n            pointRadius:3.5,\r\n            borderWidth:2.5,\r\n            tension:0,\r\n            fill:false\r\n          },\r\n          {\r\n            label:'30\u00b0C Green Line',\r\n            data:safeLabels.map(function(){return 30;}),\r\n            borderColor:'#63ae3f',\r\n            pointRadius:0,\r\n            borderWidth:2,\r\n            borderDash:[6,4],\r\n            tension:0,\r\n            fill:false\r\n          },\r\n          {\r\n            label:'31\u00b0C Yellow Line',\r\n            data:safeLabels.map(function(){return 31;}),\r\n            borderColor:'#f0b61a',\r\n            pointRadius:0,\r\n            borderWidth:2,\r\n            borderDash:[6,4],\r\n            tension:0,\r\n            fill:false\r\n          },\r\n          {\r\n            label:'33\u00b0C Red Line',\r\n            data:safeLabels.map(function(){return 33;}),\r\n            borderColor:'#ea1d1d',\r\n            pointRadius:0,\r\n            borderWidth:2,\r\n            borderDash:[6,4],\r\n            tension:0,\r\n            fill:false\r\n          }\r\n        ]\r\n      },\r\n      options:{\r\n        responsive:true,\r\n        maintainAspectRatio:false,\r\n        animation:false,\r\n        plugins:{\r\n          legend:{\r\n            display:true,\r\n            position:'top',\r\n            labels:{\r\n              color:'#3b536c',\r\n              font:{family:'Poppins',size:12,weight:'700'},\r\n              padding:16\r\n            }\r\n          }\r\n        },\r\n        scales:{\r\n          x:{\r\n            title:{display:true,text:'Time',color:'#667f96',font:{family:'Poppins',size:12,weight:'700'}},\r\n            ticks:{color:'#7b91a5',font:{family:'Poppins',size:11}},\r\n            grid:{color:'rgba(15,49,112,.06)'}\r\n          },\r\n          y:{\r\n            min:20,\r\n            max:35,\r\n            title:{display:true,text:'WBGT (\u00b0C)',color:'#667f96',font:{family:'Poppins',size:12,weight:'700'}},\r\n            ticks:{stepSize:1,color:'#7b91a5',font:{family:'Poppins',size:11}},\r\n            grid:{color:'rgba(15,49,112,.06)'}\r\n          }\r\n        }\r\n      }\r\n    };\r\n\r\n    if(chartInstance){\r\n      chartInstance.destroy();\r\n    }\r\n\r\n    chartInstance = new Chart(ctx, config);\r\n  }\r\n\r\n  function normaliseDailyReadings(json){\r\n    if(!json) return [];\r\n\r\n    let rows = [];\r\n\r\n    if(Array.isArray(json.readings)){\r\n      rows = json.readings;\r\n    }else if(Array.isArray(json.data)){\r\n      rows = json.data;\r\n    }else if(Array.isArray(json.records)){\r\n      rows = json.records;\r\n    }else if(Array.isArray(json)){\r\n      rows = json;\r\n    }\r\n\r\n    return rows.map(function(r){\r\n      const wbgt = toNumber(r.wbgt || r.value);\r\n      let time = r.time || r.record_time || r.recorded_time || '';\r\n\r\n      if(!time && r.recorded_at){\r\n        const s = String(r.recorded_at);\r\n        time = s.includes(' ') ? s.split(' ')[1] : s;\r\n      }\r\n\r\n      return {\r\n        time:String(time || '').substring(0,8),\r\n        wbgt:wbgt\r\n      };\r\n    }).filter(function(r){\r\n      return r.wbgt !== null;\r\n    });\r\n  }\r\n\r\n  async function loadDailyTrend(){\r\n    try{\r\n      const dateStr = fromDateInput.value || todayYMD();\r\n      const json = await fetchJson(API_DAILY + encodeURIComponent(dateStr));\r\n      const readings = normaliseDailyReadings(json);\r\n\r\n      if(!readings.length) return false;\r\n\r\n      renderChart(\r\n        readings.map(function(r){return String(r.time || '').substring(0,5);}),\r\n        readings.map(function(r){return Number(r.wbgt);})\r\n      );\r\n\r\n      return true;\r\n    }catch(err){\r\n      console.warn('Daily API failed, latest JSON still working:', err);\r\n      return false;\r\n    }\r\n  }\r\n\r\n  async function loadLiveWBGT(){\r\n    if(isLoadingLive) return;\r\n    isLoadingLive = true;\r\n\r\n    try{\r\n      let latestJson = null;\r\n\r\n      try{\r\n        latestJson = await fetchJson(API_LATEST);\r\n      }catch(e1){\r\n        latestJson = await fetchJson(API_WBGT);\r\n      }\r\n\r\n      const currentWBGT = getLatestValue(latestJson);\r\n\r\n      if(currentWBGT === null){\r\n        throw new Error('WBGT value not found in latest JSON');\r\n      }\r\n\r\n      wbgtValueEl.textContent = currentWBGT.toFixed(1);\r\n      envLiveTime.textContent = getLatestTime(latestJson);\r\n\r\n      updateCircleColor(currentWBGT);\r\n      updateAdvisoryBox(currentWBGT);\r\n      updateAlert(currentWBGT);\r\n\r\n      const trendLoaded = await loadDailyTrend();\r\n\r\n      if(!trendLoaded){\r\n        renderChart([getLatestTime(latestJson).substring(0,5)], [currentWBGT]);\r\n      }\r\n\r\n    }catch(err){\r\n      console.error('WBGT load error:', err);\r\n      wbgtValueEl.textContent = '--';\r\n      envLiveTime.textContent = '--:--:--';\r\n      renderChart([], []);\r\n\r\n      adviceHead.innerHTML = 'DATA LOADING ERROR<small>Please check latest.json \/ daily.php<\/small>';\r\n      adviceHead.style.background = 'linear-gradient(135deg,#7f8c99,#99a8b5)';\r\n      adviceTyping.innerHTML = '<b>Status:<\/b><ul><li>WBGT API loading error<\/li><li>Check browser console for exact URL error<\/li><\/ul>';\r\n\r\n      alertBar.innerHTML = 'WBGT data loading error. Please check API URL.';\r\n      alertBar.style.background = 'linear-gradient(90deg,#fff0c4,#f7df93)';\r\n      alertBar.style.borderColor = '#e7d48a';\r\n      alertBar.style.color = '#795600';\r\n    }\r\n\r\n    isLoadingLive = false;\r\n  }\r\n\r\n  function renderTable(rows){\r\n    if(!rows.length){\r\n      recordsBody.innerHTML = '<tr><td colspan=\"6\" class=\"env-empty\">No records found.<\/td><\/tr>';\r\n      return;\r\n    }\r\n\r\n    recordsBody.innerHTML = rows.map(function(r){\r\n      const photoUrl = r.photo_url || r.photo || r.image_url || '';\r\n\r\n      return `\r\n        <tr>\r\n          <td>${escapeHtml(r.record_date || '')}<\/td>\r\n          <td>${escapeHtml(String(r.record_time || '').substring(0,5))}<\/td>\r\n          <td>${escapeHtml(r.company_name || '')}<\/td>\r\n          <td>${escapeHtml(String(r.workers_count || ''))}<\/td>\r\n          <td>${escapeHtml(r.conducted_by || '')}<\/td>\r\n          <td>\r\n            ${photoUrl ? `\r\n              <a class=\"env-thumb-link\" href=\"${safeAttr(photoUrl)}\" target=\"_blank\" rel=\"noopener\">\r\n                <img decoding=\"async\" class=\"env-thumb env-pdf-photo\" src=\"${safeAttr(photoUrl)}\" data-original-photo=\"${safeAttr(photoUrl)}\" alt=\"Photo\" loading=\"lazy\">\r\n              <\/a>\r\n            ` : `<span class=\"env-no-photo\">No Photo<\/span>`}\r\n          <\/td>\r\n        <\/tr>\r\n      `;\r\n    }).join('');\r\n  }\r\n\r\n  function filterRecords(){\r\n    const q = searchInput.value.trim().toLowerCase();\r\n    const from = fromDateInput.value;\r\n    const to = toDateInput.value;\r\n\r\n    filteredRecords = allRecords.filter(function(r){\r\n      const textMatch = !q || [\r\n        r.record_date || '',\r\n        r.record_time || '',\r\n        r.company_name || '',\r\n        r.conducted_by || '',\r\n        String(r.workers_count || ''),\r\n        r.photo_url || ''\r\n      ].join(' ').toLowerCase().includes(q);\r\n\r\n      const fromMatch = !from || (r.record_date || '') >= from;\r\n      const toMatch = !to || (r.record_date || '') <= to;\r\n\r\n      return textMatch && fromMatch && toMatch;\r\n    });\r\n\r\n    renderTable(filteredRecords);\r\n  }\r\n\r\n  async function loadContractorRecords(){\r\n    if(isLoadingRecords) return;\r\n    isLoadingRecords = true;\r\n\r\n    try{\r\n      let recordsUrl = API_RECORDS;\r\n      const qs = [];\r\n\r\n      if(fromDateInput.value){\r\n        qs.push('from=' + encodeURIComponent(fromDateInput.value));\r\n      }\r\n\r\n      if(toDateInput.value){\r\n        qs.push('to=' + encodeURIComponent(toDateInput.value));\r\n      }\r\n\r\n      if(qs.length){\r\n        recordsUrl += '?' + qs.join('&');\r\n      }\r\n\r\n      let json = null;\r\n\r\n      try{\r\n        json = await fetchJson(recordsUrl);\r\n      }catch(fetchErr){\r\n        console.warn('Normal records fetch failed, trying JSONP:', fetchErr);\r\n        json = await loadJsonp(recordsUrl);\r\n      }\r\n\r\n      if(!json || !json.ok || !Array.isArray(json.records)){\r\n        allRecords = [];\r\n        filteredRecords = [];\r\n        recordsBody.innerHTML = '<tr><td colspan=\"6\" class=\"env-empty\">Records API error. Please check wbgt_form_records.php.<\/td><\/tr>';\r\n        isLoadingRecords = false;\r\n        return;\r\n      }\r\n\r\n      allRecords = json.records.map(function(r){\r\n        return {\r\n          id:r.id || '',\r\n          submitted_at:r.submitted_at || '',\r\n          record_date:r.record_date || '',\r\n          record_time:r.record_time || '',\r\n          company_name:r.company_name || '',\r\n          workers_count:r.workers_count || '',\r\n          conducted_by:r.conducted_by || '',\r\n          photo_url:r.photo_url || r.photo || r.image_url || ''\r\n        };\r\n      });\r\n\r\n      filterRecords();\r\n\r\n    }catch(err){\r\n      console.warn('Contractor record load error:', err);\r\n      allRecords = [];\r\n      filteredRecords = [];\r\n      recordsBody.innerHTML =\r\n        '<tr><td colspan=\"6\" class=\"env-empty\">Records loading failed: ' + escapeHtml(err.message) + '<\/td><\/tr>';\r\n    }\r\n\r\n    isLoadingRecords = false;\r\n  }\r\n\r\n  function setTodayDates(){\r\n    const today = todayYMD();\r\n    fromDateInput.value = today;\r\n    toDateInput.value = today;\r\n  }\r\n\r\n  async function refreshDashboard(){\r\n    await loadLiveWBGT();\r\n    await loadContractorRecords();\r\n  }\r\n\r\n  document.getElementById('btnSearch').addEventListener('click', function(){\r\n    loadContractorRecords();\r\n    loadLiveWBGT();\r\n  });\r\n\r\n  searchInput.addEventListener('input', filterRecords);\r\n\r\n  fromDateInput.addEventListener('change', function(){\r\n    loadContractorRecords();\r\n    loadLiveWBGT();\r\n  });\r\n\r\n  toDateInput.addEventListener('change', function(){\r\n    loadContractorRecords();\r\n  });\r\n\r\n  setTodayDates();\r\n  renderTable([]);\r\n  refreshDashboard();\r\n\r\n  setInterval(refreshDashboard, AUTO_REFRESH_MS);\r\n\r\n  async function imageUrlToPdfPhoto(photoUrl){\r\n    if(!photoUrl) return null;\r\n\r\n    const apiUrl =\r\n      PHOTO_BASE64 +\r\n      '?url=' + encodeURIComponent(photoUrl) +\r\n      '&_=' + Date.now();\r\n\r\n    try{\r\n      const res = await fetch(apiUrl, {\r\n        method:'GET',\r\n        cache:'no-store',\r\n        mode:'cors',\r\n        credentials:'omit'\r\n      });\r\n\r\n      const text = await res.text();\r\n\r\n      if(res.ok){\r\n        const json = JSON.parse(text);\r\n\r\n        if(json && json.ok && json.data_url){\r\n          return {\r\n            dataUrl:json.data_url,\r\n            format:json.format || guessPdfImageFormat(json.data_url)\r\n          };\r\n        }\r\n\r\n        console.warn('Photo base64 fetch returned not ok:', json);\r\n      }else{\r\n        console.warn('Photo base64 fetch HTTP error:', res.status, text);\r\n      }\r\n    }catch(err){\r\n      console.warn('Photo base64 fetch failed, trying JSONP:', err);\r\n    }\r\n\r\n    return new Promise(function(resolve){\r\n      const cbName = 'wbgtPhotoBase64_' + Date.now() + '_' + Math.floor(Math.random() * 100000);\r\n      let script = null;\r\n      let done = false;\r\n\r\n      function finish(value){\r\n        if(done) return;\r\n        done = true;\r\n\r\n        try{\r\n          delete window[cbName];\r\n        }catch(e){\r\n          window[cbName] = undefined;\r\n        }\r\n\r\n        if(script && script.parentNode){\r\n          script.parentNode.removeChild(script);\r\n        }\r\n\r\n        resolve(value || null);\r\n      }\r\n\r\n      window[cbName] = function(json){\r\n        if(json && json.ok && json.data_url){\r\n          finish({\r\n            dataUrl:json.data_url,\r\n            format:json.format || guessPdfImageFormat(json.data_url)\r\n          });\r\n        }else{\r\n          console.warn('Photo base64 JSONP not ok:', json);\r\n          finish(null);\r\n        }\r\n      };\r\n\r\n      script = document.createElement('script');\r\n      script.src = apiUrl + '&callback=' + encodeURIComponent(cbName);\r\n\r\n      script.onerror = function(){\r\n        console.warn('Photo base64 JSONP script load failed:', photoUrl);\r\n        finish(null);\r\n      };\r\n\r\n      document.head.appendChild(script);\r\n\r\n      setTimeout(function(){\r\n        console.warn('Photo base64 JSONP timeout:', photoUrl);\r\n        finish(null);\r\n      }, 15000);\r\n    });\r\n  }\r\n\r\n  function guessPdfImageFormat(dataUrl){\r\n    const s = String(dataUrl || '').toLowerCase();\r\n\r\n    if(s.indexOf('data:image\/png') === 0){\r\n      return 'PNG';\r\n    }\r\n\r\n    return 'JPEG';\r\n  }\r\n\r\n  function formatDateTimeNow(){\r\n    const d = new Date();\r\n    const y = d.getFullYear();\r\n    const m = String(d.getMonth() + 1).padStart(2, '0');\r\n    const day = String(d.getDate()).padStart(2, '0');\r\n    const hh = String(d.getHours()).padStart(2, '0');\r\n    const mm = String(d.getMinutes()).padStart(2, '0');\r\n    return y + '-' + m + '-' + day + ' ' + hh + ':' + mm;\r\n  }\r\n\r\n  async function generatePdfDirect(){\r\n    const jsPDF = window.jspdf && window.jspdf.jsPDF;\r\n\r\n    if(!jsPDF){\r\n      alert('PDF library not loaded.');\r\n      return;\r\n    }\r\n\r\n    await loadContractorRecords();\r\n\r\n    await new Promise(function(resolve){\r\n      setTimeout(resolve, 500);\r\n    });\r\n\r\n    const rows = filteredRecords.slice();\r\n\r\n    if(!rows.length){\r\n      alert('No records found for selected date.');\r\n      return;\r\n    }\r\n\r\n    const pdf = new jsPDF('p', 'mm', 'a4');\r\n\r\n    const pageW = pdf.internal.pageSize.getWidth();\r\n    const pageH = pdf.internal.pageSize.getHeight();\r\n\r\n    const margin = 8;\r\n    const tableX = margin;\r\n    let y = 10;\r\n\r\n    const col = {\r\n      date:23,\r\n      time:18,\r\n      company:64,\r\n      workers:24,\r\n      conducted:40,\r\n      photo:40\r\n    };\r\n\r\n    const rowH = 30;\r\n\r\n    function drawHeader(){\r\n      y = 10;\r\n\r\n      pdf.setFillColor(238,243,248);\r\n      pdf.rect(0,0,pageW,pageH,'F');\r\n\r\n      pdf.setTextColor(13,49,112);\r\n      pdf.setFont('helvetica','bold');\r\n      pdf.setFontSize(14);\r\n      pdf.text('ENV AUTOMATION PTE LTD', margin, y);\r\n\r\n      y += 6;\r\n\r\n      pdf.setTextColor(80,100,120);\r\n      pdf.setFont('helvetica','normal');\r\n      pdf.setFontSize(8);\r\n      pdf.text('WBGT Heat Stress Monitoring - KIMLY KBGC3', margin, y);\r\n\r\n      y += 5;\r\n      pdf.text('Report Generated: ' + formatDateTimeNow(), margin, y);\r\n\r\n      y += 5;\r\n      pdf.text('Date Range: ' + (fromDateInput.value || '-') + ' to ' + (toDateInput.value || '-'), margin, y);\r\n\r\n      y += 8;\r\n\r\n      pdf.setFillColor(13,49,112);\r\n      pdf.rect(tableX, y, pageW - margin * 2, 9, 'F');\r\n\r\n      pdf.setTextColor(255,255,255);\r\n      pdf.setFont('helvetica','bold');\r\n      pdf.setFontSize(7);\r\n\r\n      let x = tableX + 2;\r\n\r\n      pdf.text('Date', x, y + 6);\r\n      x += col.date;\r\n\r\n      pdf.text('Time', x, y + 6);\r\n      x += col.time;\r\n\r\n      pdf.text('Company Name', x, y + 6);\r\n      x += col.company;\r\n\r\n      pdf.text('Workers', x, y + 6);\r\n      x += col.workers;\r\n\r\n      pdf.text('Conducted By', x, y + 6);\r\n      x += col.conducted;\r\n\r\n      pdf.text('Photo', x, y + 6);\r\n\r\n      y += 9;\r\n    }\r\n\r\n    function addNewPage(){\r\n      pdf.addPage();\r\n      drawHeader();\r\n    }\r\n\r\n    drawHeader();\r\n\r\n    for(let i = 0; i < rows.length; i++){\r\n      const r = rows[i];\r\n\r\n      if(y + rowH > pageH - 12){\r\n        addNewPage();\r\n      }\r\n\r\n      const bg = i % 2 === 0 ? [238,244,250] : [231,238,246];\r\n\r\n      pdf.setFillColor(bg[0], bg[1], bg[2]);\r\n      pdf.rect(tableX, y, pageW - margin * 2, rowH, 'F');\r\n\r\n      pdf.setDrawColor(219,229,239);\r\n      pdf.rect(tableX, y, pageW - margin * 2, rowH, 'S');\r\n\r\n      pdf.setTextColor(22,50,74);\r\n      pdf.setFont('helvetica','bold');\r\n      pdf.setFontSize(6.2);\r\n\r\n      let x = tableX + 2;\r\n      const textY = y + 9;\r\n\r\n      pdf.text(String(r.record_date || ''), x, textY);\r\n      x += col.date;\r\n\r\n      pdf.text(String(r.record_time || '').substring(0,5), x, textY);\r\n      x += col.time;\r\n\r\n      const companyLines = pdf.splitTextToSize(String(r.company_name || ''), col.company - 3);\r\n      pdf.text(companyLines.slice(0,3), x, textY);\r\n      x += col.company;\r\n\r\n      pdf.text(String(r.workers_count || ''), x, textY);\r\n      x += col.workers;\r\n\r\n      const conductedLines = pdf.splitTextToSize(String(r.conducted_by || ''), col.conducted - 3);\r\n      pdf.text(conductedLines.slice(0,3), x, textY);\r\n      x += col.conducted;\r\n\r\n      const photoUrl = r.photo_url || '';\r\n\r\n      if(photoUrl){\r\n        const photoObj = await imageUrlToPdfPhoto(photoUrl);\r\n\r\n        if(photoObj && photoObj.dataUrl){\r\n          try{\r\n            pdf.addImage(photoObj.dataUrl, photoObj.format || 'JPEG', x, y + 3, 32, 24);\r\n          }catch(e1){\r\n            try{\r\n              pdf.addImage(photoObj.dataUrl, 'JPEG', x, y + 3, 32, 24);\r\n            }catch(e2){\r\n              try{\r\n                pdf.addImage(photoObj.dataUrl, 'PNG', x, y + 3, 32, 24);\r\n              }catch(e3){\r\n                pdf.setFont('helvetica','normal');\r\n                pdf.setTextColor(150,150,150);\r\n                pdf.text('Photo error', x, textY);\r\n              }\r\n            }\r\n          }\r\n        }else{\r\n          pdf.setFont('helvetica','normal');\r\n          pdf.setTextColor(150,150,150);\r\n          pdf.text('Photo failed', x, textY);\r\n        }\r\n      }else{\r\n        pdf.setFont('helvetica','normal');\r\n        pdf.setTextColor(150,150,150);\r\n        pdf.text('No photo URL', x, textY);\r\n      }\r\n\r\n      y += rowH;\r\n    }\r\n\r\n    pdf.save('WBGT_Dashboard_Report.pdf');\r\n  }\r\n\r\n  document.getElementById('btnPdf').addEventListener('click', async function(){\r\n    const btn = this;\r\n    const oldText = btn.textContent;\r\n\r\n    btn.disabled = true;\r\n    btn.textContent = 'Generating PDF...';\r\n\r\n    try{\r\n      await generatePdfDirect();\r\n    }catch(err){\r\n      console.error('PDF generation error:', err);\r\n      alert('Failed to generate PDF: ' + err.message);\r\n    }finally{\r\n      btn.disabled = false;\r\n      btn.textContent = oldText;\r\n    }\r\n  });\r\n\r\n})();\r\n<\/script>\r\n<\/div>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>EA ENV AUTOMATION PTE LTD ENV Automation Pte Ltd \u2022 Secure live dashboard System Online Last Update: &#8211;:&#8211;:&#8211; Logout LOW [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"site-sidebar-layout":"no-sidebar","site-content-layout":"","ast-site-content-layout":"full-width-container","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"disabled","ast-breadcrumbs-content":"","ast-featured-img":"disabled","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"class_list":["post-1437","page","type-page","status-publish","hentry"],"brizy_media":[],"_links":{"self":[{"href":"https:\/\/envautomation.com\/index.php?rest_route=\/wp\/v2\/pages\/1437","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/envautomation.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/envautomation.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/envautomation.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/envautomation.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1437"}],"version-history":[{"count":5,"href":"https:\/\/envautomation.com\/index.php?rest_route=\/wp\/v2\/pages\/1437\/revisions"}],"predecessor-version":[{"id":2185,"href":"https:\/\/envautomation.com\/index.php?rest_route=\/wp\/v2\/pages\/1437\/revisions\/2185"}],"wp:attachment":[{"href":"https:\/\/envautomation.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1437"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}