/** * Home flow: * - range mode: start~end, skip TW holidays/weekends * - list mode: exact dates, fill even on holidays * Preview/fill: Teams meetings + GitLab issues → PTS */ function setDefaultDates() { const today = todayStr(); document.getElementById("startDate").value = today; document.getElementById("endDate").value = today; } function getDateMode() { const checked = document.querySelector('input[name="dateMode"]:checked'); return checked ? checked.value : "range"; } function getDateRange() { const start = document.getElementById("startDate")?.value || todayStr(); let end = document.getElementById("endDate")?.value || start; if (end < start) { document.getElementById("endDate").value = start; document.getElementById("startDate").value = end; return { start: end, end: start }; } return { start, end }; } function getDateListText() { return (document.getElementById("dateList")?.value || "").trim(); } function parseDateList(text) { const dates = []; for (const line of text.split(/\n+/)) { const t = line.trim(); if (!t || t.startsWith("#")) continue; for (const part of t.split(/[\s,;]+/)) { const d = part.trim(); if (/^\d{4}-\d{2}-\d{2}$/.test(d)) dates.push(d); } } return [...new Set(dates)].sort(); } function setRangeToToday() { const today = todayStr(); document.getElementById("startDate").value = today; document.getElementById("endDate").value = today; updateRangeMeta(); } function updateDateModeUI() { const mode = getDateMode(); document.getElementById("rangePanel")?.classList.toggle("hidden", mode !== "range"); document.getElementById("listPanel")?.classList.toggle("hidden", mode !== "list"); updateRangeMeta(); } function updateRangeMeta() { const el = document.getElementById("rangeMeta"); if (!el) return; if (getDateMode() === "list") { const dates = parseDateList(getDateListText()); el.textContent = dates.length ? `指定 ${dates.length} 天(含假日也填):${dates.join("、")}` : "請輸入至少一個日期"; return; } const { start, end } = getDateRange(); el.textContent = `區間 ${start} → ${end}(自動跳過週末/台灣假日)`; } function setActionStatus(text, ok) { const el = document.getElementById("actionStatus"); if (!el) return; el.textContent = text || ""; el.className = "settings-status" + (ok === true ? " ok" : ok === false ? " err" : ""); } function escapeHtml(text) { return String(text) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } async function refreshConn() { const line = document.getElementById("connLine"); try { const data = await api("/api/status"); const teams = data.teams_calendar || {}; const parts = [ data.has_pts_session ? "工時 ✓" : "工時 ✗", data.gitlab_configured && !data.gitlab_error ? "任務 ✓" : "任務 ✗", data.teams_calendar_enabled ? (teams.connected ? "行事曆 ✓" : "行事曆 ✗") : "行事曆 關", ]; if (line) { line.textContent = parts.join(" · "); if (data.gitlab_error) line.textContent += " — " + data.gitlab_error; } } catch (err) { if (line) line.textContent = err.message; } } function renderPlan(data, { filled } = {}) { const root = document.getElementById("planView"); if (!root) return; const workdays = data.workdays || []; const plan = data.plan || {}; const meetings = data.analysis?.meetings || data.meetings || {}; const source = data.source || ""; const gitlabN = data.gitlab_task_count ?? "—"; const meetingNote = meetings.enabled === false ? "會議未啟用" : meetings.error ? `會議:${meetings.error}` : meetings.meeting_count != null ? `會議 ${meetings.meeting_count} 場 · ${meetings.meeting_hours_total ?? 0}h` : ""; if (!workdays.length && !Object.keys(plan).length) { root.innerHTML = `
沒有可填日期。區間模式會跳過假日;指定多日請確認日期格式。
`; return; } const days = workdays.length ? workdays : Object.keys(plan).sort(); const dayBlocks = days .map((day) => { const entries = plan[day] || []; if (!entries.length) { return `會議 + GitLab 分配中…
`; try { let data; if (mode === "list") { const dates = parseDateList(getDateListText()); if (!dates.length) throw new Error("請至少輸入一個日期(YYYY-MM-DD)"); data = await api("/api/preview/dates", { method: "POST", body: JSON.stringify({ dates_text: dates.join("\n") }), }); document.getElementById("rangeMeta").textContent = `指定 ${dates.length} 天(含假日):${dates.join("、")}`; } else { const { start, end } = getDateRange(); data = await api( `/api/preview?start_date=${encodeURIComponent(start)}&end_date=${encodeURIComponent(end)}` ); document.getElementById("rangeMeta").textContent = `區間 ${start} → ${end} · 工作日 ${(data.workdays || []).length} 天(已跳過假日)`; } renderPlan(data, { filled: false }); setActionStatus("預覽完成(尚未送出)", true); } catch (err) { document.getElementById("planView").innerHTML = `${escapeHtml(err.message)}
`; setActionStatus(err.message, false); } } async function runFill() { const mode = getDateMode(); const dryRun = document.getElementById("dryRun").checked; setActionStatus(`${dryRun ? "試跑" : "填寫"}中…`); document.getElementById("planView").innerHTML = `處理中…
`; try { let data; if (mode === "list") { const dates = parseDateList(getDateListText()); if (!dates.length) throw new Error("請至少輸入一個日期(YYYY-MM-DD)"); data = await api("/api/fill/dates", { method: "POST", body: JSON.stringify({ dates_text: dates.join("\n"), dry_run: dryRun, skip_existing: true, }), }); document.getElementById("rangeMeta").textContent = `指定 ${dates.length} 天` + (dryRun ? " · 試跑" : " · 已送出"); } else { const { start, end } = getDateRange(); data = await api("/api/fill/range", { method: "POST", body: JSON.stringify({ start_date: start, end_date: end, dry_run: dryRun, skip_existing: true, }), }); document.getElementById("rangeMeta").textContent = `區間 ${start} → ${end}` + (dryRun ? " · 試跑" : " · 已送出"); } if (data.plan || data.workdays) { renderPlan({ ...data, dry_run: dryRun }, { filled: !dryRun }); } else { document.getElementById("planView").innerHTML = `${escapeHtml(JSON.stringify(data, null, 2))}
`;
}
setActionStatus(dryRun ? "試跑完成" : "填寫完成", true);
await refreshConn();
} catch (err) {
document.getElementById("planView").innerHTML =
`${escapeHtml(err.message)}
`; setActionStatus(err.message, false); } } document.querySelectorAll('input[name="dateMode"]').forEach((el) => { el.addEventListener("change", updateDateModeUI); }); document.getElementById("setTodayBtn")?.addEventListener("click", setRangeToToday); document.getElementById("previewBtn")?.addEventListener("click", () => runPreview().catch(console.error)); document.getElementById("fillBtn")?.addEventListener("click", () => runFill().catch(console.error)); document.getElementById("startDate")?.addEventListener("change", updateRangeMeta); document.getElementById("endDate")?.addEventListener("change", updateRangeMeta); document.getElementById("dateList")?.addEventListener("input", updateRangeMeta); setDefaultDates(); updateDateModeUI(); refreshConn().catch(console.error);