OuiMCB commited on
Commit
f65495c
·
verified ·
1 Parent(s): 6cfe470

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +56 -24
index.html CHANGED
@@ -29,7 +29,15 @@
29
  h1 {
30
  text-align: center;
31
  color: #2c3e50;
 
 
 
 
 
 
 
32
  margin-bottom: 20px;
 
33
  }
34
 
35
  /* 頂部交集結果區塊 */
@@ -168,7 +176,6 @@
168
  box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
169
  }
170
 
171
- /* 響應式微調 */
172
  @media (max-width: 768px) {
173
  .members-grid {
174
  grid-template-columns: repeat(2, 1fr);
@@ -176,7 +183,7 @@
176
  }
177
  </style>
178
 
179
- <!-- Firebase 9 SDK (Compat 模式,方便單一檔案整合) -->
180
  <script src="https://www.gstatic.com/firebasejs/9.23.0/firebase-app-compat.js"></script>
181
  <script src="https://www.gstatic.com/firebasejs/9.23.0/firebase-database-compat.js"></script>
182
  </head>
@@ -184,12 +191,13 @@
184
 
185
  <div class="container">
186
  <h1>📅 7人空檔時間協調行事曆 (8/14 - 9/2)</h1>
 
187
 
188
  <!-- 共同有空交集區塊 -->
189
  <div class="intersection-card">
190
  <h2>🎉 全員皆有空的日期(交集):</h2>
191
  <div id="intersection-results" class="available-tags">
192
- <span class="no-match">計算中或目前尚無所有人都有空的日期...</span>
193
  </div>
194
  </div>
195
 
@@ -206,12 +214,11 @@
206
  const db = firebase.database();
207
  const scheduleRef = db.ref("schedule_2026");
208
 
209
- // 2. 設定參數
210
  const members = ["桑", "吳", "張", "趙", "謝", "黃", "康"];
211
  const startDate = new Date("2026-08-14");
212
  const endDate = new Date("2026-09-02");
213
 
214
- // 生成日期陣列 YYYY-MM-DD
215
  const dateList = [];
216
  let curr = new Date(startDate);
217
  while (curr <= endDate) {
@@ -227,8 +234,9 @@
227
  curr.setDate(curr.getDate() + 1);
228
  }
229
 
230
- // 3. 渲染 HTML 結構
231
  const calendarContainer = document.getElementById("calendar");
 
232
 
233
  dateList.forEach(({ dateStr, displayStr }) => {
234
  const dateCard = document.createElement("div");
@@ -242,11 +250,11 @@
242
  <div class="member-name-row">
243
  <span>${name}</span>
244
  <label>
245
- <input type="checkbox" id="chk-${dateStr}-${name}" onchange="updateData('${dateStr}', '${name}')">
246
  有空
247
  </label>
248
  </div>
249
- <input type="text" id="txt-${dateStr}-${name}" placeholder="標註時間/備註..." onchange="updateData('${dateStr}', '${name}')" onkeydown="if(event.key==='Enter') this.blur()">
250
  </div>
251
  `;
252
  });
@@ -254,7 +262,7 @@
254
  dateCard.innerHTML = `
255
  <div class="date-header">
256
  <span>${displayStr}</span>
257
- <span class="status-badge" id="badge-${dateStr}">填寫中</span>
258
  </div>
259
  <div class="members-grid">
260
  ${membersHTML}
@@ -264,19 +272,40 @@
264
  calendarContainer.appendChild(dateCard);
265
  });
266
 
267
- // 4. 更新 Firebase 資料
268
- function updateData(dateStr, memberName) {
269
- const isAvailable = document.getElementById(`chk-${dateStr}-${memberName}`).checked;
270
- const textNote = document.getElementById(`txt-${dateStr}-${memberName}`).value;
271
-
272
- scheduleRef.child(dateStr).child(memberName).set({
273
- available: isAvailable,
274
- note: textNote
275
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  }
277
 
278
- // 5. 監聽 Firebase 資料庫變更並更新 UI
279
  scheduleRef.on("value", (snapshot) => {
 
 
 
280
  const data = snapshot.val() || {};
281
  const fullAvailableDates = [];
282
 
@@ -290,7 +319,7 @@
290
  const chkEl = document.getElementById(`chk-${dateStr}-${name}`);
291
  const txtEl = document.getElementById(`txt-${dateStr}-${name}`);
292
 
293
- // 避免覆蓋正在輸入中的 input content
294
  if (document.activeElement !== txtEl) {
295
  txtEl.value = memberData.note || "";
296
  }
@@ -304,10 +333,9 @@
304
  const cardEl = document.getElementById(`card-${dateStr}`);
305
  const badgeEl = document.getElementById(`badge-${dateStr}`);
306
 
307
- // 比對交集:7 個人都勾選有空
308
  if (availableCount === members.length) {
309
  cardEl.classList.add("all-available");
310
- badgeEl.innerText = "全員皆有空!";
311
  fullAvailableDates.push(displayStr);
312
  } else {
313
  cardEl.classList.remove("all-available");
@@ -315,15 +343,19 @@
315
  }
316
  });
317
 
318
- // 更新頂部交集結果
319
  const resultsContainer = document.getElementById("intersection-results");
320
  if (fullAvailableDates.length > 0) {
321
  resultsContainer.innerHTML = fullAvailableDates
322
  .map(d => `<span class="tag">${d}</span>`)
323
  .join("");
324
  } else {
325
- resultsContainer.innerHTML = `<span class="no-match">目前尚無所有人都有空的日期,大家快來勾選!</span>`;
326
  }
 
 
 
 
327
  });
328
  </script>
329
  </body>
 
29
  h1 {
30
  text-align: center;
31
  color: #2c3e50;
32
+ margin-bottom: 10px;
33
+ }
34
+
35
+ .status-indicator {
36
+ text-align: center;
37
+ font-size: 0.9rem;
38
+ color: #666;
39
  margin-bottom: 20px;
40
+ height: 20px;
41
  }
42
 
43
  /* 頂部交集結果區塊 */
 
176
  box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
177
  }
178
 
 
179
  @media (max-width: 768px) {
180
  .members-grid {
181
  grid-template-columns: repeat(2, 1fr);
 
183
  }
184
  </style>
185
 
186
+ <!-- Firebase SDK -->
187
  <script src="https://www.gstatic.com/firebasejs/9.23.0/firebase-app-compat.js"></script>
188
  <script src="https://www.gstatic.com/firebasejs/9.23.0/firebase-database-compat.js"></script>
189
  </head>
 
191
 
192
  <div class="container">
193
  <h1>📅 7人空檔時間協調行事曆 (8/14 - 9/2)</h1>
194
+ <div id="sync-status" class="status-indicator">📡 連線中...</div>
195
 
196
  <!-- 共同有空交集區塊 -->
197
  <div class="intersection-card">
198
  <h2>🎉 全員皆有空的日期(交集):</h2>
199
  <div id="intersection-results" class="available-tags">
200
+ <span class="no-match">讀取中...</span>
201
  </div>
202
  </div>
203
 
 
214
  const db = firebase.database();
215
  const scheduleRef = db.ref("schedule_2026");
216
 
 
217
  const members = ["桑", "吳", "張", "趙", "謝", "黃", "康"];
218
  const startDate = new Date("2026-08-14");
219
  const endDate = new Date("2026-09-02");
220
 
221
+ // 生成日期列表
222
  const dateList = [];
223
  let curr = new Date(startDate);
224
  while (curr <= endDate) {
 
234
  curr.setDate(curr.getDate() + 1);
235
  }
236
 
237
+ // 2. 渲染 DOM
238
  const calendarContainer = document.getElementById("calendar");
239
+ const syncStatus = document.getElementById("sync-status");
240
 
241
  dateList.forEach(({ dateStr, displayStr }) => {
242
  const dateCard = document.createElement("div");
 
250
  <div class="member-name-row">
251
  <span>${name}</span>
252
  <label>
253
+ <input type="checkbox" id="chk-${dateStr}-${name}" onchange="saveData('${dateStr}', '${name}')">
254
  有空
255
  </label>
256
  </div>
257
+ <input type="text" id="txt-${dateStr}-${name}" placeholder="輸入時間..." oninput="saveData('${dateStr}', '${name}')">
258
  </div>
259
  `;
260
  });
 
262
  dateCard.innerHTML = `
263
  <div class="date-header">
264
  <span>${displayStr}</span>
265
+ <span class="status-badge" id="badge-${dateStr}">計算中...</span>
266
  </div>
267
  <div class="members-grid">
268
  ${membersHTML}
 
272
  calendarContainer.appendChild(dateCard);
273
  });
274
 
275
+ // 3. 儲存資料機制(防抖動)
276
+ const debounceTimers = {};
277
+ function saveData(dateStr, memberName) {
278
+ syncStatus.innerText = "⏳ 儲存中...";
279
+ syncStatus.style.color = "#e67e22";
280
+
281
+ const key = `${dateStr}-${memberName}`;
282
+ clearTimeout(debounceTimers[key]);
283
+
284
+ debounceTimers[key] = setTimeout(() => {
285
+ const isAvailable = document.getElementById(`chk-${dateStr}-${memberName}`).checked;
286
+ const textNote = document.getElementById(`txt-${dateStr}-${memberName}`).value;
287
+
288
+ scheduleRef.child(dateStr).child(memberName).set({
289
+ available: isAvailable,
290
+ note: textNote
291
+ }, (error) => {
292
+ if (error) {
293
+ syncStatus.innerText = "❌ 儲存失敗!請檢查 Firebase 規則";
294
+ syncStatus.style.color = "#e74c3c";
295
+ console.error("Firebase 保存失敗:", error);
296
+ } else {
297
+ syncStatus.innerText = "✅ 所有變更已即時同步保存";
298
+ syncStatus.style.color = "#27ae60";
299
+ }
300
+ });
301
+ }, 300);
302
  }
303
 
304
+ // 4. 即時監聽與畫面更新
305
  scheduleRef.on("value", (snapshot) => {
306
+ syncStatus.innerText = "✅ 資料庫已連線,即時同步中";
307
+ syncStatus.style.color = "#27ae60";
308
+
309
  const data = snapshot.val() || {};
310
  const fullAvailableDates = [];
311
 
 
319
  const chkEl = document.getElementById(`chk-${dateStr}-${name}`);
320
  const txtEl = document.getElementById(`txt-${dateStr}-${name}`);
321
 
322
+ // 若使用者當前正在輸入該格,不打斷輸入
323
  if (document.activeElement !== txtEl) {
324
  txtEl.value = memberData.note || "";
325
  }
 
333
  const cardEl = document.getElementById(`card-${dateStr}`);
334
  const badgeEl = document.getElementById(`badge-${dateStr}`);
335
 
 
336
  if (availableCount === members.length) {
337
  cardEl.classList.add("all-available");
338
+ badgeEl.innerText = "🎉 全員皆有空!";
339
  fullAvailableDates.push(displayStr);
340
  } else {
341
  cardEl.classList.remove("all-available");
 
343
  }
344
  });
345
 
346
+ // 更新頂部交集
347
  const resultsContainer = document.getElementById("intersection-results");
348
  if (fullAvailableDates.length > 0) {
349
  resultsContainer.innerHTML = fullAvailableDates
350
  .map(d => `<span class="tag">${d}</span>`)
351
  .join("");
352
  } else {
353
+ resultsContainer.innerHTML = `<span class="no-match">目前尚無所有人都有空的日期</span>`;
354
  }
355
+ }, (error) => {
356
+ syncStatus.innerText = "❌ 無法讀取資料,請至 Firebase 設定 Rules 權限!";
357
+ syncStatus.style.color = "#e74c3c";
358
+ console.error("Firebase 讀取失敗:", error);
359
  });
360
  </script>
361
  </body>