//static/js/frontend.js function setLoggedIn(loggedInBoolean) { let body = document.body; body.dataset.loggedIn = !!loggedInBoolean; } function createExistingVote(voter, vote) { let voteDiv = document.createElement('div'); voteDiv.innerHTML = ""; if (vote === "yes") { voteDiv.classList.add('vote-yes'); } else if (vote === "no") { voteDiv.classList.add('vote-no'); } voteDiv.innerText = voter; return voteDiv; } function createOneDayCard(dayData, currentSession, weather) { let date = new Date(dayData.date + 'T00:00:00'); let card = document.createElement('div'); let temp = weather.main.temp; let icon = weather.weather[0].icon; card.innerHTML = `
${date.toLocaleString("en-CA", { weekday: 'long' })}
${date.toLocaleString("en-CA", { month: 'short', day: 'numeric' })}
${temp}°C
${
            weather.weather[0].description
          }
Can you attend?
` card.classList.add("card") card.dataset.date = dayData.date; if (weather) { // you need to figure this part out yourself } let existingVotesDiv = card.querySelector('.existing-votes'); for (let [voter, vote] of Object.entries(dayData.votes)) { existingVotesDiv.append(createExistingVote(voter, vote)) } return card; } function updateVotableDays(daysWithVotes, currentSession, weatherForecasts) { let daysView = document.querySelector(".days-view"); if (!daysView) { console.error("could not find element to put days into") return; } daysView.innerHTML = ''; for (let date in daysWithVotes) { let votes = daysWithVotes[date]; let weather = weatherForecasts.list.find((w) => w.dt_txt.startsWith(date)); daysView.append(createOneDayCard({ date, votes }, currentSession, weather)) } } class FrontendState { constructor() { this.currentSession = undefined; this.daysWithVotes = []; this.weatherForecasts = {}; } async refreshAllState(updateView = true) { await Promise.all([ this.refreshVotesState(false), this.refreshWeatherState(false), this.refreshSessionState(false), ]) if (updateView) { this.updateView(); } } async refreshVotesState(updateView = true) { let { success, data, error } = await getVotesFromBackend() if (success) { this.daysWithVotes = data; } else { // ha ha I'm being lazy. can you do better? updateErrorMessage(error) } if (updateView) { this.updateView(); } } async refreshWeatherState(updateView = true) { let { success, data, error } = await fetchWeatherForCurrentLocation() if (success) { this.weatherForecasts = data; } else { // ha ha I'm being lazy. can you do better? updateErrorMessage(error) } if (updateView) { this.updateView(); } } async refreshSessionState(updateView = true) { let { success, data, error } = await getSessionFromBackend() if (success) { this.currentSession = data; } else { // 處理錯誤情況 updateErrorMessage('Unable to refresh session state. Please try again later.'); } if (updateView) { this.updateView(); } } async updateView() { // 1. update the whole frontend so that it shows relevant logged-in vs logged-out features setLoggedIn(!!this.currentSession) // 2. optionally update the header so that it shows the username of the person logged in if(this.currentSession) updateHeader(this.currentSession.username) else updateHeader('') // 3. render the days updateVotableDays(this.daysWithVotes, this.currentSession, this.weatherForecasts) } } const fes = new FrontendState(); fes.refreshAllState() async function handleAuthEvent(event) { event.preventDefault(); // console.log(event.currentTarget, event.target) let usernameInput = event.currentTarget.querySelector('#headerusername'); let usernameValue = usernameInput.value; let passwordInput = event.currentTarget.querySelector('#headerpassword'); let passwordValue = passwordInput.value; let button = event.target.closest('button'); if (button) { let authActionName = button?.dataset?.authAction; let authActionFunction = { signup: ajaxSignup, login: ajaxLogin, logout: ajaxLogout, } [authActionName]; if (authActionFunction) { let authResult = await authActionFunction(usernameValue, passwordValue); if (authResult && authResult.success) { await fes.refreshSessionState(); usernameInput.value = passwordInput.value = ''; } else if (authResult) { // 處理登入、註冊或登出失敗的情況 updateErrorMessage(authResult.error); } else { // 處理未知網絡錯誤 updateErrorMessage("unknown network error"); } } } } function updateHeader(username) { const usernameSpan = document.querySelector('.username'); usernameSpan.textContent = username ? username : 'nobody'; } function updateErrorMessage(message) { const errorMessageDiv = document.querySelector('.error-message'); errorMessageDiv.textContent = message; } const authform = document.querySelector('form.authform') authform.addEventListener("click", handleAuthEvent); async function handleVoteEvent(event) { event.preventDefault(); let button = event.target.closest('button.vote'); if (button) { let voteVal = button.dataset.vote; // 從按鈕的 data-vote 屬性中獲取投票選項 let cardDiv = button.closest("div.card"); let date = cardDiv.dataset.date; // 從卡片的 data-date 屬性中獲取日期 if (fes.currentSession && fes.currentSession.username && voteVal && date) { // 假設用戶已登入,並且 fes.currentSession 中包含 username let voteActionResult = await setMyVote(fes.currentSession.username, date, voteVal); if (voteActionResult.success) { await fes.refreshVotesState(); // 重新獲取並顯示最新的投票資訊 } else { // 顯示錯誤訊息 updateErrorMessage(voteActionResult.error); } } } } const daysViewDiv = document.querySelector('section.days-view'); daysViewDiv.addEventListener("click", handleVoteEvent);