thanthamky commited on
Commit
3a5b5d4
·
verified ·
1 Parent(s): 1de2f47

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.js +27 -3
  2. index.html +6 -0
  3. styles.css +91 -3
app.js CHANGED
@@ -12,9 +12,11 @@ const elements = {
12
  totalAreaM2: document.getElementById("totalAreaM2"),
13
  totalAreaRai: document.getElementById("totalAreaRai"),
14
  itemCount: document.getElementById("itemCount"),
 
15
  };
16
 
17
  declareLoadingRow("Loading data...");
 
18
 
19
  let allFeatures = [];
20
  let map;
@@ -62,7 +64,19 @@ function initMap() {
62
  {},
63
  { collapsed: false }
64
  ).addTo(map);
65
-
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
  window.addEventListener("resize", () => map.invalidateSize());
68
 
@@ -74,7 +88,7 @@ function initMap() {
74
  adminLayer = L.geoJSON(null, {
75
  pane: "adminPane",
76
  style: {
77
- color: "#ffaa00ff",
78
  weight: 0.8,
79
  fillOpacity: 0,
80
  },
@@ -92,6 +106,7 @@ function initMap() {
92
 
93
  async function loadData() {
94
  try {
 
95
  const [palmResponse, adminResponse] = await Promise.all([
96
  fetch("palm_pred_all_admin.geojson"),
97
  fetch("admin_bnd.geojson"),
@@ -111,9 +126,11 @@ async function loadData() {
111
 
112
  initializeSelects();
113
  updateDashboard({ fitBounds: true });
 
114
  } catch (error) {
115
  console.error(error);
116
  declareLoadingRow("Failed to load data. Check console for details.");
 
117
  }
118
  }
119
 
@@ -279,7 +296,7 @@ function renderTable(features) {
279
  function palmDefaultStyle() {
280
  return {
281
  color: "#ffffffff",
282
- weight: 0.5,
283
  fillColor: "#649e66ff",
284
  fillOpacity: 0.7,
285
  };
@@ -401,3 +418,10 @@ function formatNumber(value, fractionDigits = 0) {
401
  maximumFractionDigits: fractionDigits,
402
  }).format(Number(value));
403
  }
 
 
 
 
 
 
 
 
12
  totalAreaM2: document.getElementById("totalAreaM2"),
13
  totalAreaRai: document.getElementById("totalAreaRai"),
14
  itemCount: document.getElementById("itemCount"),
15
+ loadingOverlay: document.getElementById("loadingOverlay"),
16
  };
17
 
18
  declareLoadingRow("Loading data...");
19
+ setLoading(true);
20
 
21
  let allFeatures = [];
22
  let map;
 
64
  {},
65
  { collapsed: false }
66
  ).addTo(map);
67
+ if (L.control && typeof L.control.measure === "function") {
68
+ L.control
69
+ .measure({
70
+ position: "topleft",
71
+ primaryLengthUnit: "kilometers",
72
+ secondaryLengthUnit: "meters",
73
+ primaryAreaUnit: "hectares",
74
+ secondaryAreaUnit: "sqmeters",
75
+ })
76
+ .addTo(map);
77
+ } else {
78
+ console.warn("Leaflet measure plugin is unavailable; measure control disabled.");
79
+ }
80
 
81
  window.addEventListener("resize", () => map.invalidateSize());
82
 
 
88
  adminLayer = L.geoJSON(null, {
89
  pane: "adminPane",
90
  style: {
91
+ color: "#ffbf00ff",
92
  weight: 0.8,
93
  fillOpacity: 0,
94
  },
 
106
 
107
  async function loadData() {
108
  try {
109
+ setLoading(true);
110
  const [palmResponse, adminResponse] = await Promise.all([
111
  fetch("palm_pred_all_admin.geojson"),
112
  fetch("admin_bnd.geojson"),
 
126
 
127
  initializeSelects();
128
  updateDashboard({ fitBounds: true });
129
+ setLoading(false);
130
  } catch (error) {
131
  console.error(error);
132
  declareLoadingRow("Failed to load data. Check console for details.");
133
+ setLoading(false);
134
  }
135
  }
136
 
 
296
  function palmDefaultStyle() {
297
  return {
298
  color: "#ffffffff",
299
+ weight: 0.6,
300
  fillColor: "#649e66ff",
301
  fillOpacity: 0.7,
302
  };
 
418
  maximumFractionDigits: fractionDigits,
419
  }).format(Number(value));
420
  }
421
+
422
+ function setLoading(isLoading) {
423
+ document.body.classList.toggle("is-loading", Boolean(isLoading));
424
+ if (elements.loadingOverlay) {
425
+ elements.loadingOverlay.setAttribute("aria-hidden", isLoading ? "false" : "true");
426
+ }
427
+ }
index.html CHANGED
@@ -16,6 +16,12 @@
16
  <link rel="stylesheet" href="styles.css" />
17
  </head>
18
  <body>
 
 
 
 
 
 
19
  <main class="dashboard">
20
  <section class="top-bar">
21
  <div class="filters">
 
16
  <link rel="stylesheet" href="styles.css" />
17
  </head>
18
  <body>
19
+ <div id="loadingOverlay" class="loading-overlay" role="status" aria-live="polite">
20
+ <div class="loading-card">
21
+ <div class="loading-spinner" aria-hidden="true"></div>
22
+ <p>กำลังโหลดข้อมูล...</p>
23
+ </div>
24
+ </div>
25
  <main class="dashboard">
26
  <section class="top-bar">
27
  <div class="filters">
styles.css CHANGED
@@ -16,6 +16,55 @@ span {
16
  color: #000000;
17
  }
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  html,
20
  body {
21
  height: 100%;
@@ -84,10 +133,11 @@ main.dashboard {
84
 
85
  .summary {
86
  justify-content: space-between;
 
87
  }
88
 
89
  .card {
90
- flex: 1;
91
  display: flex;
92
  flex-direction: column;
93
  gap: 0.35rem;
@@ -216,23 +266,61 @@ main.dashboard {
216
  @media (max-width: 1100px) {
217
  .content {
218
  flex-direction: column;
 
219
  }
220
 
221
  #map {
222
- min-height: 400px;
 
 
 
 
 
 
 
 
 
 
 
 
223
  }
224
  }
225
 
226
  @media (max-width: 720px) {
 
 
 
 
 
 
 
 
227
  .filters,
228
  .summary {
229
  flex-direction: column;
 
 
 
 
 
 
 
 
 
230
  }
231
 
232
  .card:not(:last-child) {
233
  border-right: none;
234
  border-bottom: 1px solid var(--border);
235
  padding-right: 0;
236
- padding-bottom: 1rem;
 
 
 
 
 
 
 
 
237
  }
238
  }
 
16
  color: #000000;
17
  }
18
 
19
+ body.is-loading {
20
+ overflow: hidden;
21
+ }
22
+
23
+ .loading-overlay {
24
+ position: fixed;
25
+ inset: 0;
26
+ display: none;
27
+ align-items: center;
28
+ justify-content: center;
29
+ background: rgba(245, 247, 250, 0.92);
30
+ backdrop-filter: blur(2px);
31
+ z-index: 1200;
32
+ transition: opacity 0.2s ease;
33
+ }
34
+
35
+ body.is-loading .loading-overlay {
36
+ display: flex;
37
+ }
38
+
39
+ .loading-card {
40
+ display: flex;
41
+ flex-direction: column;
42
+ align-items: center;
43
+ gap: 1rem;
44
+ padding: 2rem 2.5rem;
45
+ border-radius: 16px;
46
+ background: var(--surface);
47
+ border: 1px solid var(--border);
48
+ box-shadow: 0 16px 36px rgba(15, 23, 42, 0.18);
49
+ color: #000000;
50
+ font-size: 1.05rem;
51
+ }
52
+
53
+ .loading-spinner {
54
+ width: 48px;
55
+ height: 48px;
56
+ border-radius: 50%;
57
+ border: 4px solid rgba(42, 122, 74, 0.2);
58
+ border-top-color: var(--accent);
59
+ animation: spin 1s linear infinite;
60
+ }
61
+
62
+ @keyframes spin {
63
+ to {
64
+ transform: rotate(360deg);
65
+ }
66
+ }
67
+
68
  html,
69
  body {
70
  height: 100%;
 
133
 
134
  .summary {
135
  justify-content: space-between;
136
+ gap: 1rem;
137
  }
138
 
139
  .card {
140
+ flex: 1 1 0;
141
  display: flex;
142
  flex-direction: column;
143
  gap: 0.35rem;
 
266
  @media (max-width: 1100px) {
267
  .content {
268
  flex-direction: column;
269
+ padding-right: 0;
270
  }
271
 
272
  #map {
273
+ min-height: 440px;
274
+ }
275
+ }
276
+
277
+ @media (max-width: 960px) {
278
+ .top-bar {
279
+ flex-direction: column;
280
+ }
281
+
282
+ .filters,
283
+ .summary {
284
+ flex: 1 1 auto;
285
+ width: 100%;
286
  }
287
  }
288
 
289
  @media (max-width: 720px) {
290
+ .dashboard {
291
+ padding: 0.75rem;
292
+ }
293
+
294
+ .top-bar {
295
+ gap: 0.75rem;
296
+ }
297
+
298
  .filters,
299
  .summary {
300
  flex-direction: column;
301
+ gap: 0.75rem;
302
+ }
303
+
304
+ .filters label {
305
+ width: 100%;
306
+ }
307
+
308
+ .card {
309
+ width: 100%;
310
  }
311
 
312
  .card:not(:last-child) {
313
  border-right: none;
314
  border-bottom: 1px solid var(--border);
315
  padding-right: 0;
316
+ padding-bottom: 0.75rem;
317
+ }
318
+
319
+ #map {
320
+ min-height: 360px;
321
+ }
322
+
323
+ .table-wrapper {
324
+ max-height: 45vh;
325
  }
326
  }