Trung tâm tiện ích

${rows}
BẢNG CHI TIẾT KHOẢN VAY
Số tiền vay${amount}Lãi suất năm${escapeExcelHtml(loanRateInput.value)}%Kỳ hạn${escapeExcelHtml(loanTermInput.value)} tháng
Phương thức trả nợ${escapeExcelHtml(methodLabel)}
Thanh toán tháng đầu${Math.round(currentLoanSchedule[0].payment)}Tổng tiền lãi${Math.round(currentLoanSchedule.reduce((sum, item) => sum + item.interest, 0))}Tổng tiền tất toán${Math.round(currentLoanSchedule.reduce((sum, item) => sum + item.payment, 0))}
ThángDư nợ đầu kỳTiền gốcTiền lãiThanh toánDư nợ còn lại
`; const blob = new Blob(["\ufeff", documentHtml], { type: "application/vnd.ms-excel;charset=utf-8" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = `bang-chi-tiet-khoan-vay-${new Date().toISOString().slice(0, 10)}.xls`; document.body.append(link); link.click(); link.remove(); URL.revokeObjectURL(url); }; const calculateLoan = () => { const amount = normalizeLoanAmount(); const annualRate = Number(loanRateInput.value); const term = Number(loanTermInput.value); const error = document.getElementById("loanCalculatorError"); if (!Number.isFinite(amount) || amount <= 0 || !Number.isFinite(annualRate) || annualRate < 0 || !Number.isInteger(term) || term < 1 || term > 600) { error.textContent = "Vui lòng nhập số tiền vay, lãi suất và kỳ hạn hợp lệ (tối đa 600 tháng)."; error.hidden = false; document.getElementById("loanCalculatorResults").hidden = true; return; } error.hidden = true; const monthlyRate = annualRate / 100 / 12; const method = loanMethodInput.value; const schedule = []; let balance = amount; let totalInterest = 0; let totalPayment = 0; const fixedPrincipal = amount / term; const annuityPayment = monthlyRate === 0 ? amount / term : amount * monthlyRate * Math.pow(1 + monthlyRate, term) / (Math.pow(1 + monthlyRate, term) - 1); for (let month = 1; month <= term; month += 1) { const openingBalance = balance; const interest = openingBalance * monthlyRate; const principal = month === term ? openingBalance : Math.min(openingBalance, method === "annuity" ? annuityPayment - interest : fixedPrincipal); const payment = principal + interest; balance = Math.max(0, openingBalance - principal); totalInterest += interest; totalPayment += payment; schedule.push({ month, openingBalance, principal, interest, payment, balance }); } document.getElementById("loanFirstPayment").textContent = formatLoanCurrency(schedule[0].payment); document.getElementById("loanAveragePayment").textContent = formatLoanCurrency(totalPayment / term); document.getElementById("loanTotalInterest").textContent = formatLoanCurrency(totalInterest); document.getElementById("loanTotalPayment").textContent = formatLoanCurrency(totalPayment); document.getElementById("loanScheduleCount").textContent = `${term.toLocaleString("vi-VN")} tháng`; document.getElementById("loanScheduleDescription").textContent = method === "annuity" ? "Khoản thanh toán gần như bằng nhau hằng tháng; tỷ trọng gốc tăng dần." : "Tiền gốc cố định hằng tháng; tiền lãi và tổng thanh toán giảm dần."; const fragment = document.createDocumentFragment(); schedule.forEach((item) => { const row = document.createElement("tr"); [ item.month.toLocaleString("vi-VN"), formatLoanCurrency(item.openingBalance), formatLoanCurrency(item.principal), formatLoanCurrency(item.interest), formatLoanCurrency(item.payment), formatLoanCurrency(item.balance) ].forEach((value, index) => { const cell = document.createElement(index === 0 ? "th" : "td"); if (index === 0) cell.scope = "row"; cell.textContent = value; row.append(cell); }); fragment.append(row); }); const scheduleBody = document.getElementById("loanScheduleBody"); scheduleBody.replaceChildren(fragment); currentLoanSchedule = schedule; document.getElementById("loanCalculatorResults").hidden = false; }; const showLoanCalculatorModal = () => { loanCalculatorModal.hidden = false; setModalOpen(); calculateLoan(); loanAmountInput.focus(); }; const hideLoanCalculatorModal = () => { loanCalculatorModal.hidden = true; setModalOpen(); }; const readCapacityMoney = (input) => Number(input.value.replace(/[^\d]/g, "")); const formatCapacityMoney = (value) => `${Math.round(value).toLocaleString("vi-VN")} đ`; const calculateBorrowingCapacity = () => { const income = readCapacityMoney(document.getElementById("capacityIncome")); const ratio = Number(document.getElementById("capacityRatio").value) / 100; const annualRate = Number(document.getElementById("capacityRate").value) / 100; const months = Math.round(Number(document.getElementById("capacityYears").value) * 12); const monthlyPayment = income * ratio; const monthlyRate = annualRate / 12; const maximum = monthlyRate > 0 ? monthlyPayment * (1 - Math.pow(1 + monthlyRate, -months)) / monthlyRate : monthlyPayment * months; const interest = Math.max(0, monthlyPayment * months - maximum); document.getElementById("capacityMaximum").textContent = formatCapacityMoney(maximum); document.getElementById("capacityMonthly").textContent = formatCapacityMoney(monthlyPayment); document.getElementById("capacityInterest").textContent = formatCapacityMoney(interest); document.getElementById("capacityMonths").textContent = months.toLocaleString("vi-VN"); }; const showBorrowingCapacityModal = () => { borrowingCapacityModal.hidden = false; setModalOpen(); calculateBorrowingCapacity(); document.getElementById("capacityIncome").focus(); }; const hideBorrowingCapacityModal = () => { borrowingCapacityModal.hidden = true; setModalOpen(); }; const getConsultationData = () => Object.fromEntries(new FormData(consultationForm).entries()); const saveConsultationDraft = () => { try { localStorage.setItem(consultationStorageKey, JSON.stringify(getConsultationData())); consultationStatus.textContent = `Đã lưu nháp lúc ${new Date().toLocaleTimeString("vi-VN", { hour: "2-digit", minute: "2-digit" })}`; return true; } catch (error) { consultationStatus.textContent = "Không thể lưu phiếu trên trình duyệt"; return false; } }; const loadConsultationDraft = () => { try { const draft = JSON.parse(localStorage.getItem(consultationStorageKey) || "{}"); Object.entries(draft).forEach(([name, value]) => { const field = consultationForm.elements.namedItem(name); if (field) field.value = value; }); } catch (error) { consultationStatus.textContent = "Không thể đọc phiếu tư vấn đã lưu"; } }; const consultationLabels = { customerName: "Khách hàng", phone: "Số điện thoại", email: "Email", birthYear: "Năm sinh", occupation: "Nghề nghiệp", customerSource: "Nguồn khách", priority: "Mức độ quan tâm", purpose: "Mục đích", propertyType: "Loại BĐS", location: "Dự án/khu vực", bedrooms: "Phòng ngủ", area: "Diện tích", direction: "Hướng", floor: "Tầng/vị trí", requirements: "Tiêu chí bắt buộc", flexibleRequirements: "Tiêu chí linh hoạt", budgetMin: "Ngân sách tối thiểu", budgetMax: "Ngân sách tối đa", equity: "Vốn tự có", loanNeed: "Nhu cầu vay", loanRatio: "Tỷ lệ vay", loanTerm: "Thời hạn vay", monthlyCapacity: "Khả năng trả/tháng", purchaseTimeline: "Dự kiến giao dịch", recommendedProperties: "Sản phẩm đã tư vấn", customerFeedback: "Phản hồi khách hàng", contactMethod: "Liên hệ ưu tiên", followUpAt: "Chăm sóc tiếp theo", consultantName: "Chuyên viên tư vấn", notes: "Kế hoạch tiếp theo" }; const buildConsultationText = () => { const data = getConsultationData(); const lines = ["PHIẾU TƯ VẤN KHÁCH HÀNG BẤT ĐỘNG SẢN", `Ngày lập: ${new Date().toLocaleDateString("vi-VN")}`, ""]; Object.entries(consultationLabels).forEach(([key, label]) => { if (data[key]?.trim()) lines.push(`${label}: ${data[key].trim()}`); }); return lines.join("\n"); }; const showConsultationModal = () => { loadConsultationDraft(); document.getElementById("consultationDate").textContent = new Date().toLocaleDateString("vi-VN"); consultationModal.hidden = false; setModalOpen(); consultationForm.elements.customerName.focus(); }; const hideConsultationModal = () => { saveConsultationDraft(); consultationModal.hidden = true; setModalOpen(); }; const fengShuiDirections = { N: "Bắc", NE: "Đông Bắc", E: "Đông", SE: "Đông Nam", S: "Nam", SW: "Tây Nam", W: "Tây", NW: "Tây Bắc" }; const fengShuiPalaces = { 1: "Khảm", 2: "Khôn", 3: "Chấn", 4: "Tốn", 6: "Càn", 7: "Đoài", 8: "Cấn", 9: "Ly" }; const fengShuiMatrix = { 1: { good: ["SE", "E", "S", "N"], bad: ["SW", "NE", "NW", "W"] }, 2: { good: ["NE", "W", "NW", "SW"], bad: ["N", "SE", "S", "E"] }, 3: { good: ["S", "N", "SE", "E"], bad: ["W", "NW", "NE", "SW"] }, 4: { good: ["N", "S", "E", "SE"], bad: ["NE", "W", "SW", "NW"] }, 6: { good: ["W", "NE", "SW", "NW"], bad: ["S", "E", "N", "SE"] }, 7: { good: ["NW", "SW", "NE", "W"], bad: ["E", "SE", "S", "N"] }, 8: { good: ["SW", "NW", "W", "NE"], bad: ["SE", "N", "E", "S"] }, 9: { good: ["E", "SE", "N", "S"], bad: ["NW", "W", "SW", "NE"] } }; const fengShuiGoodMeanings = [ ["Sinh Khí", "Vượng khí, tài lộc và cơ hội phát triển."], ["Thiên Y", "Tốt cho sức khỏe, quý nhân và sự che chở."], ["Diên Niên", "Hỗ trợ tình cảm, gia đạo và quan hệ bền vững."], ["Phục Vị", "Tăng sự ổn định, bình an và sức mạnh tinh thần."] ]; const fengShuiBadMeanings = [ ["Tuyệt Mệnh", "Nên hạn chế; dễ gây bất ổn và hao tổn."], ["Ngũ Quỷ", "Dễ phát sinh thị phi, xung đột hoặc trở ngại."], ["Lục Sát", "Có thể ảnh hưởng quan hệ và sự ổn định."], ["Họa Hại", "Dễ gặp phiền toái nhỏ, công việc thiếu thuận lợi."] ]; const reduceFengShuiNumber = (number) => { let value = number; while (value > 9) value = String(value).split("").reduce((sum, digit) => sum + Number(digit), 0); return value; }; const calculateKua = (year, gender) => { // Công thức đang dùng (10/5 trước năm 2000, 9/6 từ năm 2000) // được tính từ tổng rút gọn của hai chữ số cuối năm sinh. const lastTwoDigits = String(year).slice(-2).padStart(2, "0"); const yearNumber = reduceFengShuiNumber( lastTwoDigits.split("").reduce((sum, digit) => sum + Number(digit), 0) ); let kua = gender === "male" ? (year < 2000 ? 10 : 9) - yearNumber : yearNumber + (year < 2000 ? 5 : 6); kua = reduceFengShuiNumber(kua); if (kua === 5) kua = gender === "male" ? 2 : 8; return kua === 0 ? 9 : kua; }; const showFengShuiModal = () => { fengShuiModal.hidden = false; setModalOpen(); document.getElementById("fengShuiBirthYear").focus(); }; const showApartmentFengShuiModal = () => { apartmentFengShuiItems.forEach((item) => { item.panel.hidden = true; }); apartmentFengShuiList.hidden = false; apartmentFengShuiModal.hidden = false; setModalOpen(); apartmentFengShuiContent.scrollTop = 0; closeApartmentFengShuiButton.focus(); }; const hideApartmentFengShuiModal = () => { apartmentFengShuiModal.hidden = true; setModalOpen(); }; const hideFengShuiModal = () => { fengShuiModal.hidden = true; setModalOpen(); }; const renderFengShuiResult = (year, gender) => { const kua = calculateKua(year, gender); const matrix = fengShuiMatrix[kua]; const group = [1, 3, 4, 9].includes(kua) ? "Đông tứ mệnh" : "Tây tứ mệnh"; document.getElementById("fengShuiKua").textContent = `Cung ${fengShuiPalaces[kua]} · số ${kua}`; document.getElementById("fengShuiGroup").textContent = `${gender === "male" ? "Nam" : "Nữ"} sinh năm ${year}`; document.getElementById("fengShuiSelectedDirection").textContent = group; document.getElementById("fengShuiSelectedMeaning").textContent = group === "Đông tứ mệnh" ? "Hợp nhóm hướng Đông tứ trạch" : "Hợp nhóm hướng Tây tứ trạch"; document.getElementById("fengShuiVerdict").textContent = matrix.good.map((direction) => fengShuiDirections[direction]).join(" · "); document.getElementById("fengShuiAdvice").textContent = "Thứ tự: Sinh Khí, Thiên Y, Diên Niên và Phục Vị."; document.getElementById("fengShuiYearLabel").textContent = `${gender === "male" ? "Nam" : "Nữ"} · ${year}`; document.getElementById("fengShuiVerdictCard").className = "feng-shui-verdict is-good"; const grid = document.getElementById("fengShuiDirectionGrid"); grid.replaceChildren(); [...matrix.good.map((direction, index) => ({ direction, meaning: fengShuiGoodMeanings[index], good: true })), ...matrix.bad.map((direction, index) => ({ direction, meaning: fengShuiBadMeanings[index], good: false }))] .forEach((item) => { const article = document.createElement("article"); article.className = `feng-shui-direction ${item.good ? "is-good" : "is-bad"}`; const top = document.createElement("div"); top.className = "feng-shui-direction__top"; const title = document.createElement("strong"); const badge = document.createElement("em"); const description = document.createElement("p"); title.textContent = fengShuiDirections[item.direction]; badge.textContent = item.meaning[0]; description.textContent = item.meaning[1]; top.append(title, badge); article.append(top, description); grid.append(article); }); document.getElementById("fengShuiResults").hidden = false; document.getElementById("fengShuiError").hidden = true; }; const zodiacByRemainder = ["Thân", "Dậu", "Tuất", "Hợi", "Tý", "Sửu", "Dần", "Mão", "Thìn", "Tỵ", "Ngọ", "Mùi"]; const hoangOcNames = ["Nhất Cát", "Nhì Nghi", "Tam Địa Sát", "Tứ Tấn Tài", "Ngũ Thọ Tử", "Lục Hoang Ốc"]; const getHomeAgeAssessment = (birthYear, purchaseYear) => { const lunarAge = purchaseYear - birthYear + 1; const birthZodiac = zodiacByRemainder[birthYear % 12]; const purchaseZodiac = zodiacByRemainder[purchaseYear % 12]; const tamTaiMap = { "Thân": ["Dần", "Mão", "Thìn"], "Tý": ["Dần", "Mão", "Thìn"], "Thìn": ["Dần", "Mão", "Thìn"], "Dần": ["Thân", "Dậu", "Tuất"], "Ngọ": ["Thân", "Dậu", "Tuất"], "Tuất": ["Thân", "Dậu", "Tuất"], "Hợi": ["Tỵ", "Ngọ", "Mùi"], "Mão": ["Tỵ", "Ngọ", "Mùi"], "Mùi": ["Tỵ", "Ngọ", "Mùi"], "Tỵ": ["Hợi", "Tý", "Sửu"], "Dậu": ["Hợi", "Tý", "Sửu"], "Sửu": ["Hợi", "Tý", "Sửu"] }; const tamTai = tamTaiMap[birthZodiac].includes(purchaseZodiac); const kimLauRemainder = lunarAge % 9; const kimLau = [1, 3, 6, 8].includes(kimLauRemainder); const kimLauNames = { 1: "Kim Lâu Thân", 3: "Kim Lâu Thê", 6: "Kim Lâu Tử", 8: "Kim Lâu Súc" }; const hoangOcIndex = (Math.floor(lunarAge / 10) - 1 + lunarAge % 10 + 60) % 6; const hoangOc = ![0, 1, 3].includes(hoangOcIndex); return { lunarAge, birthZodiac, purchaseZodiac, criteria: [ { name: "Tam Tai", bad: tamTai, detail: tamTai ? `Tuổi ${birthZodiac} gặp năm ${purchaseZodiac}, thuộc chu kỳ Tam Tai.` : `Tuổi ${birthZodiac} không gặp Tam Tai trong năm ${purchaseZodiac}.` }, { name: "Kim Lâu", bad: kimLau, detail: kimLau ? `Tuổi âm chia 9 dư ${kimLauRemainder}: ${kimLauNames[kimLauRemainder]}.` : `Tuổi âm chia 9 dư ${kimLauRemainder}, không phạm Kim Lâu.` }, { name: "Hoang Ốc", bad: hoangOc, detail: `${hoangOcNames[hoangOcIndex]} — ${hoangOc ? "cung chưa thuận lợi cho việc tạo lập nhà ở." : "cung tốt, thuận lợi cho nhà ở."}` } ] }; }; const showHomeAgeModal = () => { homeAgeModal.hidden = false; setModalOpen(); document.getElementById("homeAgeBirthYear").focus(); }; const hideHomeAgeModal = () => { homeAgeModal.hidden = true; setModalOpen(); }; const renderHomeAgeResult = (birthYear, purchaseYear) => { const assessment = getHomeAgeAssessment(birthYear, purchaseYear); const badCount = assessment.criteria.filter((item) => item.bad).length; const verdictCard = document.getElementById("homeAgeVerdictCard"); const verdicts = badCount === 0 ? ["Tuổi đẹp để mua nhà", "Không phạm Tam Tai, Kim Lâu và Hoang Ốc theo cách tính tham khảo.", "is-good"] : badCount === 1 ? ["Có thể cân nhắc", "Có 1 tiêu chí chưa thuận; nên xem thêm điều kiện thực tế hoặc tham khảo chuyên gia.", "is-warning"] : ["Nên cân nhắc năm khác", `Có ${badCount} tiêu chí chưa thuận lợi trong năm dự định mua nhà.`, "is-bad"]; document.getElementById("homeAgeLunarAge").textContent = `${assessment.lunarAge} tuổi`; document.getElementById("homeAgeZodiac").textContent = `Tuổi ${assessment.birthZodiac} · năm ${assessment.purchaseZodiac}`; document.getElementById("homeAgeVerdict").textContent = verdicts[0]; document.getElementById("homeAgeAdvice").textContent = verdicts[1]; verdictCard.className = `home-age-verdict ${verdicts[2]}`; const criteria = document.getElementById("homeAgeCriteria"); criteria.replaceChildren(); assessment.criteria.forEach((item) => { const article = document.createElement("article"); article.className = `home-age-criterion ${item.bad ? "is-bad" : "is-good"}`; const top = document.createElement("div"); top.className = "home-age-criterion__top"; const title = document.createElement("strong"); const badge = document.createElement("em"); const detail = document.createElement("p"); title.textContent = item.name; badge.textContent = item.bad ? "PHẠM" : "KHÔNG PHẠM"; detail.textContent = item.detail; top.append(title, badge); article.append(top, detail); criteria.append(article); }); const suggested = document.getElementById("homeAgeSuggestedYears"); suggested.replaceChildren(); for (let year = purchaseYear; year <= purchaseYear + 10 && suggested.children.length < 3; year += 1) { if (getHomeAgeAssessment(birthYear, year).criteria.every((item) => !item.bad)) { const tag = document.createElement("b"); tag.textContent = year; suggested.append(tag); } } if (!suggested.children.length) suggested.textContent = "Chưa có năm phù hợp trong 10 năm tới"; document.getElementById("homeAgeResults").hidden = false; document.getElementById("homeAgeError").hidden = true; }; const showBuildAgeModal = () => { buildAgeModal.hidden = false; setModalOpen(); document.getElementById("buildAgeBirthYear").focus(); }; const hideBuildAgeModal = () => { buildAgeModal.hidden = true; setModalOpen(); }; const appendYearTags = (container, years, suffix = "") => { container.replaceChildren(); if (!years.length) { const empty = document.createElement("span"); empty.textContent = "Chưa tìm thấy trong phạm vi tra cứu"; container.append(empty); return; } years.forEach((year) => { const tag = document.createElement("b"); tag.textContent = `${year}${suffix}`; container.append(tag); }); }; const renderBuildAgeResult = (birthYear, buildYear) => { const assessment = getHomeAgeAssessment(birthYear, buildYear); const badCount = assessment.criteria.filter((item) => item.bad).length; const verdicts = badCount === 0 ? ["Năm thuận lợi để xây nhà", "Không phạm Tam Tai, Kim Lâu và Hoang Ốc theo cách tính tham khảo.", "is-good"] : badCount === 1 ? ["Cần cân nhắc thêm", "Có 1 tiêu chí chưa thuận; có thể xem xét thời điểm khác hoặc phương án mượn tuổi.", "is-warning"] : ["Chưa thuận để động thổ", `Có ${badCount} tiêu chí chưa thuận lợi; nên cân nhắc năm khác hoặc mượn tuổi phù hợp.`, "is-bad"]; document.getElementById("buildAgeLunarAge").textContent = `${assessment.lunarAge} tuổi`; document.getElementById("buildAgeZodiac").textContent = `Gia chủ tuổi ${assessment.birthZodiac} · năm ${assessment.purchaseZodiac}`; document.getElementById("buildAgeVerdict").textContent = verdicts[0]; document.getElementById("buildAgeAdvice").textContent = verdicts[1]; document.getElementById("buildAgeVerdictCard").className = `home-age-verdict ${verdicts[2]}`; const criteria = document.getElementById("buildAgeCriteria"); criteria.replaceChildren(); assessment.criteria.forEach((item) => { const article = document.createElement("article"); article.className = `home-age-criterion ${item.bad ? "is-bad" : "is-good"}`; const top = document.createElement("div"); top.className = "home-age-criterion__top"; const title = document.createElement("strong"); const badge = document.createElement("em"); const detail = document.createElement("p"); title.textContent = item.name; badge.textContent = item.bad ? "PHẠM" : "KHÔNG PHẠM"; detail.textContent = item.detail; top.append(title, badge); article.append(top, detail); criteria.append(article); }); const goodYears = []; for (let year = buildYear; year <= buildYear + 12 && goodYears.length < 4; year += 1) { if (getHomeAgeAssessment(birthYear, year).criteria.every((item) => !item.bad)) goodYears.push(year); } const borrowBirthYears = []; for (let candidate = buildYear - 70; candidate <= buildYear - 30 && borrowBirthYears.length < 5; candidate += 1) { if (getHomeAgeAssessment(candidate, buildYear).criteria.every((item) => !item.bad)) borrowBirthYears.push(candidate); } appendYearTags(document.getElementById("buildAgeSuggestedYears"), goodYears); appendYearTags(document.getElementById("buildAgeBorrowYears"), borrowBirthYears, ""); document.getElementById("buildAgeResults").hidden = false; document.getElementById("buildAgeError").hidden = true; }; const apartmentPriorities = { family: { title: "An cư gia đình", description: "Ưu tiên sự yên tĩnh, an toàn và sinh hoạt lâu dài.", positions: ["Căn giữa hành lang, hạn chế sát thang máy và phòng rác.", "Tầm nhìn thoáng, có ánh sáng nhưng tránh nắng Tây trực diện.", "Gần khu tiện ích trẻ em nhưng không tiếp giáp khu vực ồn."] }, investment: { title: "Đầu tư, thanh khoản", description: "Ưu tiên tầng phổ biến, mặt bằng dễ ở và khả năng bán lại.", positions: ["Căn vuông vức, diện tích và số phòng ngủ dễ tiếp cận số đông.", "Tầm nhìn ít bị che chắn bởi công trình tương lai.", "Khoảng cách hợp lý tới thang máy, tránh vị trí quá đặc thù."] }, career: { title: "Công việc, tài lộc", description: "Ưu tiên Sinh Khí, không gian sáng và bàn làm việc thuận tiện.", positions: ["Ưu tiên hướng Sinh Khí trong danh sách gợi ý.", "Phòng khách sáng, có khoảng trống bố trí góc làm việc.", "Tránh cửa chính đối diện trực tiếp thang máy hoặc hành lang đâm thẳng."] }, health: { title: "Sức khỏe, nghỉ dưỡng", description: "Ưu tiên Thiên Y, thông gió và môi trường ít tiếng ồn.", positions: ["Ưu tiên hướng Thiên Y trong danh sách gợi ý.", "Căn có thông gió chéo, không sát phòng máy hoặc trục kỹ thuật.", "Tránh tầng chịu nhiều bụi, tiếng xe hoặc nắng nóng kéo dài."] } }; const apartmentElements = { 0: ["Kim", [4, 9], [5, 0]], 1: ["Kim", [4, 9], [5, 0]], 2: ["Thủy", [1, 6], [4, 9]], 3: ["Thủy", [1, 6], [4, 9]], 4: ["Mộc", [3, 8], [1, 6]], 5: ["Mộc", [3, 8], [1, 6]], 6: ["Hỏa", [2, 7], [3, 8]], 7: ["Hỏa", [2, 7], [3, 8]], 8: ["Thổ", [5, 0], [2, 7]], 9: ["Thổ", [5, 0], [2, 7]] }; const showApartmentSuggestionModal = () => { apartmentSuggestionModal.hidden = false; setModalOpen(); document.getElementById("apartmentBirthYear").focus(); }; const hideApartmentSuggestionModal = () => { apartmentSuggestionModal.hidden = true; setModalOpen(); }; const renderApartmentSuggestion = (birthYear, gender, priorityKey) => { const kua = calculateKua(birthYear, gender); const directionData = fengShuiMatrix[kua].good; const elementData = apartmentElements[birthYear % 10]; const priority = apartmentPriorities[priorityKey]; const directionTags = document.getElementById("apartmentDirectionTags"); const floorTags = document.getElementById("apartmentFloorTags"); const positionList = document.getElementById("apartmentPositionList"); document.getElementById("apartmentPalace").textContent = `Cung ${fengShuiPalaces[kua]} · ${[1, 3, 4, 9].includes(kua) ? "Đông" : "Tây"} tứ mệnh`; document.getElementById("apartmentElement").textContent = `Ngũ hành tham khảo: ${elementData[0]}`; document.getElementById("apartmentPriorityTitle").textContent = priority.title; document.getElementById("apartmentPriorityDescription").textContent = priority.description; directionTags.replaceChildren(); directionData.forEach((direction, index) => { const tag = document.createElement("b"); tag.textContent = `${fengShuiDirections[direction]} · ${fengShuiGoodMeanings[index][0]}`; directionTags.append(tag); }); floorTags.replaceChildren(); [ ...elementData[1].map((digit) => `Tầng đuôi ${digit} · tương hợp`), ...elementData[2].map((digit) => `Tầng đuôi ${digit} · tương sinh`) ].forEach((label) => { const tag = document.createElement("b"); tag.textContent = label; floorTags.append(tag); }); positionList.replaceChildren(); priority.positions.forEach((description) => { const item = document.createElement("li"); item.textContent = description; positionList.append(item); }); document.getElementById("apartmentSuggestionResults").hidden = false; document.getElementById("apartmentSuggestionError").hidden = true; }; const showViewingDateModal = () => { viewingDateModal.hidden = false; setModalOpen(); document.getElementById("viewingBirthYear").focus(); }; const hideViewingDateModal = () => { viewingDateModal.hidden = true; setModalOpen(); }; const getViewingDateSuggestions = (birthYear, startDate, range, timePreference) => { const birthZodiac = zodiacByRemainder[birthYear % 12]; const birthIndex = zodiacByRemainder.indexOf(birthZodiac); const compatibleGroups = [ ["Thân", "Tý", "Thìn"], ["Dần", "Ngọ", "Tuất"], ["Hợi", "Mão", "Mùi"], ["Tỵ", "Dậu", "Sửu"] ]; const birthGroup = compatibleGroups.find((group) => group.includes(birthZodiac)); const suggestions = []; for (let offset = 0; offset < range; offset += 1) { const date = new Date(startDate); date.setDate(startDate.getDate() + offset); const dayZodiac = zodiacByRemainder[Math.floor(date.getTime() / 86400000 + 4) % 12]; const dayIndex = zodiacByRemainder.indexOf(dayZodiac); const weekday = date.getDay(); let score = 68; const reasons = []; if (weekday === 0 || weekday === 6) { score += 12; reasons.push("Thuận tiện sắp xếp lịch"); } else if (weekday === 2 || weekday === 4) { score += 7; reasons.push("Ngày giữa tuần dễ khảo sát kỹ"); } if (birthGroup.includes(dayZodiac)) { score += 15; reasons.push(`Ngày ${dayZodiac} tam hợp tuổi ${birthZodiac}`); } if (Math.abs(dayIndex - birthIndex) === 6) { score -= 22; reasons.push(`Ngày ${dayZodiac} xung tuổi ${birthZodiac}`); } if ([8, 9, 10, 14, 15, 16].includes(date.getDate())) { score += 4; reasons.push("Thời điểm dễ ghi nhớ, sắp lịch"); } if (score >= 72) suggestions.push({ date, dayZodiac, score, reasons }); } const slots = timePreference === "morning" ? ["08:00–10:00", "09:30–11:00"] : timePreference === "afternoon" ? ["14:00–16:00", "15:30–17:00"] : ["09:00–11:00", "14:30–16:30"]; return { birthZodiac, slots, dates: suggestions.sort((a, b) => b.score - a.score || a.date - b.date).slice(0, 5) }; }; const renderViewingDates = (birthYear, startDate, range, timePreference) => { const result = getViewingDateSuggestions(birthYear, startDate, range, timePreference); const list = document.getElementById("viewingDateList"); document.getElementById("viewingDateZodiac").textContent = `Gia chủ tuổi ${result.birthZodiac}`; document.getElementById("viewingDateSummaryText").textContent = `Top ${result.dates.length} ngày trong ${range} ngày tính từ ${startDate.toLocaleDateString("vi-VN")}.`; list.replaceChildren(); result.dates.forEach((item, index) => { const card = document.createElement("article"); card.className = `viewing-date-card${index === 0 ? " is-best" : ""}`; const dateBox = document.createElement("div"); dateBox.className = "viewing-date-card__date"; const weekday = document.createElement("small"); const day = document.createElement("strong"); const month = document.createElement("span"); weekday.textContent = new Intl.DateTimeFormat("vi-VN", { weekday: "long" }).format(item.date); day.textContent = String(item.date.getDate()).padStart(2, "0"); month.textContent = `Tháng ${item.date.getMonth() + 1}/${item.date.getFullYear()} · ngày ${item.dayZodiac}`; dateBox.append(weekday, day, month); const body = document.createElement("div"); body.className = "viewing-date-card__body"; const score = document.createElement("span"); const slot = document.createElement("strong"); const reason = document.createElement("p"); score.className = "viewing-date-score"; score.textContent = index === 0 ? `ĐỀ XUẤT TỐT NHẤT · ${item.score}/100` : `PHÙ HỢP · ${item.score}/100`; slot.textContent = result.slots[index % result.slots.length]; reason.textContent = item.reasons.slice(0, 2).join(". ") || "Khung ngày phù hợp để bố trí lịch xem căn."; body.append(score, slot, reason); card.append(dateBox, body); list.append(card); }); document.getElementById("viewingDateResults").hidden = false; document.getElementById("viewingDateError").hidden = true; }; const showDepositDateModal = () => { depositDateModal.hidden = false; setModalOpen(); document.getElementById("depositBirthYear").focus(); }; const hideDepositDateModal = () => { depositDateModal.hidden = true; setModalOpen(); }; const renderDepositDates = (birthYear, startDate, range, type) => { const result = getViewingDateSuggestions(birthYear, startDate, range, "any"); const typeLabels = { apartment: "căn hộ", house: "nhà/đất", project: "sản phẩm dự án" }; const list = document.getElementById("depositDateList"); document.getElementById("depositDateZodiac").textContent = `Người mua tuổi ${result.birthZodiac}`; document.getElementById("depositDateSummaryText").textContent = `Top ${result.dates.length} ngày trong ${range} ngày cho giao dịch ${typeLabels[type]}.`; list.replaceChildren(); result.dates.forEach((item, index) => { const card = document.createElement("article"); card.className = `viewing-date-card${index === 0 ? " is-best" : ""}`; const dateBox = document.createElement("div"); dateBox.className = "viewing-date-card__date"; const weekday = document.createElement("small"); const day = document.createElement("strong"); const month = document.createElement("span"); weekday.textContent = new Intl.DateTimeFormat("vi-VN", { weekday: "long" }).format(item.date); day.textContent = String(item.date.getDate()).padStart(2, "0"); month.textContent = `Tháng ${item.date.getMonth() + 1}/${item.date.getFullYear()} · ngày ${item.dayZodiac}`; dateBox.append(weekday, day, month); const body = document.createElement("div"); body.className = "viewing-date-card__body"; const score = document.createElement("span"); const slot = document.createElement("strong"); const reason = document.createElement("p"); score.className = "viewing-date-score"; score.textContent = index === 0 ? `ƯU TIÊN · ${item.score}/100` : `PHÙ HỢP · ${item.score}/100`; slot.textContent = index % 2 ? "14:00–16:00" : "09:00–11:00"; reason.textContent = `${item.reasons.slice(0, 1).join("") || "Ngày phù hợp tham khảo"}. Nên ký trong giờ hành chính để thuận tiện xác minh và chuyển khoản.`; body.append(score, slot, reason); card.append(dateBox, body); list.append(card); }); document.querySelectorAll("#depositDateModal .deposit-safety-grid input").forEach((input) => { input.checked = false; }); document.getElementById("depositDateResults").hidden = false; document.getElementById("depositDateError").hidden = true; }; const showMoveInDateModal = () => { moveInDateModal.hidden = false; setModalOpen(); document.getElementById("moveInBirthYear").focus(); }; const hideMoveInDateModal = () => { moveInDateModal.hidden = true; setModalOpen(); }; const renderMoveInDates = (birthYear, startDate, range, propertyType) => { const result = getViewingDateSuggestions(birthYear, startDate, range, "morning"); const list = document.getElementById("moveInDateList"); const propertyLabel = propertyType === "apartment" ? "căn hộ" : "nhà riêng"; document.getElementById("moveInDateZodiac").textContent = `Gia chủ tuổi ${result.birthZodiac}`; document.getElementById("moveInDateSummaryText").textContent = `Top ${result.dates.length} ngày trong ${range} ngày để chuyển vào ${propertyLabel}.`; list.replaceChildren(); result.dates.forEach((item, index) => { const card = document.createElement("article"); card.className = `viewing-date-card${index === 0 ? " is-best" : ""}`; const dateBox = document.createElement("div"); dateBox.className = "viewing-date-card__date"; const weekday = document.createElement("small"); const day = document.createElement("strong"); const month = document.createElement("span"); weekday.textContent = new Intl.DateTimeFormat("vi-VN", { weekday: "long" }).format(item.date); day.textContent = String(item.date.getDate()).padStart(2, "0"); month.textContent = `Tháng ${item.date.getMonth() + 1}/${item.date.getFullYear()} · ngày ${item.dayZodiac}`; dateBox.append(weekday, day, month); const body = document.createElement("div"); body.className = "viewing-date-card__body"; const score = document.createElement("span"); const slot = document.createElement("strong"); const reason = document.createElement("p"); score.className = "viewing-date-score"; score.textContent = index === 0 ? `ƯU TIÊN · ${item.score}/100` : `PHÙ HỢP · ${item.score}/100`; slot.textContent = index % 2 ? "07:00–09:00" : "09:00–11:00"; reason.textContent = `${item.reasons.slice(0, 1).join("") || "Ngày phù hợp tham khảo"}. Khung giờ sáng thuận tiện chuyển đồ và kiểm tra nhà.`; body.append(score, slot, reason); card.append(dateBox, body); list.append(card); }); const practical = propertyType === "apartment" ? ["Đăng ký lịch chuyển đồ và thang máy với ban quản lý.", "Kiểm tra điện, nước, internet và biên bản bàn giao.", "Bảo vệ sàn, tường và khu vực vận chuyển chung."] : ["Kiểm tra điện, nước, khóa cửa và hệ thống an ninh.", "Bố trí xe chuyển đồ, lối vào và nơi đỗ phù hợp.", "Kiểm tra mái, thoát nước và khu vực xung quanh nhà."]; const practicalList = document.getElementById("moveInPracticalList"); practicalList.replaceChildren(); practical.forEach((text) => { const item = document.createElement("li"); item.textContent = text; practicalList.append(item); }); document.getElementById("moveInDateResults").hidden = false; document.getElementById("moveInDateError").hidden = true; }; const fengShuiElementColors = { Kim: [["Trắng", "#f7f7f5"], ["Ghi sáng", "#c9ccca"], ["Vàng nhạt", "#ead99b"]], Thủy: [["Xanh dương", "#4f8fd9"], ["Đen", "#25282b"], ["Trắng", "#f7f7f5"]], Mộc: [["Xanh lá", "#4c9b68"], ["Xanh ngọc", "#74b8a5"], ["Xanh dương", "#4f8fd9"]], Hỏa: [["Đỏ đất", "#b9574f"], ["Cam", "#df8b45"], ["Xanh lá", "#4c9b68"]], Thổ: [["Vàng đất", "#c9a354"], ["Nâu", "#8b6547"], ["Cam nhạt", "#e8a66c"]] }; const otherFengShuiSpaces = { living: { title: "Phòng khách", summary: "Tập trung sinh khí, ánh sáng và sự kết nối.", layout: ["Giữ lối vào và luồng di chuyển thông thoáng.", "Bố trí ghế có điểm tựa vững, hướng nhìn ra không gian mở.", "Dùng cây xanh vừa phải tại vị trí đủ sáng."], avoid: ["Gương đối diện trực tiếp cửa chính.", "Đồ đạc chắn lối hoặc quá nhiều góc nhọn."] }, bedroom: { title: "Phòng ngủ", summary: "Ưu tiên sự yên tĩnh, riêng tư và giấc ngủ.", layout: ["Đầu giường tựa tường chắc chắn.", "Dùng ánh sáng ấm, rèm cản sáng linh hoạt.", "Giữ hai bên giường tương đối cân bằng."], avoid: ["Đầu giường sát cửa sổ hoặc đối diện cửa ra vào.", "Gương chiếu trực tiếp vào giường."] }, work: { title: "Góc làm việc", summary: "Ưu tiên tập trung, sáng rõ và tư thế chủ động.", layout: ["Bàn có điểm tựa phía sau, phía trước thoáng.", "Ưu tiên một trong các hướng tốt của cung mệnh.", "Bố trí đủ sáng và quản lý dây điện gọn gàng."], avoid: ["Ngồi quay lưng trực tiếp ra cửa.", "Bàn dưới xà ngang hoặc sát nguồn ồn."] }, kitchen: { title: "Bếp", summary: "Ưu tiên sạch sẽ, thông gió và an toàn vận hành.", layout: ["Tách hợp lý khu nấu, rửa và lưu trữ.", "Bảo đảm hút mùi, ánh sáng và mặt bàn thao tác.", "Giữ bếp khô ráo, ngăn nắp."], avoid: ["Bếp nấu sát chậu rửa nếu không có khoảng đệm.", "Bếp đối diện trực tiếp cửa nhà vệ sinh."] } }; const showOtherFengShuiModal = () => { otherFengShuiModal.hidden = false; setModalOpen(); document.getElementById("otherFengShuiBirthYear").focus(); }; const hideOtherFengShuiModal = () => { otherFengShuiModal.hidden = true; setModalOpen(); }; const renderTextList = (container, values) => { container.replaceChildren(); values.forEach((value) => { const item = document.createElement("li"); item.textContent = value; container.append(item); }); }; const renderOtherFengShui = (birthYear, gender, spaceKey) => { const kua = calculateKua(birthYear, gender); const element = apartmentElements[birthYear % 10][0]; const space = otherFengShuiSpaces[spaceKey]; document.getElementById("otherFengShuiPalace").textContent = `Cung ${fengShuiPalaces[kua]} · số ${kua}`; document.getElementById("otherFengShuiElement").textContent = `Ngũ hành tham khảo: ${element}`; document.getElementById("otherFengShuiSpaceTitle").textContent = space.title; document.getElementById("otherFengShuiSpaceSummary").textContent = space.summary; const colors = document.getElementById("otherFengShuiColors"); colors.replaceChildren(); fengShuiElementColors[element].forEach(([name, color]) => { const tag = document.createElement("span"); const swatch = document.createElement("i"); tag.className = "feng-shui-color"; swatch.style.backgroundColor = color; tag.append(swatch, document.createTextNode(name)); colors.append(tag); }); const directions = document.getElementById("otherFengShuiDirections"); directions.replaceChildren(); fengShuiMatrix[kua].good.forEach((direction, index) => { const tag = document.createElement("b"); tag.textContent = `${fengShuiDirections[direction]} · ${fengShuiGoodMeanings[index][0]}`; directions.append(tag); }); renderTextList(document.getElementById("otherFengShuiLayout"), space.layout); renderTextList(document.getElementById("otherFengShuiAvoid"), space.avoid); document.getElementById("otherFengShuiResults").hidden = false; document.getElementById("otherFengShuiError").hidden = true; }; const activateLockGuideTab = (tabName) => { lockGuideModal.querySelectorAll("[data-lock-tab]").forEach((button) => { button.classList.toggle("is-active", button.dataset.lockTab === tabName); }); lockGuideModal.querySelectorAll("[data-lock-panel]").forEach((panel) => { const active = panel.dataset.lockPanel === tabName; panel.hidden = !active; panel.classList.toggle("is-active", active); }); }; const showLockGuideModal = () => { lockGuideModal.hidden = false; setModalOpen(); lockGuideModal.querySelector("[data-lock-tab].is-active").focus(); }; const hideLockGuideModal = () => { lockGuideModal.hidden = true; setModalOpen(); }; const activateTrainingTab = (section) => { trainingModal.querySelectorAll("[data-training-tab]").forEach((button) => { const active = button.dataset.trainingTab === section; button.classList.toggle("is-active", active); button.setAttribute("aria-selected", active ? "true" : "false"); }); trainingModal.querySelectorAll("[data-training-panel]").forEach((panel) => { panel.hidden = panel.dataset.trainingPanel !== section; }); }; const prepareTrainingQuizzes = () => { trainingModal.querySelectorAll("[data-training-quiz]").forEach((quiz) => { quiz.reset(); const questions = [...quiz.querySelectorAll("fieldset[data-answer]")]; for (let index = questions.length - 1; index > 0; index -= 1) { const target = Math.floor(Math.random() * (index + 1)); [questions[index], questions[target]] = [questions[target], questions[index]]; } questions.forEach((question, index) => { question.hidden = index >= 10; question.classList.remove("is-correct", "is-wrong"); question.querySelector("legend span").textContent = index < 10 ? index + 1 : ""; question.querySelector(".training-quiz__explanation").hidden = true; if (index < 10) quiz.querySelector(".training-quiz__actions").before(question); }); quiz.querySelector(".training-quiz__result").textContent = "10 câu ngẫu nhiên / 20 câu"; }); }; const showTrainingModal = (section) => { activateTrainingTab(section); prepareTrainingQuizzes(); trainingModal.hidden = false; setModalOpen(); trainingModal.querySelector(`[data-training-tab="${section}"]`).focus(); }; const hideTrainingModal = () => { trainingModal.hidden = true; setModalOpen(); }; const activateLegalSection = (section) => { legalGuideModal.querySelectorAll("[data-legal-tab]").forEach((button) => { const active = button.dataset.legalTab === section; button.classList.toggle("is-active", active); button.setAttribute("aria-selected", active ? "true" : "false"); }); legalGuideModal.querySelectorAll("[data-legal-panel]").forEach((panel) => { panel.hidden = panel.dataset.legalPanel !== section; }); legalGuideModal.querySelector(".legal-guide-modal__body").scrollTop = 0; }; const showLegalGuideModal = (section) => { activateLegalSection(section); legalGuideModal.hidden = false; setModalOpen(); legalGuideModal.querySelector(`[data-legal-tab="${section}"]`).focus(); }; const hideLegalGuideModal = () => { legalGuideModal.hidden = true; setModalOpen(); }; const interiorPalettes = { warm: { title: "Modern Warm linh hoạt", accent: "#b8794e", images: { compact: "https://test.hisale.com/images/interior-suggestions/compact-warm-views.png", standard: "https://test.hisale.com/images/interior-suggestions/palette-warm-views.png", spacious: "https://test.hisale.com/images/interior-suggestions/spacious-warm-views.png" }, colors: ["#f4eee5", "#d8c2a8", "#b8794e", "#806958", "#f7f4ef"], material: "Trắng ngà, be, gỗ sồi sáng và điểm nhấn đất nung; ưu tiên vải linen, veneer gỗ và đá sáng.", decor: "Ánh sáng vàng ấm 3000K, rèm hai lớp, tranh khổ lớn tông đất và cây xanh dáng thanh." }, sage: { title: "Sage Minimal thư giãn", accent: "#7c9275", images: { compact: "https://test.hisale.com/images/interior-suggestions/compact-sage-views.png", standard: "https://test.hisale.com/images/interior-suggestions/palette-sage-views.png", spacious: "https://test.hisale.com/images/interior-suggestions/spacious-sage-views.png" }, colors: ["#f2f0e8", "#cbd2bf", "#7c9275", "#a88f72", "#ffffff"], material: "Kem nhạt, xanh sage, gỗ sồi và vải thô; bề mặt mờ giúp không gian dịu và ít lỗi mốt.", decor: "Ánh sáng tự nhiên, đèn hắt nhẹ, đầu giường bọc nỉ bo cong và decor gốm/cây xanh vừa phải." }, blue: { title: "Urban Blue hiện đại", accent: "#5c7e98", images: { compact: "https://test.hisale.com/images/interior-suggestions/compact-blue-views.png", standard: "https://test.hisale.com/images/interior-suggestions/palette-blue-views.png", spacious: "https://test.hisale.com/images/interior-suggestions/spacious-blue-views.png" }, colors: ["#f5f3ec", "#b9ccd7", "#5c7e98", "#d6b684", "#3e4850"], material: "Ivory, xanh bụi, gỗ tần bì và điểm kim loại champagne; mặt bếp quartz sáng, tủ sơn mờ.", decor: "Đèn tuyến tính, gương lớn ở sảnh, phụ kiện kim loại mảnh và tranh hình học tông xanh–kem." }, mono: { title: "Contemporary Neutral tinh gọn", accent: "#656a70", images: { compact: "https://test.hisale.com/images/interior-suggestions/compact-mono-views.png", standard: "https://test.hisale.com/images/interior-suggestions/palette-mono-views.png", spacious: "https://test.hisale.com/images/interior-suggestions/spacious-mono-views.png" }, colors: ["#f5f5f2", "#d4d1cc", "#8c8c88", "#555b62", "#b79e84"], material: "Trắng ấm, greige, xám than và gỗ trung tính; kết hợp đá vân nhẹ, kính và kim loại đen mờ.", decor: "Ánh sáng 3500K, decor ít chi tiết, hình khối rõ ràng và một điểm nhấn nghệ thuật đơn sắc." } }; const renderInteriorSuggestion = () => { const area = Number(document.getElementById("interiorArea").value); const age = Number(document.getElementById("interiorAge").value); const residents = Number(document.getElementById("interiorResidents").value); const priority = document.getElementById("interiorPriority").value; const paletteKey = interiorSuggestionForm.querySelector('input[name="interiorPalette"]:checked').value; const palette = interiorPalettes[paletteKey]; const bedroomCount = area < 45 ? 1 : area < 82 ? 2 : area < 125 ? 3 : 4; const compact = area < 55; const spacious = area >= 90; const areaGroup = compact ? "compact" : spacious ? "spacious" : "standard"; const areaGroupLabel = compact ? "căn nhỏ 25–54 m²" : spacious ? "căn rộng từ 90 m²" : "căn tiêu chuẩn 55–89 m²"; const galleryImage = palette.images[areaGroup]; const ageTone = age < 30 ? "trẻ trung, dễ thay đổi" : age < 50 ? "thanh lịch, bền vững" : "êm dịu, thuận tiện di chuyển"; const priorityText = { balanced: "Giữ phòng khách thoáng, bếp–ăn liên thông và phân bổ lưu trữ đều giữa các phòng.", storage: "Tận dụng tủ kịch trần tại sảnh, bếp và phòng ngủ; ưu tiên nội thất tích hợp, hạn chế kệ mở.", family: "Bo tròn góc nội thất, tạo vùng chơi quan sát được từ bếp và dùng vật liệu dễ vệ sinh, chống trượt.", work: "Bố trí bàn làm việc gần ánh sáng tự nhiên, có tủ tài liệu và vách ngăn nhẹ để gọi họp.", luxury: "Mở rộng khu tiếp khách, tạo mảng TV/đá điểm nhấn và hệ ánh sáng nhiều lớp có thể điều chỉnh." }; const layout = `${priorityText[priority]} ${compact ? "Dùng cửa trượt, bàn ăn gọn và đồ đa năng để giữ lối đi tối thiểu 900 mm." : spacious ? "Có thể bổ sung đảo bếp hoặc phòng đa năng nhưng vẫn giữ trục giao thông rõ ràng." : "Ưu tiên đồ vừa tỷ lệ, tránh chia vụn không gian bằng vách kín."}`; const budgetRate = priority === "luxury" ? [12, 20] : priority === "storage" ? [9, 15] : [7, 13]; const budgetMin = Math.round(area * budgetRate[0] / 10) * 10; const budgetMax = Math.round(area * budgetRate[1] / 10) * 10; const result = document.getElementById("interiorSuggestionResult"); result.style.setProperty("--interior-accent", palette.accent); document.getElementById("interiorResultTitle").textContent = palette.title; document.getElementById("interiorResultDescription").textContent = `Phương án ${ageTone}, phù hợp căn ${area} m² cho ${residents >= 5 ? "5+" : residents} người.`; document.getElementById("interiorResultRooms").textContent = `${bedroomCount} PN`; document.getElementById("interiorResultArea").textContent = `${area} m²`; document.getElementById("interiorLayoutSuggestion").textContent = layout; document.getElementById("interiorMaterialSuggestion").textContent = palette.material; document.getElementById("interiorDecorSuggestion").textContent = palette.decor; document.getElementById("interiorBudgetSuggestion").textContent = `Khoảng ${budgetMin.toLocaleString("vi-VN")}–${budgetMax.toLocaleString("vi-VN")} triệu đồng, tùy thiết bị, vật liệu và hiện trạng bàn giao.`; document.getElementById("interiorGalleryNote").textContent = `Bộ ảnh dành cho ${areaGroupLabel}, phong cách ${palette.title}; gồm ba góc nhìn khác nhau của cùng một căn.`; const swatches = document.getElementById("interiorColorSwatches"); swatches.replaceChildren(); palette.colors.forEach((color) => { const item = document.createElement("i"); item.style.background = color; item.title = color; swatches.append(item); }); document.querySelectorAll("[data-interior-image]").forEach((button) => { button.dataset.interiorImage = galleryImage; button.querySelector("img").src = galleryImage; }); }; const showInteriorSuggestionModal = () => { renderInteriorSuggestion(); interiorSuggestionModal.hidden = false; setModalOpen(); document.getElementById("interiorArea").focus(); }; const hideInteriorSuggestionModal = () => { if (!interiorLightbox.hidden) { interiorLightbox.hidden = true; document.getElementById("interiorLightboxImage").src = ""; } interiorSuggestionModal.hidden = true; setModalOpen(); }; const showInteriorLightbox = (button) => { const image = document.getElementById("interiorLightboxImage"); image.src = button.dataset.interiorImage; image.alt = button.dataset.interiorLabel; document.getElementById("interiorLightboxTitle").textContent = button.dataset.interiorLabel; interiorLightbox.hidden = false; setModalOpen(); closeInteriorLightboxButton.focus(); }; const hideInteriorLightbox = () => { interiorLightbox.hidden = true; document.getElementById("interiorLightboxImage").src = ""; setModalOpen(); }; const showInteriorReferenceModal = () => { interiorReferenceModal.hidden = false; setModalOpen(); interiorReferenceModal.querySelector("[data-interior-reference-filter].is-active").focus(); }; const hideInteriorReferenceModal = () => { interiorReferenceModal.hidden = true; setModalOpen(); }; const filterInteriorReferences = (filter) => { interiorReferenceModal.querySelectorAll("[data-interior-reference-filter]").forEach((button) => { button.classList.toggle("is-active", button.dataset.interiorReferenceFilter === filter); }); interiorReferenceModal.querySelectorAll("[data-interior-reference-group]").forEach((sample) => { sample.hidden = filter !== "all" && sample.dataset.interiorReferenceGroup !== filter; }); }; const formatInteriorCost = (value) => `${Math.round(value / 1000000).toLocaleString("vi-VN")} triệu ₫`; const renderInteriorCost = () => { const area = Number(document.getElementById("interiorCostArea").value); const bedrooms = Number(document.getElementById("interiorCostBedrooms").value); const condition = document.getElementById("interiorCostCondition").value; const packageName = document.getElementById("interiorCostPackage").value; const hasAppliances = document.getElementById("interiorCostAppliances").checked; const unitPrices = { basic: 4500000, standard: 7000000, premium: 11000000 }; const conditionCosts = { finished: 0, basic: 900000, raw: 2200000 }; const applianceCosts = { basic: 45000000, standard: 75000000, premium: 130000000 }; const base = area * (unitPrices[packageName] + conditionCosts[condition]); const bedroomAdjustment = Math.max(0, bedrooms - 2) * 18000000; const appliances = hasAppliances ? applianceCosts[packageName] : 0; const subtotal = base + bedroomAdjustment + appliances; const reserve = subtotal * .1; const total = subtotal + reserve; const min = total * .9; const max = total * 1.15; const allocations = [ ["Nội thất liền tường", .38], ["Nội thất rời & trang trí", .20], ["Bếp & thiết bị vệ sinh", .18], ["Trần, đèn & hoàn thiện", .14], ["Dự phòng phát sinh", .10] ]; document.getElementById("interiorCostTotal").textContent = formatInteriorCost(total); document.getElementById("interiorCostRange").textContent = `Khoảng ${formatInteriorCost(min)} – ${formatInteriorCost(max)}`; document.getElementById("interiorCostUnit").textContent = `${formatInteriorCost(total / area)}/m²`; document.getElementById("interiorCostReserve").textContent = formatInteriorCost(reserve); document.getElementById("interiorCostDuration").textContent = `${Math.max(4, Math.ceil(area / 18) + (condition === "raw" ? 3 : condition === "basic" ? 1 : 0))}–${Math.max(6, Math.ceil(area / 15) + (condition === "raw" ? 4 : 2))} tuần`; const breakdown = document.getElementById("interiorCostBreakdown"); breakdown.replaceChildren(); allocations.forEach(([label, ratio]) => { const row = document.createElement("div"); const text = document.createElement("p"); const bar = document.createElement("i"); text.innerHTML = `${label}${formatInteriorCost(total * ratio)}`; bar.style.setProperty("--cost-width", `${ratio * 100}%`); row.append(text, bar); breakdown.append(row); }); }; const showInteriorCostModal = () => { renderInteriorCost(); interiorCostModal.hidden = false; setModalOpen(); document.getElementById("interiorCostArea").focus(); }; const hideInteriorCostModal = () => { interiorCostModal.hidden = true; setModalOpen(); }; const renderSpacePlanning = () => { const area = Number(document.getElementById("spacePlanningArea").value); const bedrooms = Number(document.getElementById("spacePlanningBedrooms").value); const residents = Number(document.getElementById("spacePlanningResidents").value); const priority = document.getElementById("spacePlanningPriority").value; const openKitchen = document.getElementById("spacePlanningOpenKitchen").checked; const priorityTitles = { balanced: "Cân bằng & linh hoạt", living: "Tăng cường không gian sinh hoạt", storage: "Tối ưu lưu trữ", work: "Làm việc tại nhà", family: "An toàn cho gia đình trẻ" }; const bedroomRatio = Math.min(.46, .23 + bedrooms * .055); let livingRatio = priority === "living" ? .30 : openKitchen ? .26 : .23; let storageRatio = priority === "storage" ? .10 : .06; const workRatio = priority === "work" ? .07 : 0; const serviceRatio = .15; const totalFixed = bedroomRatio + livingRatio + storageRatio + workRatio + serviceRatio; if (totalFixed > .94) livingRatio -= totalFixed - .94; const otherRatio = Math.max(.06, 1 - bedroomRatio - livingRatio - storageRatio - workRatio - serviceRatio); const zones = [ ["Phòng khách & bếp", livingRatio, "sofa", openKitchen ? "Không gian liên thông" : "Bếp phân tách"], [`${bedrooms} phòng ngủ`, bedroomRatio, "bed-double", `${residents >= 5 ? "5+" : residents} người sử dụng`], ["WC & khu kỹ thuật", serviceRatio, "bath", "Gom theo trục kỹ thuật"], ["Lưu trữ", storageRatio, "archive", priority === "storage" ? "Tủ kịch trần" : "Tủ âm/tủ đa năng"] ]; if (workRatio) zones.push(["Góc làm việc", workRatio, "laptop", "Ưu tiên gần cửa sổ"]); zones.push(["Sảnh & giao thông", otherRatio, "route", "Lối đi thông thoáng"]); document.getElementById("spacePlanningResultTitle").textContent = priorityTitles[priority]; document.getElementById("spacePlanningResultArea").textContent = `${area} m²`; document.getElementById("spacePlanningResultText").textContent = `Phương án ${bedrooms} phòng ngủ cho ${residents >= 5 ? "5+" : residents} người, ưu tiên ${priorityTitles[priority].toLowerCase()}.`; const container = document.getElementById("spacePlanningZones"); container.replaceChildren(); zones.forEach(([name, ratio, icon, note]) => { const zone = document.createElement("article"); const size = Math.max(2, Math.round(area * ratio * 10) / 10); zone.innerHTML = `
${name}

${note}

${size.toLocaleString("vi-VN")} m²`; container.append(zone); }); document.getElementById("spacePlanningCirculation").textContent = openKitchen ? "Giữ trục từ cửa vào phòng khách rõ ràng, bếp–ăn liên thông và lối đi chính rộng khoảng 900 mm." : "Tách bếp bằng vách nhẹ hoặc cửa trượt, tránh để luồng đi xuyên qua khu nấu."; document.getElementById("spacePlanningStorage").textContent = priority === "storage" ? "Bố trí tủ kịch trần tại sảnh, hành lang và đầu giường; ưu tiên giường, ghế có khoang chứa." : "Tận dụng tủ âm tường tại sảnh và phòng ngủ, giữ mặt sàn thoáng để căn hộ rộng hơn."; document.getElementById("spacePlanningLight").textContent = priority === "family" ? "Dành vùng sáng tự nhiên cho khu chơi, bo góc đồ nội thất và giữ khả năng quan sát từ bếp." : "Đưa phòng khách và không gian làm việc về phía cửa sổ; hạn chế vách kín cản sáng và thông gió."; if (window.lucide) window.lucide.createIcons(); }; const showSpacePlanningModal = () => { renderSpacePlanning(); spacePlanningModal.hidden = false; setModalOpen(); document.getElementById("spacePlanningArea").focus(); }; const hideSpacePlanningModal = () => { spacePlanningModal.hidden = true; setModalOpen(); }; const materialCatalogItems = [ { group: "surface", budget: "economy", icon: "grid-3x3", name: "Gạch porcelain", use: "Sàn phòng khách, bếp và WC", price: "250.000–650.000 ₫/m²", good: "Bền, ít thấm nước, dễ vệ sinh", note: "Chọn độ chống trượt phù hợp từng khu vực" }, { group: "surface", budget: "standard", icon: "layers-3", name: "Sàn gỗ công nghiệp", use: "Phòng khách và phòng ngủ", price: "350.000–900.000 ₫/m²", good: "Ấm chân, thi công nhanh, nhiều màu", note: "Ưu tiên cốt chống ẩm và hèm khóa tốt" }, { group: "surface", budget: "premium", icon: "sparkles", name: "Đá thạch anh nhân tạo", use: "Mặt bếp, lavabo và mảng nhấn", price: "2,5–7 triệu ₫/m dài", good: "Ít thấm, bền màu, dễ bảo trì", note: "Kiểm tra độ dày, mối nối và bảo hành" }, { group: "wood", budget: "economy", icon: "cabinet", name: "MDF chống ẩm phủ Melamine", use: "Tủ bếp, tủ áo và kệ TV", price: "2,8–5,5 triệu ₫/m²", good: "Chi phí hợp lý, màu sắc đa dạng", note: "Không để cạnh cắt tiếp xúc nước kéo dài" }, { group: "wood", budget: "standard", icon: "panels-top-left", name: "Plywood phủ Laminate", use: "Tủ bếp và đồ liền tường", price: "4,5–8 triệu ₫/m²", good: "Chịu lực và bám vít tốt", note: "Chọn keo phát thải thấp, xử lý kín cạnh" }, { group: "wood", budget: "premium", icon: "trees", name: "Gỗ tự nhiên ghép thanh", use: "Cửa, bàn ăn và chi tiết điểm nhấn", price: "7–18 triệu ₫/m²", good: "Vân tự nhiên, có thể sửa và hoàn thiện lại", note: "Cần kiểm soát độ ẩm và xử lý cong vênh" }, { group: "kitchen", budget: "standard", icon: "cooking-pot", name: "Bếp từ âm", use: "Khu nấu ăn", price: "8–25 triệu ₫/bộ", good: "An toàn, sạch và hiệu suất cao", note: "Kiểm tra công suất điện và kích thước khoét đá" }, { group: "kitchen", budget: "premium", icon: "wind", name: "Máy hút mùi âm tủ", use: "Bếp căn hộ", price: "5–20 triệu ₫/bộ", good: "Gọn, giảm mùi và dầu mỡ", note: "Xác định đường thoát khí và độ ồn thực tế" }, { group: "bathroom", budget: "standard", icon: "shower-head", name: "Sen nhiệt độ", use: "Phòng tắm", price: "5–18 triệu ₫/bộ", good: "Ổn định nhiệt, an toàn cho trẻ nhỏ", note: "Yêu cầu áp lực nước và nguồn nóng phù hợp" }, { group: "bathroom", budget: "premium", icon: "toilet", name: "Bồn cầu thông minh", use: "WC chính", price: "15–60 triệu ₫/bộ", good: "Tăng tiện nghi và vệ sinh", note: "Cần ổ điện chống nước và dịch vụ bảo hành" }, { group: "electrical", budget: "economy", icon: "lightbulb", name: "Đèn LED âm trần", use: "Chiếu sáng nền toàn căn", price: "120.000–450.000 ₫/đèn", good: "Tiết kiệm điện, dễ thay thế", note: "Chọn CRI từ 80–90 và nhiệt màu đồng nhất" }, { group: "electrical", budget: "premium", icon: "sliders-horizontal", name: "Công tắc thông minh", use: "Điều khiển đèn, rèm và ngữ cảnh", price: "1–4 triệu ₫/điểm", good: "Điều khiển từ xa, tạo kịch bản", note: "Kiểm tra dây trung tính, chuẩn kết nối và hệ sinh thái" } ]; let activeMaterialGroup = "all"; const materialBudgetLabels = { economy: "Tiết kiệm", standard: "Tiêu chuẩn", premium: "Cao cấp" }; const renderMaterialCatalog = () => { const keyword = document.getElementById("materialCatalogSearch").value.trim().toLocaleLowerCase("vi-VN"); const budget = document.getElementById("materialCatalogBudget").value; const items = materialCatalogItems.filter((item) => { const matchesGroup = activeMaterialGroup === "all" || item.group === activeMaterialGroup; const matchesBudget = budget === "all" || item.budget === budget; const haystack = `${item.name} ${item.use} ${item.good} ${item.note}`.toLocaleLowerCase("vi-VN"); return matchesGroup && matchesBudget && (!keyword || haystack.includes(keyword)); }); const grid = document.getElementById("materialCatalogGrid"); grid.replaceChildren(); items.forEach((item) => { const card = document.createElement("article"); card.className = `material-item material-item--${item.budget}`; card.innerHTML = `
${materialBudgetLabels[item.budget]}

${item.name}

${item.use}

${item.price}
Ưu điểm
${item.good}
Lưu ý
${item.note}
`; grid.append(card); }); document.getElementById("materialCatalogEmpty").hidden = items.length > 0; if (window.lucide) window.lucide.createIcons(); }; const showMaterialCatalogModal = () => { renderMaterialCatalog(); materialCatalogModal.hidden = false; setModalOpen(); document.getElementById("materialCatalogSearch").focus(); }; const hideMaterialCatalogModal = () => { materialCatalogModal.hidden = true; setModalOpen(); }; const designStyles = { warm: { name: "Modern Warm", tag: "ẤM ÁP · HIỆN ĐẠI", fit: 90, image: "https://test.hisale.com/images/interior-suggestions/palette-warm-views.png", colors: ["#f4eee5", "#d8c2a8", "#b8794e", "#806958", "#f7f4ef"], description: "Không gian hiện đại được làm mềm bằng gỗ sáng, sắc be và ánh sáng vàng ấm.", features: "Đường nét gọn, hình khối bo nhẹ, ít chi tiết trang trí nhưng vẫn gần gũi.", materials: "Gỗ sồi, veneer, đá sáng, vải linen và kim loại màu champagne.", suitable: "Gia đình trẻ, căn 2–3 phòng ngủ và người thích cảm giác ấm cúng.", budget: "Trung bình đến khá; dễ cân đối bằng vật liệu thay thế.", tip: "Giữ tối đa 3 tông màu chính và dùng ánh sáng 3000K để không gian đồng nhất." }, sage: { name: "Sage Minimal", tag: "TỐI GIẢN · THƯ GIÃN", fit: 87, image: "https://test.hisale.com/images/interior-suggestions/palette-sage-views.png", colors: ["#f2f0e8", "#cbd2bf", "#7c9275", "#a88f72", "#ffffff"], description: "Tối giản mềm mại với xanh sage, vật liệu tự nhiên và nhiều khoảng thở.", features: "Mặt phẳng sạch, nội thất thấp, góc bo tròn và trang trí vừa đủ.", materials: "Gỗ sáng, vải thô, sơn mờ, gốm và cây xanh tự nhiên.", suitable: "Căn nhỏ đến trung bình, người yêu sự yên tĩnh và lối sống gọn gàng.", budget: "Trung bình; ưu tiên chất lượng hoàn thiện hơn số lượng đồ.", tip: "Không dùng quá nhiều sắc xanh; lấy kem và gỗ làm nền để tránh cảm giác lạnh." }, blue: { name: "Urban Blue", tag: "ĐÔ THỊ · CÁ TÍNH", fit: 82, image: "https://test.hisale.com/images/interior-suggestions/palette-blue-views.png", colors: ["#f5f3ec", "#b9ccd7", "#5c7e98", "#d6b684", "#3e4850"], description: "Tinh thần đô thị hiện đại với xanh bụi, đường nét rõ và điểm kim loại thanh mảnh.", features: "Tương phản có kiểm soát, đèn tuyến tính và các mảng hình học rõ ràng.", materials: "Gỗ tần bì, kính, kim loại đen mờ, đá quartz và vải dệt tông xanh.", suitable: "Người trẻ, căn hộ thành thị và không gian có ánh sáng tự nhiên tốt.", budget: "Trung bình đến cao tùy hệ đèn và chi tiết kim loại.", tip: "Dùng xanh làm điểm nhấn khoảng 20–30% diện tích màu, tránh phủ toàn bộ tường." }, mono: { name: "Contemporary Neutral", tag: "TRUNG TÍNH · TINH GỌN", fit: 92, image: "https://test.hisale.com/images/interior-suggestions/palette-mono-views.png", colors: ["#f5f5f2", "#d4d1cc", "#8c8c88", "#555b62", "#b79e84"], description: "Bảng màu greige và xám ấm tạo nền thanh lịch, bền xu hướng và dễ thay đổi decor.", features: "Hình khối dứt khoát, bề mặt phẳng và điểm nhấn nghệ thuật đơn sắc.", materials: "Đá vân nhẹ, kính, kim loại xám đen, gỗ trung tính và da/vải dệt.", suitable: "Nhiều loại diện tích, căn cho thuê cao cấp hoặc gia chủ thích sự bền vững.", budget: "Linh hoạt từ trung bình đến cao theo chất lượng bề mặt.", tip: "Kết hợp nhiều lớp chất liệu để bảng màu trung tính không trở nên đơn điệu." } }; const renderDesignStyle = (key) => { const style = designStyles[key]; const heroButton = document.getElementById("designStyleHeroButton"); heroButton.dataset.interiorImage = style.image; heroButton.dataset.interiorLabel = style.name; document.getElementById("designStyleHeroImage").src = style.image; document.getElementById("designStyleHeroImage").alt = `Phong cách ${style.name}`; document.getElementById("designStyleTag").textContent = style.tag; document.getElementById("designStyleName").textContent = style.name; document.getElementById("designStyleDescription").textContent = style.description; document.getElementById("designStyleFit").textContent = `Phù hợp ${style.fit}%`; document.getElementById("designStyleFeatures").textContent = style.features; document.getElementById("designStyleMaterials").textContent = style.materials; document.getElementById("designStyleSuitable").textContent = style.suitable; document.getElementById("designStyleBudget").textContent = style.budget; document.getElementById("designStyleTip").textContent = style.tip; const colors = document.getElementById("designStyleColors"); colors.replaceChildren(); style.colors.forEach((color) => { const swatch = document.createElement("i"); swatch.style.backgroundColor = color; swatch.title = color; colors.append(swatch); }); }; const showDesignStyleModal = () => { const active = designStyleModal.querySelector("[data-design-style].is-active"); renderDesignStyle(active.dataset.designStyle); designStyleModal.hidden = false; setModalOpen(); active.focus(); }; const hideDesignStyleModal = () => { designStyleModal.hidden = true; setModalOpen(); }; const showPlanningMapModal = () => { planningMapLoading.hidden = planningMapLoaded; if (!planningMapFrame.hasAttribute("src")) { planningMapFrame.src = planningMapFrame.dataset.src; } planningMapModal.hidden = false; setModalOpen(); closePlanningMapButton.focus(); }; const hidePlanningMapModal = () => { planningMapModal.hidden = true; setModalOpen(); }; const showConsultationV2Modal = () => { const consultationUrl = new URL(consultationV2Frame.dataset.src, window.location.href); consultationUrl.searchParams.set("new", "1"); consultationUrl.searchParams.set("_", Date.now().toString()); consultationV2Frame.src = consultationUrl.toString(); consultationV2Modal.hidden = false; setModalOpen(); closeConsultationV2Button.focus(); }; const hideConsultationV2Modal = () => { consultationV2Modal.hidden = true; setModalOpen(); }; document.querySelectorAll(".utility-card:not(.utility-card--guide):not(.utility-card--qr-share):not(.utility-card--statistics):not(.utility-card--calculator):not(.utility-card--quick-note):not(.utility-card--loan):not(.utility-card--consultation):not(.utility-card--consultation-v2):not(.utility-card--hisale-live):not(.utility-card--feng-shui):not(.utility-card--home-age):not(.utility-card--build-age):not(.utility-card--apartment-suggestion):not(.utility-card--viewing-date):not(.utility-card--deposit-date):not(.utility-card--move-in-date):not(.utility-card--other-feng-shui):not(.utility-card--lock-guide):not(.utility-card--training):not(.utility-card--legal):not(.utility-card--interior-suggestion):not(.utility-card--interior-reference):not(.utility-card--interior-cost):not(.utility-card--space-planning):not(.utility-card--material-catalog):not(.utility-card--design-style):not(.utility-card--planning-map)").forEach((card) => { card.setAttribute("aria-disabled", "true"); }); compareGuideCard.addEventListener("click", (event) => { event.preventDefault(); showCompareGuideModal(); }); document.querySelectorAll("[data-pro-locked]").forEach((card) => { card.addEventListener("click", (event) => { event.preventDefault(); event.stopImmediatePropagation(); alert(card.dataset.proLocked === "login" ? "Vui lòng đăng nhập để sử dụng tính năng Pro." : "Tính năng Pro chỉ dành cho CBNV của HomePlus"); }); }); consultationV2Card.addEventListener("click", (event) => { event.preventDefault(); if (consultationV2Card.hasAttribute("data-pro-locked")) return; showConsultationV2Modal(); }); if (new URLSearchParams(window.location.search).get("open") === "consultation-v2") { consultationV2Card.click(); } compareGuideStart.addEventListener("click", () => { const compareMenu = document.querySelector('[data-menu-action="compare-apartment"]'); if (compareMenu) window.setTimeout(() => compareMenu.click(), 0); }); quickShareGuideCard.addEventListener("click", (event) => { event.preventDefault(); showQuickShareGuideModal(); }); qrShareCard.addEventListener("click", (event) => { event.preventDefault(); showQrShareModal(); }); supportGuideCard.addEventListener("click", (event) => { event.preventDefault(); showSupportGuideModal(); }); legalGuideCards.forEach((card) => { card.addEventListener("click", (event) => { event.preventDefault(); showLegalGuideModal(card.dataset.legalSection || "project"); }); }); legalGuideModal.querySelectorAll("[data-legal-tab]").forEach((button) => { button.addEventListener("click", () => activateLegalSection(button.dataset.legalTab)); }); interiorSuggestionCard.addEventListener("click", (event) => { event.preventDefault(); showInteriorSuggestionModal(); }); interiorReferenceCard.addEventListener("click", (event) => { event.preventDefault(); showInteriorReferenceModal(); }); interiorReferenceModal.querySelectorAll("[data-interior-reference-filter]").forEach((button) => { button.addEventListener("click", () => filterInteriorReferences(button.dataset.interiorReferenceFilter)); }); interiorCostCard.addEventListener("click", (event) => { event.preventDefault(); showInteriorCostModal(); }); interiorCostForm.addEventListener("submit", (event) => { event.preventDefault(); if (interiorCostForm.reportValidity()) renderInteriorCost(); }); interiorCostForm.querySelectorAll("input, select").forEach((field) => { field.addEventListener("change", () => { if (interiorCostForm.checkValidity()) renderInteriorCost(); }); }); spacePlanningCard.addEventListener("click", (event) => { event.preventDefault(); showSpacePlanningModal(); }); spacePlanningForm.addEventListener("submit", (event) => { event.preventDefault(); if (spacePlanningForm.reportValidity()) renderSpacePlanning(); }); spacePlanningForm.querySelectorAll("input, select").forEach((field) => { field.addEventListener("change", () => { if (spacePlanningForm.checkValidity()) renderSpacePlanning(); }); }); materialCatalogCard.addEventListener("click", (event) => { event.preventDefault(); showMaterialCatalogModal(); }); materialCatalogModal.querySelectorAll("[data-material-group]").forEach((button) => { button.addEventListener("click", () => { activeMaterialGroup = button.dataset.materialGroup; materialCatalogModal.querySelectorAll("[data-material-group]").forEach((item) => item.classList.toggle("is-active", item === button)); renderMaterialCatalog(); }); }); document.getElementById("materialCatalogSearch").addEventListener("input", renderMaterialCatalog); document.getElementById("materialCatalogBudget").addEventListener("change", renderMaterialCatalog); designStyleCard.addEventListener("click", (event) => { event.preventDefault(); showDesignStyleModal(); }); designStyleModal.querySelectorAll("[data-design-style]").forEach((button) => { button.addEventListener("click", () => { designStyleModal.querySelectorAll("[data-design-style]").forEach((item) => item.classList.toggle("is-active", item === button)); renderDesignStyle(button.dataset.designStyle); }); }); planningMapCard.addEventListener("click", (event) => { event.preventDefault(); showPlanningMapModal(); }); planningMapFrame.addEventListener("load", () => { planningMapLoaded = true; planningMapLoading.hidden = true; }); interiorSuggestionForm.addEventListener("submit", (event) => { event.preventDefault(); if (!interiorSuggestionForm.reportValidity()) return; renderInteriorSuggestion(); document.getElementById("interiorSuggestionResult").scrollIntoView({ behavior: "smooth", block: "start" }); }); ["interiorArea", "interiorAge", "interiorResidents", "interiorPriority"].forEach((id) => { document.getElementById(id).addEventListener("change", () => { if (interiorSuggestionForm.checkValidity()) renderInteriorSuggestion(); }); }); interiorSuggestionForm.querySelectorAll('input[name="interiorPalette"]').forEach((input) => { input.addEventListener("change", renderInteriorSuggestion); }); document.querySelectorAll("[data-interior-image]").forEach((button) => { button.addEventListener("click", () => showInteriorLightbox(button)); }); calculatorCard.addEventListener("click", (event) => { event.preventDefault(); showCalculatorModal(); }); quickNoteCard.addEventListener("click", (event) => { event.preventDefault(); showQuickNoteModal(); }); loanCalculatorCard.addEventListener("click", (event) => { event.preventDefault(); showLoanCalculatorModal(); }); borrowingCapacityCard.addEventListener("click", (event) => { event.preventDefault(); showBorrowingCapacityModal(); }); borrowingCapacityForm.addEventListener("input", (event) => { if (event.target.id === "capacityIncome") { const value = readCapacityMoney(event.target); event.target.value = value ? value.toLocaleString("vi-VN") : ""; } calculateBorrowingCapacity(); }); borrowingCapacityForm.addEventListener("reset", (event) => { event.preventDefault(); borrowingCapacityForm.querySelectorAll("input").forEach((input) => { input.value = ""; }); document.getElementById("capacityCopyStatus").textContent = ""; calculateBorrowingCapacity(); document.getElementById("capacityIncome").focus(); }); document.getElementById("copyBorrowingCapacity").addEventListener("click", async () => { const text = `Khả năng vay tối đa: ${document.getElementById("capacityMaximum").textContent}\nTrả mỗi tháng: ${document.getElementById("capacityMonthly").textContent}\nTổng lãi: ${document.getElementById("capacityInterest").textContent} trong ${document.getElementById("capacityMonths").textContent} tháng`; const status = document.getElementById("capacityCopyStatus"); try { await navigator.clipboard.writeText(text); status.textContent = "Đã sao chép"; } catch (error) { status.textContent = "Không thể sao chép"; } }); consultationCard.addEventListener("click", (event) => { event.preventDefault(); showConsultationModal(); }); apartmentFengShuiCard.addEventListener("click", (event) => { event.preventDefault(); showApartmentFengShuiModal(); }); fengShuiCard.addEventListener("click", (event) => { event.preventDefault(); showFengShuiModal(); }); fengShuiForm.addEventListener("submit", (event) => { event.preventDefault(); const year = Number(document.getElementById("fengShuiBirthYear").value); const currentYear = new Date().getFullYear(); if (!Number.isInteger(year) || year < 1900 || year > currentYear) { const error = document.getElementById("fengShuiError"); error.textContent = `Vui lòng nhập năm sinh từ 1900 đến ${currentYear}.`; error.hidden = false; document.getElementById("fengShuiResults").hidden = true; return; } renderFengShuiResult(year, document.getElementById("fengShuiGender").value); }); document.getElementById("resetFengShui").addEventListener("click", () => { fengShuiForm.reset(); document.getElementById("fengShuiResults").hidden = true; document.getElementById("fengShuiError").hidden = true; document.getElementById("fengShuiDirectionGrid").replaceChildren(); document.getElementById("fengShuiBirthYear").focus(); }); homeAgeCard?.addEventListener("click", (event) => { event.preventDefault(); showHomeAgeModal(); }); homeAgeForm.addEventListener("submit", (event) => { event.preventDefault(); const birthYear = Number(document.getElementById("homeAgeBirthYear").value); const purchaseYear = Number(document.getElementById("homeAgePurchaseYear").value); const error = document.getElementById("homeAgeError"); if (!Number.isInteger(birthYear) || !Number.isInteger(purchaseYear) || birthYear < 1900 || purchaseYear <= birthYear || purchaseYear > new Date().getFullYear() + 50) { error.textContent = "Vui lòng kiểm tra lại năm sinh và năm dự định mua nhà."; error.hidden = false; document.getElementById("homeAgeResults").hidden = true; return; } renderHomeAgeResult(birthYear, purchaseYear); }); document.getElementById("resetHomeAge").addEventListener("click", () => { homeAgeForm.reset(); document.getElementById("homeAgePurchaseYear").value = new Date().getFullYear(); document.getElementById("homeAgeResults").hidden = true; document.getElementById("homeAgeError").hidden = true; document.getElementById("homeAgeCriteria").replaceChildren(); document.getElementById("homeAgeSuggestedYears").replaceChildren(); document.getElementById("homeAgeBirthYear").focus(); }); buildAgeCard?.addEventListener("click", (event) => { event.preventDefault(); showBuildAgeModal(); }); buildAgeForm.addEventListener("submit", (event) => { event.preventDefault(); const birthYear = Number(document.getElementById("buildAgeBirthYear").value); const buildYear = Number(document.getElementById("buildAgeYear").value); const error = document.getElementById("buildAgeError"); if (!Number.isInteger(birthYear) || !Number.isInteger(buildYear) || birthYear < 1900 || buildYear <= birthYear || buildYear > new Date().getFullYear() + 50) { error.textContent = "Vui lòng kiểm tra lại năm sinh gia chủ và năm dự kiến động thổ."; error.hidden = false; document.getElementById("buildAgeResults").hidden = true; return; } renderBuildAgeResult(birthYear, buildYear); }); document.getElementById("resetBuildAge").addEventListener("click", () => { buildAgeForm.reset(); document.getElementById("buildAgeYear").value = new Date().getFullYear(); document.getElementById("buildAgeResults").hidden = true; document.getElementById("buildAgeError").hidden = true; document.getElementById("buildAgeCriteria").replaceChildren(); document.getElementById("buildAgeSuggestedYears").replaceChildren(); document.getElementById("buildAgeBorrowYears").replaceChildren(); document.getElementById("buildAgeBirthYear").focus(); }); apartmentSuggestionCard?.addEventListener("click", (event) => { event.preventDefault(); showApartmentSuggestionModal(); }); apartmentSuggestionForm.addEventListener("submit", (event) => { event.preventDefault(); const birthYear = Number(document.getElementById("apartmentBirthYear").value); const error = document.getElementById("apartmentSuggestionError"); if (!Number.isInteger(birthYear) || birthYear < 1900 || birthYear > new Date().getFullYear()) { error.textContent = "Vui lòng nhập năm sinh hợp lệ."; error.hidden = false; document.getElementById("apartmentSuggestionResults").hidden = true; return; } renderApartmentSuggestion( birthYear, document.getElementById("apartmentGender").value, document.getElementById("apartmentPriority").value ); }); document.getElementById("resetApartmentSuggestion").addEventListener("click", () => { apartmentSuggestionForm.reset(); document.getElementById("apartmentSuggestionResults").hidden = true; document.getElementById("apartmentSuggestionError").hidden = true; document.getElementById("apartmentDirectionTags").replaceChildren(); document.getElementById("apartmentFloorTags").replaceChildren(); document.getElementById("apartmentPositionList").replaceChildren(); document.getElementById("apartmentBirthYear").focus(); }); viewingDateCard?.addEventListener("click", (event) => { event.preventDefault(); showViewingDateModal(); }); viewingDateForm.addEventListener("submit", (event) => { event.preventDefault(); const birthYear = Number(document.getElementById("viewingBirthYear").value); const startValue = document.getElementById("viewingStartDate").value; const startDate = new Date(`${startValue}T00:00:00`); const error = document.getElementById("viewingDateError"); if (!Number.isInteger(birthYear) || birthYear < 1900 || birthYear > new Date().getFullYear() || Number.isNaN(startDate.getTime())) { error.textContent = "Vui lòng nhập năm sinh và ngày bắt đầu hợp lệ."; error.hidden = false; document.getElementById("viewingDateResults").hidden = true; return; } renderViewingDates( birthYear, startDate, Number(document.getElementById("viewingRange").value), document.getElementById("viewingTimePreference").value ); }); document.getElementById("resetViewingDate").addEventListener("click", () => { viewingDateForm.reset(); document.getElementById("viewingStartDate").value = new Date().toISOString().slice(0, 10); document.getElementById("viewingDateResults").hidden = true; document.getElementById("viewingDateError").hidden = true; document.getElementById("viewingDateList").replaceChildren(); document.getElementById("viewingBirthYear").focus(); }); depositDateCard?.addEventListener("click", (event) => { event.preventDefault(); showDepositDateModal(); }); depositDateForm.addEventListener("submit", (event) => { event.preventDefault(); const birthYear = Number(document.getElementById("depositBirthYear").value); const startValue = document.getElementById("depositStartDate").value; const startDate = new Date(`${startValue}T00:00:00`); const error = document.getElementById("depositDateError"); if (!Number.isInteger(birthYear) || birthYear < 1900 || birthYear > new Date().getFullYear() || Number.isNaN(startDate.getTime())) { error.textContent = "Vui lòng nhập năm sinh và ngày bắt đầu hợp lệ."; error.hidden = false; document.getElementById("depositDateResults").hidden = true; return; } renderDepositDates(birthYear, startDate, Number(document.getElementById("depositRange").value), document.getElementById("depositType").value); }); document.getElementById("resetDepositDate").addEventListener("click", () => { depositDateForm.reset(); document.getElementById("depositStartDate").value = new Date().toISOString().slice(0, 10); document.getElementById("depositDateResults").hidden = true; document.getElementById("depositDateError").hidden = true; document.getElementById("depositDateList").replaceChildren(); document.querySelectorAll("#depositDateModal .deposit-safety-grid input").forEach((input) => { input.checked = false; }); document.getElementById("depositBirthYear").focus(); }); moveInDateCard?.addEventListener("click", (event) => { event.preventDefault(); showMoveInDateModal(); }); moveInDateForm.addEventListener("submit", (event) => { event.preventDefault(); const birthYear = Number(document.getElementById("moveInBirthYear").value); const startValue = document.getElementById("moveInStartDate").value; const startDate = new Date(`${startValue}T00:00:00`); const error = document.getElementById("moveInDateError"); if (!Number.isInteger(birthYear) || birthYear < 1900 || birthYear > new Date().getFullYear() || Number.isNaN(startDate.getTime())) { error.textContent = "Vui lòng nhập năm sinh và ngày bắt đầu hợp lệ."; error.hidden = false; document.getElementById("moveInDateResults").hidden = true; return; } renderMoveInDates(birthYear, startDate, Number(document.getElementById("moveInRange").value), document.getElementById("moveInPropertyType").value); }); document.getElementById("resetMoveInDate").addEventListener("click", () => { moveInDateForm.reset(); document.getElementById("moveInStartDate").value = new Date().toISOString().slice(0, 10); document.getElementById("moveInDateResults").hidden = true; document.getElementById("moveInDateError").hidden = true; document.getElementById("moveInDateList").replaceChildren(); document.getElementById("moveInPracticalList").replaceChildren(); document.getElementById("moveInBirthYear").focus(); }); otherFengShuiCard?.addEventListener("click", (event) => { event.preventDefault(); showOtherFengShuiModal(); }); otherFengShuiForm.addEventListener("submit", (event) => { event.preventDefault(); const birthYear = Number(document.getElementById("otherFengShuiBirthYear").value); const error = document.getElementById("otherFengShuiError"); if (!Number.isInteger(birthYear) || birthYear < 1900 || birthYear > new Date().getFullYear()) { error.textContent = "Vui lòng nhập năm sinh hợp lệ."; error.hidden = false; document.getElementById("otherFengShuiResults").hidden = true; return; } renderOtherFengShui(birthYear, document.getElementById("otherFengShuiGender").value, document.getElementById("otherFengShuiSpace").value); }); document.getElementById("resetOtherFengShui").addEventListener("click", () => { otherFengShuiForm.reset(); document.getElementById("otherFengShuiResults").hidden = true; document.getElementById("otherFengShuiError").hidden = true; document.getElementById("otherFengShuiColors").replaceChildren(); document.getElementById("otherFengShuiDirections").replaceChildren(); document.getElementById("otherFengShuiLayout").replaceChildren(); document.getElementById("otherFengShuiAvoid").replaceChildren(); document.getElementById("otherFengShuiBirthYear").focus(); }); lockGuideCard.addEventListener("click", (event) => { event.preventDefault(); showLockGuideModal(); }); lockGuideModal.querySelectorAll("[data-lock-tab]").forEach((button) => { button.addEventListener("click", () => activateLockGuideTab(button.dataset.lockTab)); }); trainingCards.forEach((card) => { card.addEventListener("click", (event) => { event.preventDefault(); showTrainingModal(card.dataset.trainingSection); if (card.dataset.trainingSection === "videos") { trainingModal.querySelector("[data-youtube-pagination]")?.showPage(1); } else if (card.dataset.trainingSection === "documents") { trainingModal.querySelector("[data-document-pagination]")?.showPage(1); } }); }); trainingModal.querySelectorAll("[data-training-tab]").forEach((button) => { button.addEventListener("click", () => { activateTrainingTab(button.dataset.trainingTab); if (button.dataset.trainingTab === "videos") { trainingModal.querySelector("[data-youtube-pagination]")?.showPage(1); } else if (button.dataset.trainingTab === "documents") { trainingModal.querySelector("[data-document-pagination]")?.showPage(1); } }); }); trainingModal.querySelectorAll("[data-training-lesson]").forEach((button) => { button.addEventListener("click", () => { const detail = trainingModal.querySelector(`[data-training-lesson-detail="${button.dataset.trainingLesson}"]`); const expanded = button.getAttribute("aria-expanded") === "true"; button.setAttribute("aria-expanded", expanded ? "false" : "true"); detail.hidden = expanded; }); }); trainingModal.querySelectorAll("[data-training-video]").forEach((videoLesson) => { videoLesson.querySelectorAll("[data-video-chapter]").forEach((chapter) => { chapter.addEventListener("click", () => { videoLesson.querySelectorAll("[data-video-chapter]").forEach((item) => item.classList.remove("is-active")); chapter.classList.add("is-active"); }); }); }); trainingModal.querySelectorAll("[data-youtube-pagination]").forEach((pagination) => { const cards = [...trainingModal.querySelectorAll("[data-youtube-card]")]; const pageSize = Number(pagination.dataset.pageSize) || 6; const totalPages = Math.max(1, Math.ceil(cards.length / pageSize)); const previous = pagination.querySelector("[data-youtube-page-prev]"); const next = pagination.querySelector("[data-youtube-page-next]"); const numbers = pagination.querySelector("[data-youtube-page-numbers]"); const status = pagination.querySelector("[data-youtube-page-status]"); let currentPage = 1; const showPage = (page) => { currentPage = Math.min(totalPages, Math.max(1, page)); const first = (currentPage - 1) * pageSize; cards.forEach((card, cardIndex) => { card.hidden = cardIndex < first || cardIndex >= first + pageSize; }); previous.disabled = currentPage === 1; next.disabled = currentPage === totalPages; numbers.querySelectorAll("button").forEach((button) => { const active = Number(button.dataset.youtubePage) === currentPage; button.classList.toggle("is-active", active); button.setAttribute("aria-current", active ? "page" : "false"); }); status.textContent = `Trang ${currentPage}/${totalPages} · ${cards.length} video`; }; for (let page = 1; page <= totalPages; page += 1) { const button = document.createElement("button"); button.type = "button"; button.dataset.youtubePage = page; button.textContent = page; button.setAttribute("aria-label", `Đến trang ${page}`); button.addEventListener("click", () => showPage(page)); numbers.appendChild(button); } previous.addEventListener("click", () => showPage(currentPage - 1)); next.addEventListener("click", () => showPage(currentPage + 1)); pagination.showPage = showPage; showPage(1); }); trainingModal.querySelectorAll("[data-document-pagination]").forEach((pagination) => { const cards = [...trainingModal.querySelectorAll("[data-document-card]")]; const pageSize = Number(pagination.dataset.pageSize) || 6; const totalPages = Math.max(1, Math.ceil(cards.length / pageSize)); const previous = pagination.querySelector("[data-document-page-prev]"); const next = pagination.querySelector("[data-document-page-next]"); const numbers = pagination.querySelector("[data-document-page-numbers]"); const status = pagination.querySelector("[data-document-page-status]"); let currentPage = 1; const showPage = (page) => { currentPage = Math.min(totalPages, Math.max(1, page)); const first = (currentPage - 1) * pageSize; cards.forEach((card, cardIndex) => { card.hidden = cardIndex < first || cardIndex >= first + pageSize; }); previous.disabled = currentPage === 1; next.disabled = currentPage === totalPages; numbers.querySelectorAll("button").forEach((button) => { const active = Number(button.dataset.documentPage) === currentPage; button.classList.toggle("is-active", active); button.setAttribute("aria-current", active ? "page" : "false"); }); status.textContent = `Trang ${currentPage}/${totalPages} · ${cards.length} tài liệu`; }; for (let page = 1; page <= totalPages; page += 1) { const button = document.createElement("button"); button.type = "button"; button.dataset.documentPage = page; button.textContent = page; button.setAttribute("aria-label", `Đến trang ${page}`); button.addEventListener("click", () => showPage(page)); numbers.appendChild(button); } previous.addEventListener("click", () => showPage(currentPage - 1)); next.addEventListener("click", () => showPage(currentPage + 1)); pagination.showPage = showPage; showPage(1); }); trainingModal.querySelectorAll("[data-training-quiz]").forEach((quiz) => { quiz.addEventListener("submit", (event) => { event.preventDefault(); const questions = [...quiz.querySelectorAll("fieldset[data-answer]:not([hidden])")]; const unanswered = questions.filter((question) => !question.querySelector("input:checked")); if (unanswered.length) { quiz.querySelector(".training-quiz__result").textContent = `Còn ${unanswered.length} câu chưa trả lời`; unanswered[0].scrollIntoView({ behavior: "smooth", block: "center" }); return; } let score = 0; questions.forEach((question) => { const selected = Number(question.querySelector("input:checked").value); const correct = selected === Number(question.dataset.answer); if (correct) score += 1; question.classList.toggle("is-correct", correct); question.classList.toggle("is-wrong", !correct); const explanation = question.querySelector(".training-quiz__explanation"); explanation.textContent = `${correct ? "Đúng." : "Chưa đúng."} ${question.dataset.explanation}`; explanation.hidden = false; }); const percent = Math.round((score / questions.length) * 100); quiz.querySelector(".training-quiz__result").textContent = `${score}/${questions.length} câu đúng · ${percent}%`; }); quiz.addEventListener("reset", () => { window.setTimeout(() => { quiz.querySelectorAll("fieldset").forEach((question) => { question.classList.remove("is-correct", "is-wrong"); question.querySelector(".training-quiz__explanation").hidden = true; }); quiz.querySelector(".training-quiz__result").textContent = "10 câu ngẫu nhiên / 20 câu"; }, 0); }); }); const formatCalculatorValue = (value) => { if (value === "Lỗi") return value; const [integer, decimal] = String(value).split("."); const formattedInteger = Number(integer).toLocaleString("vi-VN"); return decimal !== undefined ? `${formattedInteger},${decimal}` : formattedInteger; }; const updateCalculatorDisplay = () => { calculatorDisplay.textContent = formatCalculatorValue(calculatorValue); calculatorOperation.textContent = calculatorPreviousValue !== null && calculatorOperator ? `${formatCalculatorValue(calculatorPreviousValue)} ${calculatorOperator}` : "\u00a0"; }; const clearCalculator = () => { calculatorValue = "0"; calculatorPreviousValue = null; calculatorOperator = null; calculatorWaitingForValue = false; updateCalculatorDisplay(); }; const inputCalculatorNumber = (number) => { if (calculatorValue === "Lỗi" || calculatorWaitingForValue) { calculatorValue = number === "." ? "0." : number; calculatorWaitingForValue = false; } else if (number === ".") { if (!calculatorValue.includes(".")) calculatorValue += "."; } else { calculatorValue = calculatorValue === "0" ? number : calculatorValue + number; } updateCalculatorDisplay(); }; const calculateResult = (left, right, operator) => { if (operator === "+") return left + right; if (operator === "−") return left - right; if (operator === "×") return left * right; if (operator === "÷") return right === 0 ? null : left / right; return right; }; const chooseCalculatorOperator = (nextOperator) => { const inputValue = Number(calculatorValue); if (calculatorValue === "Lỗi") return; if (calculatorOperator && !calculatorWaitingForValue) { const result = calculateResult(Number(calculatorPreviousValue), inputValue, calculatorOperator); if (result === null || !Number.isFinite(result)) { calculatorValue = "Lỗi"; calculatorPreviousValue = null; calculatorOperator = null; calculatorWaitingForValue = true; updateCalculatorDisplay(); return; } calculatorValue = String(Number(result.toPrecision(12))); calculatorPreviousValue = calculatorValue; } else { calculatorPreviousValue = calculatorValue; } calculatorOperator = nextOperator; calculatorWaitingForValue = true; updateCalculatorDisplay(); }; const finishCalculator = () => { if (!calculatorOperator || calculatorPreviousValue === null || calculatorValue === "Lỗi") return; const result = calculateResult(Number(calculatorPreviousValue), Number(calculatorValue), calculatorOperator); calculatorValue = result === null || !Number.isFinite(result) ? "Lỗi" : String(Number(result.toPrecision(12))); calculatorPreviousValue = null; calculatorOperator = null; calculatorWaitingForValue = true; updateCalculatorDisplay(); }; calculatorModal.querySelector(".calculator__keys").addEventListener("click", (event) => { const button = event.target.closest(".calculator__key"); if (!button) return; if (button.dataset.number !== undefined) inputCalculatorNumber(button.dataset.number); if (button.dataset.operator) chooseCalculatorOperator(button.dataset.operator); if (button.dataset.action === "equals") finishCalculator(); if (button.dataset.action === "clear") clearCalculator(); if (button.dataset.action === "delete" && !calculatorWaitingForValue && calculatorValue !== "Lỗi") { calculatorValue = calculatorValue.length > 1 ? calculatorValue.slice(0, -1) : "0"; updateCalculatorDisplay(); } }); [quickNoteName, quickNoteContent].forEach((field) => field.addEventListener("input", () => { updateQuickNoteCount(); quickNoteChanged = true; setQuickNoteStatus("Có thay đổi chưa lưu"); })); document.getElementById("saveQuickNote").addEventListener("click", saveQuickNote); document.getElementById("newQuickNote").addEventListener("click", () => { if (quickNoteChanged && (quickNoteName.value.trim() || quickNoteContent.value.trim())) saveQuickNote(); openQuickNote(null); quickNoteName.focus(); }); quickNoteList.addEventListener("click", (event) => { const item = event.target.closest(".quick-note__item"); if (!item || item.dataset.noteId === activeQuickNoteId) return; if (quickNoteChanged && (quickNoteName.value.trim() || quickNoteContent.value.trim())) saveQuickNote(); openQuickNote(item.dataset.noteId); quickNoteContent.focus(); }); document.getElementById("clearQuickNote").addEventListener("click", () => { if (!activeQuickNoteId) { quickNoteName.value = ""; quickNoteContent.value = ""; quickNoteChanged = false; updateQuickNoteCount(); return; } if (!window.confirm("Anh/chị có chắc muốn xóa ghi chú này?")) return; quickNotes = quickNotes.filter((note) => note.id !== activeQuickNoteId); persistQuickNotes(); openQuickNote(quickNotes[0]?.id || null); setQuickNoteStatus("Đã xóa ghi chú"); }); document.getElementById("copyQuickNote").addEventListener("click", async () => { if (!quickNoteContent.value.trim()) { setQuickNoteStatus("Chưa có nội dung để sao chép"); return; } try { await navigator.clipboard.writeText(quickNoteContent.value); setQuickNoteStatus("Đã sao chép ghi chú"); } catch (error) { quickNoteContent.select(); document.execCommand("copy"); setQuickNoteStatus("Đã sao chép ghi chú"); } }); loanAmountInput.addEventListener("input", () => { const value = normalizeLoanAmount(); loanAmountInput.value = value ? value.toLocaleString("vi-VN") : ""; }); document.getElementById("loanCalculatorForm").addEventListener("submit", (event) => { event.preventDefault(); calculateLoan(); }); document.getElementById("exportLoanExcel").addEventListener("click", exportLoanExcel); consultationForm.querySelectorAll('input[inputmode="numeric"]').forEach((input) => { input.addEventListener("input", () => { const value = Number(input.value.replace(/[^\d]/g, "")); input.value = value ? value.toLocaleString("vi-VN") : ""; }); }); consultationForm.addEventListener("input", () => { consultationStatus.textContent = "Có thay đổi chưa lưu"; }); consultationForm.addEventListener("submit", (event) => { event.preventDefault(); if (consultationForm.reportValidity()) saveConsultationDraft(); }); document.getElementById("resetConsultation").addEventListener("click", () => { if (!window.confirm("Anh/chị có chắc muốn xóa toàn bộ nội dung phiếu?")) return; consultationForm.reset(); localStorage.removeItem(consultationStorageKey); consultationStatus.textContent = "Đã xóa nội dung phiếu"; consultationForm.elements.customerName.focus(); }); document.getElementById("copyConsultation").addEventListener("click", async () => { if (!consultationForm.elements.customerName.value.trim() && !consultationForm.elements.phone.value.trim()) { consultationStatus.textContent = "Vui lòng nhập thông tin khách hàng trước"; return; } try { await navigator.clipboard.writeText(buildConsultationText()); consultationStatus.textContent = "Đã sao chép nội dung phiếu"; } catch (error) { consultationStatus.textContent = "Trình duyệt không cho phép sao chép"; } }); document.getElementById("printConsultation").addEventListener("click", () => { if (!consultationForm.reportValidity()) return; window.print(); }); closeCompareGuideButtons.forEach((button) => button.addEventListener("click", hideCompareGuideModal)); compareGuideModal.querySelector(".development-modal__backdrop").addEventListener("click", hideCompareGuideModal); closeQuickShareGuideButtons.forEach((button) => button.addEventListener("click", hideQuickShareGuideModal)); quickShareGuideModal.querySelector(".development-modal__backdrop").addEventListener("click", hideQuickShareGuideModal); closeQrShareButtons.forEach((button) => button.addEventListener("click", hideQrShareModal)); qrShareModal.querySelector(".development-modal__backdrop").addEventListener("click", hideQrShareModal); closeSupportGuideButtons.forEach((button) => button.addEventListener("click", hideSupportGuideModal)); supportGuideModal.querySelector(".development-modal__backdrop").addEventListener("click", hideSupportGuideModal); closeCalculatorButton.addEventListener("click", hideCalculatorModal); calculatorModal.querySelector(".development-modal__backdrop").addEventListener("click", hideCalculatorModal); closeQuickNoteButton.addEventListener("click", hideQuickNoteModal); quickNoteModal.querySelector(".development-modal__backdrop").addEventListener("click", hideQuickNoteModal); closeLoanCalculatorButton.addEventListener("click", hideLoanCalculatorModal); loanCalculatorModal.querySelector(".development-modal__backdrop").addEventListener("click", hideLoanCalculatorModal); closeBorrowingCapacityButton.addEventListener("click", hideBorrowingCapacityModal); borrowingCapacityModal.querySelector(".development-modal__backdrop").addEventListener("click", hideBorrowingCapacityModal); closeConsultationButton.addEventListener("click", hideConsultationModal); consultationModal.querySelector(".development-modal__backdrop").addEventListener("click", hideConsultationModal); closeApartmentFengShuiButton.addEventListener("click", hideApartmentFengShuiModal); apartmentFengShuiModal.querySelector(".development-modal__backdrop").addEventListener("click", hideApartmentFengShuiModal); closeFengShuiButton.addEventListener("click", hideFengShuiModal); fengShuiModal.querySelector(".development-modal__backdrop").addEventListener("click", hideFengShuiModal); closeHomeAgeButton.addEventListener("click", hideHomeAgeModal); homeAgeModal.querySelector(".development-modal__backdrop").addEventListener("click", hideHomeAgeModal); closeBuildAgeButton.addEventListener("click", hideBuildAgeModal); buildAgeModal.querySelector(".development-modal__backdrop").addEventListener("click", hideBuildAgeModal); closeApartmentSuggestionButton.addEventListener("click", hideApartmentSuggestionModal); apartmentSuggestionModal.querySelector(".development-modal__backdrop").addEventListener("click", hideApartmentSuggestionModal); closeViewingDateButton.addEventListener("click", hideViewingDateModal); viewingDateModal.querySelector(".development-modal__backdrop").addEventListener("click", hideViewingDateModal); closeDepositDateButton.addEventListener("click", hideDepositDateModal); depositDateModal.querySelector(".development-modal__backdrop").addEventListener("click", hideDepositDateModal); closeMoveInDateButton.addEventListener("click", hideMoveInDateModal); moveInDateModal.querySelector(".development-modal__backdrop").addEventListener("click", hideMoveInDateModal); closeOtherFengShuiButton.addEventListener("click", hideOtherFengShuiModal); otherFengShuiModal.querySelector(".development-modal__backdrop").addEventListener("click", hideOtherFengShuiModal); closeLockGuideButton.addEventListener("click", hideLockGuideModal); lockGuideModal.querySelector(".development-modal__backdrop").addEventListener("click", hideLockGuideModal); closeTrainingButton.addEventListener("click", hideTrainingModal); trainingModal.querySelector(".development-modal__backdrop").addEventListener("click", hideTrainingModal); closeLegalGuideButton.addEventListener("click", hideLegalGuideModal); legalGuideModal.querySelector(".development-modal__backdrop").addEventListener("click", hideLegalGuideModal); closeInteriorSuggestionButton.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); hideInteriorSuggestionModal(); }); footerCloseInteriorSuggestionButton.addEventListener("click", hideInteriorSuggestionModal); interiorSuggestionModal.querySelector(".development-modal__backdrop").addEventListener("click", hideInteriorSuggestionModal); closeInteriorLightboxButton.addEventListener("click", hideInteriorLightbox); interiorLightbox.querySelector(".development-modal__backdrop").addEventListener("click", hideInteriorLightbox); closeInteriorReferenceButton.addEventListener("click", hideInteriorReferenceModal); interiorReferenceModal.querySelector(".development-modal__backdrop").addEventListener("click", hideInteriorReferenceModal); closeInteriorCostButton.addEventListener("click", hideInteriorCostModal); interiorCostModal.querySelector(".development-modal__backdrop").addEventListener("click", hideInteriorCostModal); closeSpacePlanningButton.addEventListener("click", hideSpacePlanningModal); spacePlanningModal.querySelector(".development-modal__backdrop").addEventListener("click", hideSpacePlanningModal); closeMaterialCatalogButton.addEventListener("click", hideMaterialCatalogModal); materialCatalogModal.querySelector(".development-modal__backdrop").addEventListener("click", hideMaterialCatalogModal); closeDesignStyleButton.addEventListener("click", hideDesignStyleModal); designStyleModal.querySelector(".development-modal__backdrop").addEventListener("click", hideDesignStyleModal); closePlanningMapButton.addEventListener("click", hidePlanningMapModal); planningMapModal.querySelector(".development-modal__backdrop").addEventListener("click", hidePlanningMapModal); closeConsultationV2Button.addEventListener("click", hideConsultationV2Modal); consultationV2Modal.querySelector(".development-modal__backdrop").addEventListener("click", hideConsultationV2Modal); document.addEventListener("keydown", (event) => { if (event.key === "Escape" && !compareGuideModal.hidden) hideCompareGuideModal(); if (event.key === "Escape" && !consultationV2Modal.hidden) hideConsultationV2Modal(); if (event.key === "Escape" && !quickShareGuideModal.hidden) hideQuickShareGuideModal(); if (event.key === "Escape" && !qrShareModal.hidden) hideQrShareModal(); if (event.key === "Escape" && !supportGuideModal.hidden) hideSupportGuideModal(); if (event.key === "Escape" && !calculatorModal.hidden) hideCalculatorModal(); if (event.key === "Escape" && !quickNoteModal.hidden) hideQuickNoteModal(); if (event.key === "Escape" && !loanCalculatorModal.hidden) hideLoanCalculatorModal(); if (event.key === "Escape" && !borrowingCapacityModal.hidden) hideBorrowingCapacityModal(); if (event.key === "Escape" && !consultationModal.hidden) hideConsultationModal(); if (event.key === "Escape" && !apartmentFengShuiModal.hidden) hideApartmentFengShuiModal(); if (event.key === "Escape" && !fengShuiModal.hidden) hideFengShuiModal(); if (event.key === "Escape" && !homeAgeModal.hidden) hideHomeAgeModal(); if (event.key === "Escape" && !buildAgeModal.hidden) hideBuildAgeModal(); if (event.key === "Escape" && !apartmentSuggestionModal.hidden) hideApartmentSuggestionModal(); if (event.key === "Escape" && !viewingDateModal.hidden) hideViewingDateModal(); if (event.key === "Escape" && !depositDateModal.hidden) hideDepositDateModal(); if (event.key === "Escape" && !moveInDateModal.hidden) hideMoveInDateModal(); if (event.key === "Escape" && !otherFengShuiModal.hidden) hideOtherFengShuiModal(); if (event.key === "Escape" && !lockGuideModal.hidden) hideLockGuideModal(); if (event.key === "Escape" && !trainingModal.hidden) hideTrainingModal(); if (event.key === "Escape" && !legalGuideModal.hidden) hideLegalGuideModal(); if (event.key === "Escape" && !interiorLightbox.hidden) hideInteriorLightbox(); else if (event.key === "Escape" && !interiorSuggestionModal.hidden) hideInteriorSuggestionModal(); else if (event.key === "Escape" && !interiorReferenceModal.hidden) hideInteriorReferenceModal(); else if (event.key === "Escape" && !interiorCostModal.hidden) hideInteriorCostModal(); else if (event.key === "Escape" && !spacePlanningModal.hidden) hideSpacePlanningModal(); else if (event.key === "Escape" && !materialCatalogModal.hidden) hideMaterialCatalogModal(); else if (event.key === "Escape" && !designStyleModal.hidden) hideDesignStyleModal(); else if (event.key === "Escape" && !planningMapModal.hidden) hidePlanningMapModal(); if (!quickNoteModal.hidden) { if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "s") { event.preventDefault(); saveQuickNote(); } return; } if (calculatorModal.hidden) return; if (/^\d$/.test(event.key)) inputCalculatorNumber(event.key); if (event.key === "." || event.key === ",") inputCalculatorNumber("."); if (event.key === "+") chooseCalculatorOperator("+"); if (event.key === "-") chooseCalculatorOperator("−"); if (event.key === "*") chooseCalculatorOperator("×"); if (event.key === "/") { event.preventDefault(); chooseCalculatorOperator("÷"); } if (event.key === "Enter" || event.key === "=") finishCalculator(); if (event.key === "Backspace" && !calculatorWaitingForValue && calculatorValue !== "Lỗi") { calculatorValue = calculatorValue.length > 1 ? calculatorValue.slice(0, -1) : "0"; updateCalculatorDisplay(); } if (event.key === "Delete") clearCalculator(); });
zalo
//