const $ = (selector, root = document) => root.querySelector(selector); const $$ = (selector, root = document) => Array.from(root.querySelectorAll(selector)); const fallbackImage = "https://www.hoorayhomecare.com.tw/upload/banner/20230523171345t0p5h.jpg"; function escapeHTML(value = "") { return String(value) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function setText(selector, value) { const node = $(selector); if (node) node.textContent = value || ""; } function formatDate(value) { if (!value) return ""; const date = new Date(`${value}T00:00:00`); if (Number.isNaN(date.getTime())) return value; return new Intl.DateTimeFormat("zh-TW", { year: "numeric", month: "2-digit", day: "2-digit", }).format(date); } function renderSettings(settings) { setText("[data-brand-name]", settings.brand_name); setText("[data-company-name]", settings.company_name); setText("[data-hero-note]", settings.hero_note); setText("[data-hero-title]", settings.hero_title); setText("[data-hero-subtitle]", settings.hero_subtitle); setText("[data-about-title]", settings.about_title); setText("[data-about-text]", settings.about_text); setText("[data-service-intro]", settings.service_intro); setText("[data-service-area]", settings.service_area); setText("[data-office-hours]", settings.office_hours); setText("[data-recruit-note]", settings.recruit_note); setText("[data-footer-brand]", settings.brand_name); setText("[data-footer-company]", settings.company_name); setText("[data-address]", settings.address); setText("[data-phone]", settings.phone); setText("[data-email]", settings.email); setText("[data-license]", `本機構核准設立字號:${settings.license_number || ""}`); const hero = $("[data-hero]"); if (hero && settings.hero_image) { hero.style.setProperty("--hero-image", `url("${settings.hero_image}")`); } const phoneLink = $("[data-phone-link]"); if (phoneLink && settings.phone) { phoneLink.href = `tel:${settings.phone.replace(/[^\d+]/g, "")}`; } const emailLink = $("[data-email-link]"); if (emailLink && settings.email) { emailLink.href = `mailto:${settings.email}`; } const lineLink = $("[data-line-link]"); if (lineLink && settings.line_url) lineLink.href = settings.line_url; const facebookLink = $("[data-facebook-link]"); if (facebookLink && settings.facebook_url) facebookLink.href = settings.facebook_url; if (settings.brand_name) { document.title = `${settings.brand_name}|居家照護服務`; } } function renderNews(news) { const root = $("[data-news]"); if (!root) return; root.innerHTML = news .slice(0, 6) .map((item) => { const image = item.image_url || fallbackImage; return `
${escapeHTML(item.title)}
${escapeHTML(formatDate(item.date))}${escapeHTML(item.category)}

${escapeHTML(item.title)}

${escapeHTML(item.excerpt)}

`; }) .join(""); } function renderServices(services) { const root = $("[data-services]"); if (!root) return; root.innerHTML = services .map((item) => `
${escapeHTML(item.icon || "H")} ${escapeHTML(item.group_name)}

${escapeHTML(item.title)}

${escapeHTML(item.description)}

${escapeHTML(item.detail)}

`) .join(""); } function renderCourses(courses) { const root = $("[data-courses]"); if (!root) return; root.innerHTML = courses .map((item) => { const date = item.start_date ? new Date(`${item.start_date}T00:00:00`) : null; const month = date && !Number.isNaN(date.getTime()) ? date.getMonth() + 1 : "--"; const day = date && !Number.isNaN(date.getTime()) ? date.getDate() : "--"; return `
${escapeHTML(String(month))}月 ${escapeHTML(String(day))}
${escapeHTML(item.category)}${escapeHTML(item.status)}

${escapeHTML(item.title)}

${escapeHTML(item.summary)}

`; }) .join(""); } function renderAlbums(albums) { const root = $("[data-albums]"); if (!root) return; root.innerHTML = albums .slice(0, 5) .map((item) => `
${escapeHTML(item.title)}
${escapeHTML(item.year)}${escapeHTML(formatDate(item.date))}

${escapeHTML(item.title)}

${escapeHTML(item.description)}

`) .join(""); } async function loadSite() { const response = await fetch("/api/public/site"); if (!response.ok) throw new Error("site api failed"); const data = await response.json(); renderSettings(data.settings || {}); renderNews(data.news || []); renderServices(data.services || []); renderCourses(data.courses || []); renderAlbums(data.albums || []); } function setupNavigation() { const toggle = $("[data-nav-toggle]"); const nav = $("[data-site-nav]"); if (!toggle || !nav) return; toggle.addEventListener("click", () => { const open = nav.classList.toggle("open"); toggle.setAttribute("aria-expanded", String(open)); }); nav.addEventListener("click", (event) => { if (event.target.closest("a")) { nav.classList.remove("open"); toggle.setAttribute("aria-expanded", "false"); } }); } function setupMessageForm() { const form = $("[data-message-form]"); const status = $("[data-form-status]"); if (!form || !status) return; form.addEventListener("submit", async (event) => { event.preventDefault(); status.classList.remove("error"); status.textContent = "送出中..."; const payload = Object.fromEntries(new FormData(form).entries()); try { const response = await fetch("/api/messages", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const result = await response.json(); if (!response.ok) throw new Error(result.error || "留言送出失敗"); form.reset(); status.textContent = "已收到留言,服務人員會依聯絡方式回覆。"; } catch (error) { status.classList.add("error"); status.textContent = error.message; } }); } function setupAppointmentForm() { const form = $("[data-appointment-form]"); const status = $("[data-appointment-status]"); if (!form || !status) return; form.addEventListener("submit", async (event) => { event.preventDefault(); status.classList.remove("error"); status.textContent = "送出中..."; const payload = Object.fromEntries(new FormData(form).entries()); try { const response = await fetch("/api/appointments", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const result = await response.json(); if (!response.ok) throw new Error(result.error || "預約送出失敗"); form.reset(); status.textContent = "已收到預約諮詢,服務人員會依希望時段聯絡。"; } catch (error) { status.classList.add("error"); status.textContent = error.message; } }); } document.addEventListener("DOMContentLoaded", () => { setupNavigation(); setupMessageForm(); setupAppointmentForm(); loadSite().catch(() => { const status = $("[data-form-status]"); if (status) { status.classList.add("error"); status.textContent = "目前無法載入網站資料。"; } }); });