{"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-04-24T00:33:28","modified_gmt":"2026-04-24T00:33:28","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\/html2canvas@1.4.1\/dist\/html2canvas.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    }\r\n\r\n    #env-wbgt-dashboard-premium *{box-sizing:border-box}\r\n    .env-wrap{max-width:1200px;margin:0 auto}\r\n\r\n    .env-topbar{\r\n      display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:12px;\r\n    }\r\n    .env-brand{display:flex;align-items:center;gap:12px}\r\n    .env-logo{\r\n      width:42px;height:42px;border-radius:12px;\r\n      background:linear-gradient(135deg,var(--blue),var(--blue2));\r\n      color:#fff;font-weight:900;display:grid;place-items:center;\r\n      box-shadow:var(--shadow);font-size:18px;\r\n    }\r\n    .env-brand h1{margin:0;font-size:18px;line-height:1.1;font-weight:900;color:var(--text)}\r\n    .env-brand p{margin:3px 0 0;font-size:11px;color:var(--muted);font-weight:600}\r\n\r\n    .env-pills{display:flex;gap:10px;flex-wrap:wrap}\r\n    .env-pill{\r\n      display:flex;align-items:center;gap:8px;padding:9px 13px;border-radius:999px;background:#fff;\r\n      border:1px solid var(--line);box-shadow:var(--shadow);font-size:11px;font-weight:700;color:#425a73;\r\n    }\r\n    .env-dot{\r\n      width:8px;height:8px;border-radius:50%;background:#27b24f;\r\n      box-shadow:0 0 0 4px rgba(39,178,79,.12);\r\n    }\r\n\r\n    .env-status-row{\r\n      display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:12px;\r\n    }\r\n    .env-status-box{\r\n      color:#fff;padding:11px 14px;border-radius:14px 14px 0 0;font-size:13px;font-weight:900;line-height:1.3;\r\n      box-shadow:var(--shadow);\r\n    }\r\n    .env-status-box small{display:block;font-size:12px;font-weight:800}\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);border:1px solid var(--line);border-radius:22px;box-shadow:var(--shadow);\r\n      padding:18px;margin-bottom:14px;\r\n    }\r\n    .env-main-grid{\r\n      display:grid;grid-template-columns:1.08fr .50fr;gap:18px;align-items:start;\r\n    }\r\n\r\n    .env-title{margin:0 0 2px;font-size:20px;font-weight:900;color:var(--text)}\r\n    .env-sub{margin:0 0 12px;font-size:12px;color:var(--muted);font-weight:600}\r\n    .env-chart-box{height:300px}\r\n\r\n    .env-side-panel{\r\n      display:flex;flex-direction:column;align-items:center;gap:14px;\r\n    }\r\n    .env-circle-card{\r\n      position:relative;width:100%;display:flex;align-items:center;justify-content:center;padding:8px 0 2px;\r\n    }\r\n    .env-circle-link{\r\n      display:inline-flex;align-items:center;justify-content:center;text-decoration:none;cursor:pointer;\r\n    }\r\n    .env-circle-glow{\r\n      position:absolute;width:150px;height:150px;border-radius:50%;filter:blur(22px);opacity:.18;z-index:0;\r\n      animation:envGlowPulse 3.2s ease-in-out infinite;pointer-events:none;\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;height:132px;border-radius:50%;position:relative;display:grid;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      transition:.35s ease;animation:envFloatCircle 3.8s ease-in-out infinite;overflow:hidden;\r\n    }\r\n    .premium-circle::after{\r\n      content:\"\";position:absolute;inset:11px;border-radius:50%;\r\n      background:linear-gradient(180deg,#ffffff 0%, #f8fbff 100%);z-index:2;\r\n      box-shadow:inset 0 0 18px rgba(10,35,60,.06);\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{position:relative;z-index:3;text-align:center;width:78px}\r\n    .env-circle-value{\r\n      font-size:31px;line-height:1;font-weight:900;letter-spacing:-.6px;color:#17324a;\r\n    }\r\n    .env-circle-unit{\r\n      margin-top:6px;font-size:10px;font-weight:800;letter-spacing:1.8px;color:#7d90a3;text-transform:uppercase;\r\n    }\r\n\r\n    .premium-advice{\r\n      width:100%;border-radius:18px;overflow:hidden;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);background:#fff;\r\n    }\r\n    .env-advice-head{\r\n      color:#fff;font-size:12px;font-weight:900;line-height:1.28;padding:11px 12px 10px;letter-spacing:.2px;\r\n    }\r\n    .env-advice-head small{display:block;font-size:10px;font-weight:800;margin-top:3px;opacity:.98}\r\n    .env-advice-body{\r\n      font-size:10.5px;line-height:1.52;color:#334e67;font-weight:600;padding:11px 12px 10px;min-height:170px;\r\n    }\r\n    .env-advice-typing ul{margin:0 0 8px 14px;padding:0}\r\n    .env-advice-typing b{display:block;font-size:10.5px;margin:0 0 3px;color:#244058;font-weight:900}\r\n\r\n    .env-alert-bar{\r\n      margin-top:10px;padding:12px 14px;border-radius:14px;font-size:12px;font-weight:800;\r\n      border:1px solid #e7d48a;background:linear-gradient(90deg,#fff0c4,#f7df93);color:#795600;\r\n    }\r\n\r\n    .env-controls{\r\n      display:grid;grid-template-columns:1.3fr 1fr 1fr auto auto;gap:8px;align-items:end;margin-bottom:10px;\r\n    }\r\n    .env-field{display:flex;flex-direction:column;gap:6px;min-width:0}\r\n    .env-label{font-size:12px;font-weight:800;color:#334f68;line-height:1.2;min-height:16px;white-space:nowrap}\r\n    .env-input{\r\n      height:42px;padding:0 12px;border:1px solid #ccd7e2;border-radius:9px;background:#fff;font-size:13px;\r\n      color:var(--text);font-family:\"Poppins\",Arial,sans-serif;outline:none;width:100%;\r\n    }\r\n\r\n    .env-btn{\r\n      height:42px;padding:0 16px;border-radius:9px;border:0;cursor:pointer;font-size:13px;font-weight:900;\r\n      font-family:\"Poppins\",Arial,sans-serif;white-space:nowrap;align-self:end;\r\n    }\r\n    .env-btn-search{background:linear-gradient(135deg,var(--blue),var(--blue2));color:#fff}\r\n    .env-btn-pdf{background:#fff;color:#236fd6;border:2px solid #2f80ed}\r\n\r\n    .env-table-wrap{\r\n      background:#fff;border:1px solid var(--line);border-radius:16px;overflow:hidden;box-shadow:var(--shadow);\r\n    }\r\n    .env-table-scroll{overflow:auto}\r\n    table{width:100%;border-collapse:collapse;min-width:900px}\r\n    thead th{\r\n      background:#0d3170;color:#fff;font-size:12px;font-weight:800;text-align:left;padding:11px 10px;\r\n      border-right:2px solid rgba(255,255,255,.12);white-space:nowrap;\r\n    }\r\n    tbody td{\r\n      font-size:12px;font-weight:600;color:#29445d;padding:12px 10px;border-right:1px solid #dbe5ef;\r\n      border-bottom:1px solid #dbe5ef;background:#eef4fa;vertical-align:middle;\r\n    }\r\n    tbody tr:nth-child(even) td{background:#e7eef6}\r\n    .env-empty{\r\n      padding:20px;text-align:center;color:var(--muted);font-size:13px;font-weight:700;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    .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    .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{text-align:center;margin-top:12px;font-size:12px;color:#8094a7;font-weight:700}\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:1fr 1fr}\r\n      .env-controls .env-field:first-child{grid-column:1 \/ -1}\r\n    }\r\n\r\n    @media (max-width:640px){\r\n      #env-wbgt-dashboard-premium{padding:12px}\r\n      .env-controls{grid-template-columns:1fr}\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-thumb,\r\n      .env-no-photo{\r\n        width:72px;\r\n        height:52px;\r\n      }\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      <\/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\">WBGT HEAT STRESS MONITORING<\/h2>\r\n          <p class=\"env-sub\">Daily monitoring trend with threshold lines<\/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 API_DAILY   = 'https:\/\/api.envautomation.com\/dtu_ingest\/ass_kimlykbgc3\/wbgt1\/wbgt1_daily.php?sensor_id=1&date=';\r\n  const API_RECORDS = 'https:\/\/api.envautomation.com\/dtu_ingest\/ass_kimlykbgc3\/wbgt1\/wbgt_form_records.php';\r\n  const PHOTO_PROXY = 'https:\/\/api.envautomation.com\/dtu_ingest\/ass_kimlykbgc3\/wbgt1\/wbgt_photo_proxy.php?url=';\r\n\r\n  let chartInstance = null;\r\n  let typingTimer = null;\r\n  let allRecords = [];\r\n  let filteredRecords = [];\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\r\n  function todayYMD(){\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    return `${y}-${m}-${day}`;\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  function getApiFolder(url){\r\n    try{\r\n      const u = new URL(url);\r\n      const path = u.pathname.substring(0, u.pathname.lastIndexOf('\/') + 1);\r\n      return `${u.origin}${path}`;\r\n    }catch(e){\r\n      return '';\r\n    }\r\n  }\r\n\r\n  const API_FOLDER = getApiFolder(API_RECORDS);\r\n\r\n  function findPhotoValue(row){\r\n    if(!row || typeof row !== 'object') return '';\r\n\r\n    const priorityKeys = [\r\n      'photo_url','photo','image','image_url','photo_path','image_path',\r\n      'photo_name','image_name','photo_file','image_file','upload_photo'\r\n    ];\r\n\r\n    for(const key of priorityKeys){\r\n      if(row[key]) return String(row[key]).trim();\r\n    }\r\n\r\n    for(const key in row){\r\n      const lower = key.toLowerCase();\r\n      if((lower.includes('photo') || lower.includes('image') || lower.includes('img')) && row[key]){\r\n        return String(row[key]).trim();\r\n      }\r\n    }\r\n\r\n    return '';\r\n  }\r\n\r\n  function buildRealPhotoUrl(raw){\r\n    if(!raw) return '';\r\n\r\n    let value = String(raw).trim();\r\n    if(!value) return '';\r\n\r\n    value = value.replace(\/\\\\\/g, '\/');\r\n\r\n    try{\r\n      if(\/^https?:\\\/\\\/\/i.test(value)) return value;\r\n      if(value.startsWith('\/\/')) return window.location.protocol + value;\r\n      if(value.startsWith('\/')) return new URL(value, window.location.origin).href;\r\n      return new URL(value, API_FOLDER).href;\r\n    }catch(e){\r\n      console.warn('buildRealPhotoUrl error:', raw, e);\r\n      return '';\r\n    }\r\n  }\r\n\r\n  function buildProxyPhotoUrl(realUrl){\r\n    if(!realUrl) return '';\r\n    return PHOTO_PROXY + encodeURIComponent(realUrl);\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(\r\n        ${mainColor} 0deg,\r\n        ${mainColor} 250deg,\r\n        rgba(255,255,255,.22) 250deg,\r\n        rgba(255,255,255,.22) 360deg\r\n      )\r\n    `;\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((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          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      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    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      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      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      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 with thin absorbent material`;\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 and contractor submission is expected.';\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 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((r, index) => {\r\n      const rawPhoto = r._photo_raw || '';\r\n      const realPhotoUrl = r._photo_real || '';\r\n      const photoUrl = r._photo_proxy || '';\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(realPhotoUrl)}\" target=\"_blank\" rel=\"noopener\" title=\"${safeAttr(rawPhoto)}\">\r\n                <img decoding=\"async\"\r\n                  class=\"env-thumb\"\r\n                  src=\"${safeAttr(photoUrl)}\"\r\n                  alt=\"Photo ${index + 1}\"\r\n                  loading=\"eager\"\r\n                  data-photo-url=\"${safeAttr(photoUrl)}\"\r\n                  onerror=\"\r\n                    console.log('Proxy image failed:', this.src);\r\n                    this.style.display='none';\r\n                    if(this.parentNode && !this.parentNode.querySelector('.env-no-photo')){\r\n                      this.parentNode.insertAdjacentHTML('beforeend','<span class=&quot;env-no-photo&quot;>No Photo<\/span>');\r\n                    }\r\n                  \"\r\n                >\r\n              <\/a>\r\n            ` : `<span class=\"env-no-photo\" title=\"${safeAttr(rawPhoto)}\">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(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      ].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  function buildTrendFromReadings(readings){\r\n    const labels = [];\r\n    const values = [];\r\n\r\n    readings.forEach(r => {\r\n      if (r && r.time && typeof r.wbgt !== 'undefined' && r.wbgt !== null) {\r\n        labels.push(String(r.time).substring(0,5));\r\n        values.push(Number(r.wbgt));\r\n      }\r\n    });\r\n\r\n    return {labels, values};\r\n  }\r\n\r\n  function renderChart(labels, values){\r\n    const ctx = document.getElementById('envWbgtChart');\r\n\r\n    const safeLabels = labels.length ? labels : ['00:00'];\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(() => 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(() => 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(() => 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          tooltip:{\r\n            backgroundColor:'rgba(20,42,66,.95)',\r\n            titleColor:'#fff',\r\n            bodyColor:'#eef6ff',\r\n            padding:10,\r\n            borderColor:'rgba(255,255,255,.10)',\r\n            borderWidth:1\r\n          }\r\n        },\r\n        scales:{\r\n          x:{\r\n            title:{\r\n              display:true,\r\n              text:'Time',\r\n              color:'#667f96',\r\n              font:{family:'Poppins',size:12,weight:'700'}\r\n            },\r\n            ticks:{\r\n              color:'#7b91a5',\r\n              font:{family:'Poppins',size:11}\r\n            },\r\n            grid:{color:'rgba(15,49,112,.06)'}\r\n          },\r\n          y:{\r\n            min:26,\r\n            max:35,\r\n            title:{\r\n              display:true,\r\n              text:'WBGT (\u00b0C)',\r\n              color:'#667f96',\r\n              font:{family:'Poppins',size:12,weight:'700'}\r\n            },\r\n            ticks:{\r\n              stepSize:1,\r\n              color:'#7b91a5',\r\n              font:{family:'Poppins',size:11}\r\n            },\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    chartInstance = new Chart(ctx, config);\r\n  }\r\n\r\n  async function loadLiveWBGT(){\r\n    try{\r\n      const dateStr = fromDateInput.value || todayYMD();\r\n      const res = await fetch(API_DAILY + encodeURIComponent(dateStr), {cache:'no-store'});\r\n      const json = await res.json();\r\n\r\n      if(!json || !Array.isArray(json.readings) || !json.readings.length){\r\n        wbgtValueEl.textContent = '--';\r\n        envLiveTime.textContent = '--:--:--';\r\n        renderChart([], []);\r\n        adviceHead.innerHTML = 'NO LIVE DATA<small>No reading found for selected date<\/small>';\r\n        adviceHead.style.background = 'linear-gradient(135deg,#7f8c99,#99a8b5)';\r\n        adviceTyping.innerHTML = '<b>Status:<\/b><ul><li>No WBGT data found for selected date<\/li><\/ul>';\r\n        alertBar.innerHTML = 'Waiting for live WBGT data...';\r\n        return;\r\n      }\r\n\r\n      const readings = json.readings.filter(r => r.wbgt !== null && !isNaN(Number(r.wbgt)));\r\n\r\n      if(!readings.length){\r\n        wbgtValueEl.textContent = '--';\r\n        envLiveTime.textContent = '--:--:--';\r\n        renderChart([], []);\r\n        return;\r\n      }\r\n\r\n      const latest = readings[readings.length - 1];\r\n      const currentWBGT = Number(latest.wbgt);\r\n\r\n      wbgtValueEl.textContent = currentWBGT.toFixed(1);\r\n      envLiveTime.textContent = String(latest.time || '--:--:--').substring(0,8);\r\n\r\n      updateCircleColor(currentWBGT);\r\n      updateAdvisoryBox(currentWBGT);\r\n      updateAlert(currentWBGT);\r\n\r\n      const trend = buildTrendFromReadings(readings);\r\n      renderChart(trend.labels, trend.values);\r\n\r\n    }catch(err){\r\n      console.error('WBGT load error:', err);\r\n      wbgtValueEl.textContent = '--';\r\n      envLiveTime.textContent = '--:--:--';\r\n    }\r\n  }\r\n\r\n  async function loadContractorRecords(){\r\n    try{\r\n      const res = await fetch(API_RECORDS + '?_=' + Date.now(), {cache:'no-store'});\r\n      const json = await res.json();\r\n\r\n      console.log('API_RECORDS full response:', json);\r\n\r\n      if(!json || !json.ok || !Array.isArray(json.records)){\r\n        allRecords = [];\r\n        filterRecords();\r\n        return;\r\n      }\r\n\r\n      allRecords = json.records.map((r, i) => {\r\n        const rawPhoto = findPhotoValue(r);\r\n        const realPhoto = buildRealPhotoUrl(rawPhoto);\r\n        const proxyPhoto = buildProxyPhotoUrl(realPhoto);\r\n\r\n        console.log('Row', i + 1, {\r\n          rawRecord: r,\r\n          rawPhoto: rawPhoto,\r\n          realPhoto: realPhoto,\r\n          proxyPhoto: proxyPhoto\r\n        });\r\n\r\n        return {\r\n          ...r,\r\n          _photo_raw: rawPhoto,\r\n          _photo_real: realPhoto,\r\n          _photo_proxy: proxyPhoto\r\n        };\r\n      });\r\n\r\n      filterRecords();\r\n\r\n    }catch(err){\r\n      console.error('Contractor record load error:', err);\r\n      allRecords = [];\r\n      filterRecords();\r\n    }\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 fetchImageAsDataUrl(url){\r\n    try{\r\n      const res = await fetch(url, {cache:'no-store'});\r\n      if(!res.ok) throw new Error('Image fetch failed');\r\n      const blob = await res.blob();\r\n\r\n      return await new Promise((resolve, reject) => {\r\n        const reader = new FileReader();\r\n        reader.onloadend = () => resolve(reader.result);\r\n        reader.onerror = reject;\r\n        reader.readAsDataURL(blob);\r\n      });\r\n    }catch(err){\r\n      console.warn('Image to base64 failed:', url, err);\r\n      return '';\r\n    }\r\n  }\r\n\r\n  async function buildPdfClone(){\r\n    const source = document.getElementById('pdfArea');\r\n    const clone = source.cloneNode(true);\r\n\r\n    clone.id = 'pdfAreaClone';\r\n    clone.style.position = 'fixed';\r\n    clone.style.left = '-99999px';\r\n    clone.style.top = '0';\r\n    clone.style.width = source.offsetWidth + 'px';\r\n    clone.style.background = '#ffffff';\r\n    clone.style.zIndex = '-1';\r\n\r\n    document.body.appendChild(clone);\r\n\r\n    const sourceCanvas = document.getElementById('envWbgtChart');\r\n    const cloneCanvas = clone.querySelector('#envWbgtChart');\r\n\r\n    if(sourceCanvas && cloneCanvas){\r\n      const chartImg = document.createElement('img');\r\n      chartImg.src = sourceCanvas.toDataURL('image\/png');\r\n      chartImg.style.width = '100%';\r\n      chartImg.style.height = sourceCanvas.offsetHeight + 'px';\r\n      chartImg.style.display = 'block';\r\n      cloneCanvas.parentNode.replaceChild(chartImg, cloneCanvas);\r\n    }\r\n\r\n    const cloneThumbs = Array.from(clone.querySelectorAll('img.env-thumb'));\r\n    for(const img of cloneThumbs){\r\n      const proxyUrl = img.getAttribute('data-photo-url') || img.getAttribute('src') || '';\r\n      const dataUrl = await fetchImageAsDataUrl(proxyUrl);\r\n\r\n      if(dataUrl){\r\n        img.src = dataUrl;\r\n        img.removeAttribute('srcset');\r\n      }else{\r\n        img.style.display = 'none';\r\n        const parent = img.parentNode;\r\n        if(parent && !parent.querySelector('.env-no-photo')){\r\n          parent.insertAdjacentHTML('beforeend','<span class=\"env-no-photo\">No Photo<\/span>');\r\n        }\r\n      }\r\n    }\r\n\r\n    await new Promise(resolve => setTimeout(resolve, 500));\r\n    return clone;\r\n  }\r\n\r\n  document.getElementById('btnSearch').addEventListener('click', function(){\r\n    filterRecords();\r\n    loadLiveWBGT();\r\n  });\r\n\r\n  searchInput.addEventListener('input', filterRecords);\r\n  fromDateInput.addEventListener('change', function(){\r\n    filterRecords();\r\n    loadLiveWBGT();\r\n  });\r\n  toDateInput.addEventListener('change', filterRecords);\r\n\r\n  setTodayDates();\r\n  renderTable([]);\r\n  loadLiveWBGT();\r\n  loadContractorRecords();\r\n  setInterval(loadLiveWBGT, 5 * 60 * 1000);\r\n  setInterval(loadContractorRecords, 30 * 1000);\r\n\r\n  document.getElementById('btnPdf').addEventListener('click', async function(){\r\n    const btn = this;\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    const oldText = btn.textContent;\r\n    btn.disabled = true;\r\n    btn.textContent = 'Generating PDF...';\r\n\r\n    let clone = null;\r\n\r\n    try{\r\n      clone = await buildPdfClone();\r\n\r\n      const canvas = await html2canvas(clone, {\r\n        scale: 2,\r\n        backgroundColor: '#ffffff',\r\n        useCORS: true,\r\n        allowTaint: false,\r\n        scrollX: 0,\r\n        scrollY: 0,\r\n        windowWidth: clone.scrollWidth,\r\n        windowHeight: clone.scrollHeight,\r\n        imageTimeout: 20000\r\n      });\r\n\r\n      const imgData = canvas.toDataURL('image\/png');\r\n      const pdf = new jsPDF('p','mm','a4');\r\n      const pageW = pdf.internal.pageSize.getWidth();\r\n      const pageH = pdf.internal.pageSize.getHeight();\r\n      const margin = 5;\r\n      const imgW = pageW - margin * 2;\r\n      const imgH = canvas.height * imgW \/ canvas.width;\r\n\r\n      let heightLeft = imgH;\r\n      let position = margin;\r\n\r\n      pdf.addImage(imgData, 'PNG', margin, position, imgW, imgH);\r\n      heightLeft -= (pageH - margin * 2);\r\n\r\n      while(heightLeft > 0){\r\n        position = heightLeft - imgH + margin;\r\n        pdf.addPage();\r\n        pdf.addImage(imgData, 'PNG', margin, position, imgW, imgH);\r\n        heightLeft -= (pageH - margin * 2);\r\n      }\r\n\r\n      pdf.save('WBGT_Dashboard_Report.pdf');\r\n    }catch(err){\r\n      console.error('PDF generation error:', err);\r\n      alert('Failed to generate PDF.');\r\n    }finally{\r\n      if(clone && clone.parentNode) clone.parentNode.removeChild(clone);\r\n      btn.disabled = false;\r\n      btn.textContent = oldText;\r\n    }\r\n  });\r\n})();\r\n<\/script>\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; LOW HEAT [&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":1689,"href":"https:\/\/envautomation.com\/index.php?rest_route=\/wp\/v2\/pages\/1437\/revisions\/1689"}],"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}]}}