let syncQueue = JSON.parse(localStorage.getItem('syncQueue') || '[]');
window.addEventListener('online', function() {
console.log('🟢 กลับมาออนไลน์แล้ว');
showToast('🟢 กลับมาออนไลน์ กำลังซิงค์ข้อมูล...', 'info');
processSyncQueue();
});
window.addEventListener('offline', function() {
console.log('🔴 ออฟไลน์');
showToast('🔴 กำลังทำงานในโหมดออฟไลน์', 'info');
});
function addToSyncQueue(data, action) {
syncQueue.push({
id: data.id || `temp_${Date.now()}`,
data: data,
action: action,
timestamp: Date.now()
});
localStorage.setItem('syncQueue', JSON.stringify(syncQueue));
console.log(`📦 เพิ่มเข้าคิว: ${action}`, data);
}
async function processSyncQueue() {
if (!navigator.onLine || !isLoggedIn) {
console.log('⏳ รอซิงค์... (ไม่มีเน็ตหรือยังไม่ login)');
return;
}
if (syncQueue.length === 0) {
console.log('✅ ไม่มีคิวที่ต้องซิงค์');
return;
}
showToast(`🔄 กำลังซิงค์ ${syncQueue.length} รายการ...`, 'info');
let success = 0;
let failed = 0;
let newQueue = [];
for (const item of syncQueue) {
try {
if (item.action === 'create') {
await saveTransactionToBackend(item.data);
} else if (item.action === 'update') {
await updateTransactionInBackend(item.data);
} else if (item.action === 'delete') {
await deleteTransactionFromBackend(item.data.id);
}
success++;
} catch (error) {
console.error('Sync failed:', error);
failed++;
newQueue.push(item);
}
}
syncQueue = newQueue;
localStorage.setItem('syncQueue', JSON.stringify(syncQueue));
if (failed === 0) {
showToast(`✅ ซิงค์สำเร็จ ${success} รายการ`, 'success');
} else {
showToast(`⚠️ ซิงค์สำเร็จ ${success} รายการ, ล้มเหลว ${failed} รายการ`, 'info');
}
}
const defaultCategories = {
income: [{ id: 'salary', label: 'เงินเดือน', icon: '💰' }, { id: 'bonus', label: 'โบนัส', icon: '🎁' }, { id: 'invest_inc', label: 'กำไรลงทุน', icon: '💹' }, { id: 'other_inc', label: 'อื่นๆ', icon: '🏦' }],
spending: [{ id: 'eat', label: 'กิน', icon: '🍱', default: null }, { id: 'fuel', label: 'น้ำมัน', icon: '⛽', default: null }, { id: 'social', label: 'สังคม', icon: '🤝', default: null }, { id: 'family', label: 'ครอบครัว', icon: '👨👩👧👦', default: null }, { id: 'supplies', label: 'ของใช้', icon: '🧺', default: null }, { id: 'ent', label: 'สิ่งบันเทิง', icon: '🎬', default: null }, { id: 'travel', label: 'ท่องเที่ยว', icon: '✈️', default: null }, { id: 'health', label: 'สุขภาพ', icon: '🏥', default: null }, { id: 'car', label: 'รถยนต์', icon: '🚗', default: null }],
investment: [{ id: 'kid_saving', label: 'เงินเก็บลูก', icon: '👶', default: null }, { id: 'short_res', label: 'สำรองระยะสั้น', icon: '🛡️', default: null }, { id: 'invest_prep', label: 'เก็บเตรียมลงทุน', icon: '💎', default: null }],
};
const updateLogs = [
{
date: "2 Mar 2026",
version: "1.0.3",
title: "Update & Fixes",
changes: [
"1.แก้ไขในส่วน javascript ในเรื่อง ขนาด Font ในช่อง Calendargrid เกินเปลี่ยนจากแสดงตัวเลขเป็น จุด เพื่อให้แสดงผลได้ดีขึ้นใน Mobile",
"2.แก้บัคเกี่ยวกับปัญหา ไม่แสดงประวัติรายการ ในเดือนก่อนหน้า เมื่อกดดูประวัติในเดือนนั้นๆ ใน Mobile&Desktop",
],
icon: "📝"
},
{
date: "3 Mar 2026",
version: "1.0.4",
title: "Update & Fixes",
changes: [
"1.แก้ไข Edit account ให้สามารถ แก้ไขปรับยอดคงเหลือของ บัญชีได้",
"2.ปรับเพิ่มหมวดหมู่ รายรับ ในหน้า page รายปี และปรับให้แสดงเฉพาะ หมวดหมู่ที่มียอดเท่านั้น",
],
icon: "📝"
}
,
{
date: "4 Mar 2026",
version: "1.0.5",
title: "Update & Fixes",
changes: [
"1.ในหน้า page รายปี เพิ่มแก้ไขให้ สามารถกดดูรายการ Transaction ของหมวดนั้นๆในเดือนนั้นๆได้ (เฉพาะหมวดหมู่ที่มียอดเท่านั้น)",
],
icon: "📝"
}
,
{
date: "10 Mar 2026",
version: "1.0.6",
title: "Update & Fixes",
changes: [
"1.แก้ไขการแสดง TAG ในหน้า กรอกข้อมูล [รับ/จ่าย] ให้แสดงได้สูงสุด 15 TAG",
"2.ปรับปรุง Layout ในหน้า [รับ/จ่าย],จัดการหนี้ ให้ดูดีขึ้นใน Mobile mode",
],
icon: "📝"
}
,
{
date: "12 Mar 2026",
version: "1.0.7",
title: "Update & Fixes",
changes: [
"1.เพิ่มระบบ Login",
],
icon: "📝"
}
]
function openUpdateLog() {
console.log("📝 เปิดหน้า Log Update");
if (isMobile() && sideMenuOpen) {
closeMobileSideMenu();
}
const sortedLogs = [...updateLogs].sort((a, b) => {
return new Date(b.date) - new Date(a.date);
});
const logHTML = sortedLogs.map(log => `
📈 สรุปภาพรวม
รวมรายการทั้งหมด:
${data.transactionCount} รายการ
รายจ่ายทั้งหมด:
${data.expenseTransactionCount} รายการ
รายการที่มี TAG:
${data.taggedTransactionCount} รายการ
รายรับรวม
฿${data.income.toLocaleString()}
รายจ่ายรวม
฿${data.expense.toLocaleString()}
ยอดคงเหลือ
฿${data.balance.toLocaleString()}
อัตราการลงทุน
${data.investmentRate}%
${data.savingRate >= 20 ?
'ดีเยี่ยม' :
data.savingRate >= 10 ?
'ปานกลาง' :
'ต้องปรับปรุง'
}
${data.categoryPieData && data.categoryPieData.labels.length > 0 ? `
🏷️ หมวดหมู่ทั้งหมดที่ใช้จ่าย
แสดงทั้งหมด ${data.categoryPieData.labels.length} หมวดหมู่ (เรียงตามมูลค่ารวมจากมากไปน้อย)
${createPieChartHTML(data.categoryPieData, 'สรุปหมวดหมู่ที่ใช้จ่าย')}
ตารางรายละเอียด
| หมวดหมู่ |
จำนวนเงิน |
จำนวนรายการ |
สัดส่วนต่อรายจ่ายทั้งหมด |
${data.topCategories.map(cat => `
| ${cat.icon} ${cat.category} |
฿${cat.amount.toLocaleString()} |
${cat.count} รายการ |
${cat.percentage.toFixed(1)}% |
`).join('')}
` : '
🏷️ หมวดหมู่ที่ใช้จ่าย
ไม่มีรายการรายจ่ายในหมวดหมู่สำหรับเดือนนี้
'}
${data.tagPieData && data.tagPieData.labels.length > 0 ? `
🏷️ TAG ที่ใช้ทั้งหมด
แสดงทั้งหมด ${data.tagPieData.labels.length} TAG (เรียงตามมูลค่ารวมจากมากไปน้อย)
${createPieChartHTML(data.tagPieData, 'สรุป TAG ที่ใช้')}
ตารางรายละเอียด
| TAG |
จำนวนเงิน |
จำนวนรายการ |
สัดส่วนต่อรายจ่ายทั้งหมด |
${data.topTags.map(tag => `
| #${tag.tag} |
฿${tag.amount.toLocaleString()} |
${tag.count} รายการ |
${tag.percentage.toFixed(1)}% |
`).join('')}
` : '
🏷️ TAG ที่ใช้
ไม่มีรายการที่ใช้ TAG สำหรับเดือนนี้
'}
🏦 ยอดคงเหลือบัญชี
| บัญชี |
ยอดคงเหลือ |
สถานะ |
${data.accounts.map(acc => `
| ${acc.icon} ${acc.name} |
฿${acc.balance.toLocaleString()} |
${acc.balance >= 0 ?
'ปกติ' :
'ติดลบ'
}
|
`).join('')}
${data.debt && data.debt.debtCount > 0 ? createDebtReportHTML(data.debt) : ''}
💡 ข้อเสนอแนะ
${getRecommendations(data)}
`;
}
function getRecommendations(data) {
const recommendations = [];
if (data.savingRate < 0) {
recommendations.push("🚨
คุณใช้งจ่ายเกินรายได้ ควรควบคุมรายจ่ายหรือหารายได้เพิ่ม");
}
if (data.savingRate < 20) {
recommendations.push("💰
อัตราการออมต่ำกว่าเป้าหมาย พยายามออมให้ได้อย่างน้อย 20% ของรายได้");
}
if (data.topCategories.length > 0) {
data.topCategories.forEach(cat => {
const percentage = parseFloat(cat.percentage);
if (percentage > 30) {
recommendations.push(`📊
${cat.category} ใช้งบประมาณ ${percentage.toFixed(1)}% ของรายจ่ายทั้งหมด พิจารณาลดค่าใช้จ่ายในหมวดนี้`);
}
});
}
if (data.topTags.length > 0) {
data.topTags.forEach(tag => {
const percentage = parseFloat(tag.percentage);
if (percentage > 20) {
recommendations.push(`🏷️
#${tag.tag} ใช้งบประมาณ ${percentage.toFixed(1)}% ของรายจ่ายทั้งหมด ควรทบทวนการใช้จ่ายใน TAG นี้`);
}
});
}
if (data.expenseTransactionCount === 0) {
recommendations.push("📝
ไม่มีรายการรายจ่ายในเดือนนี้ ตรวจสอบว่าบันทึกรายจ่ายครบถ้วนหรือไม่");
}
if (recommendations.length === 0) {
recommendations.push("✅
การเงินอยู่ในเกณฑ์ดี รักษาวินัยทางการเงินอย่างนี้ต่อไป");
if (data.taggedTransactionCount === 0) {
recommendations.push("💡
เพิ่มการใช้ TAG เพื่อจัดกลุ่มรายจ่ายให้ละเอียดขึ้น");
}
}
return recommendations.map(rec => `
${rec}
`).join('');
}
function openDateRangeModal() {
document.getElementById('reportStartDate').value = '';
document.getElementById('reportEndDate').value = '';
populateReportAccountSelect();
document.getElementById('reportAccountSelect').value = reportDateRange.accountId || 'all';
document.getElementById('selectedRangeDisplay').classList.add('hidden');
setupDateInputListeners();
document.getElementById('dateRangeModal').classList.remove('hidden');
setTimeout(() => {
document.getElementById('reportStartDate').focus();
}, 300);
}
function populateReportAccountSelect() {
const select = document.getElementById('reportAccountSelect');
let optionsHTML = '
';
accounts.forEach(acc => {
const balance = getAccountBalance(acc.id);
optionsHTML += `
`;
});
select.innerHTML = optionsHTML;
if (reportDateRange.accountId) {
select.value = reportDateRange.accountId;
}
}
function setupDateInputListeners() {
const startDateInput = document.getElementById('reportStartDate');
const endDateInput = document.getElementById('reportEndDate');
startDateInput.removeEventListener('change', handleDateChange);
endDateInput.removeEventListener('change', handleDateChange);
startDateInput.addEventListener('change', handleDateChange);
endDateInput.addEventListener('change', handleDateChange);
}
function handleDateChange() {
const startDate = document.getElementById('reportStartDate').value;
const endDate = document.getElementById('reportEndDate').value;
if (startDate || endDate) {
updateDateRangeDisplay();
}
}
function closeDateRangeModal() {
document.getElementById('dateRangeModal').classList.add('hidden');
}
function formatDateForInput(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
function formatDateForDisplay(date) {
const options = { day: 'numeric', month: 'short', year: 'numeric' };
return new Date(date).toLocaleDateString('th-TH', options);
}
function setDateRange(rangeType) {
const today = new Date();
let startDate = new Date();
let endDate = new Date();
switch(rangeType) {
case 'thisMonth':
startDate = new Date(today.getFullYear(), today.getMonth(), 1);
endDate = new Date(today.getFullYear(), today.getMonth() + 1, 0);
break;
case 'lastMonth':
startDate = new Date(today.getFullYear(), today.getMonth() - 1, 1);
endDate = new Date(today.getFullYear(), today.getMonth(), 0);
break;
case 'last3Months':
startDate = new Date(today.getFullYear(), today.getMonth() - 2, 1);
endDate = new Date(today.getFullYear(), today.getMonth() + 1, 0);
break;
case 'thisYear':
startDate = new Date(today.getFullYear(), 0, 1);
endDate = new Date(today.getFullYear(), 11, 31);
break;
case 'custom':
break;
}
document.getElementById('reportStartDate').value = formatDateForInput(startDate);
document.getElementById('reportEndDate').value = formatDateForInput(endDate);
const accountId = document.getElementById('reportAccountSelect').value;
updateDateRangeDisplay();
}
function clearDateRange() {
document.getElementById('reportStartDate').value = '';
document.getElementById('reportEndDate').value = '';
reportDateRange = {
startDate: null,
endDate: null,
accountId: 'all', isCustomRange: false
};
document.getElementById('reportAccountSelect').value = 'all';
document.getElementById('selectedRangeDisplay').classList.add('hidden');
setTimeout(() => {
document.getElementById('reportStartDate').focus();
}, 100);
showToast("🗑️ เคลียร์ช่วงเวลาที่เลือกแล้ว");
}
function updateDateRangeDisplay() {
const startDate = document.getElementById('reportStartDate').value;
const endDate = document.getElementById('reportEndDate').value;
if (startDate && endDate) {
document.getElementById('displayStartDate').textContent = formatDateForDisplay(startDate);
document.getElementById('displayEndDate').textContent = formatDateForDisplay(endDate);
document.getElementById('selectedRangeDisplay').classList.remove('hidden');
}
}
function confirmDateRange() {
const startDate = document.getElementById('reportStartDate').value;
const endDate = document.getElementById('reportEndDate').value;
const accountId = document.getElementById('reportAccountSelect').value;
if (!startDate || !endDate) {
showToast("กรุณาเลือกทั้งวันที่เริ่มต้นและสิ้นสุด");
return;
}
if (new Date(startDate) > new Date(endDate)) {
showToast("วันที่เริ่มต้นต้องมาก่อนวันที่สิ้นสุด");
return;
}
reportDateRange = {
startDate: startDate,
endDate: endDate,
accountId: accountId,
isCustomRange: true
};
closeDateRangeModal();
setTimeout(() => {
generatePDFReportWithRangeAuto();
}, 500);
}
async function generatePDFReportWithRange() {
if (!reportDateRange.startDate || !reportDateRange.endDate) {
openDateRangeModal();
} else {
await generatePDFReportWithRangeAuto();
}
}
async function createHTMLReportWithDateRange(startDateStr, endDateStr, accountId = 'all') {
const reportData = await gatherReportDataByDateRange(startDateStr, endDateStr, accountId);
const htmlContent = createPDFHTMLWithDateRange(reportData, startDateStr, endDateStr, accountId);
await downloadHTMLFile(htmlContent, reportData);
showToast('✅ สร้างรายงาน HTML สำเร็จ');
setTimeout(() => {
refreshUIAfterReport();
}, 500);
}
async function generatePDFReportWithRangeAuto() {
if (!reportDateRange.startDate || !reportDateRange.endDate) {
showToast("📅 กรุณาเลือกช่วงเวลาก่อนสร้างรายงาน");
openDateRangeModal();
return;
}
try {
if (!document.getElementById('settingsModal').classList.contains('hidden')) {
toggleSettingsModal();
}
const accountName = reportDateRange.accountId === 'all' ? 'ทุกบัญชี' : getAccountById(reportDateRange.accountId)?.name;
showToast(`🔄 กำลังสร้างรายงานตามช่วงเวลา (บัญชี: ${accountName})...`);
await createHTMLReportWithDateRange(reportDateRange.startDate, reportDateRange.endDate, reportDateRange.accountId);
} catch (error) {
console.error('Error generating report with date range:', error);
showToast('❌ สร้างรายงานไม่สำเร็จ: ' + error.message);
}
}
function refreshUIAfterReport() {
console.log("🔄 รีเฟรช UI หลังสร้างรายงาน...");
try {
reportDateRange = {
startDate: null,
endDate: null,
isCustomRange: false
};
const currentPage = getCurrentPage();
switch(currentPage) {
case 'overview':
updateUI();
renderCalendar();
break;
case 'budget':
updateBudgetUI();
break;
case 'analysis':
if (reportDateRange.startDate) {
const startDate = new Date(reportDateRange.startDate);
analysisDate = startDate;
updateAnalysisPeriodText();
}
refreshAnalysisCharts();
break;
case 'yearly':
if (reportDateRange.startDate) {
const startDate = new Date(reportDateRange.startDate);
displayYear = startDate.getFullYear();
document.getElementById('yearSelect').value = displayYear;
}
updateYearlyUI();
break;
case 'debt':
renderDebtPage();
break;
case 'accounts':
renderAccountsList();
break;
}
updateAccountFilterDropdown();
updateAllAccountIndicators();
if (financeDB && financeDB.loadInitialData) {
setTimeout(() => {
financeDB.loadInitialData();
}, 500);
}
showToast("✅ รายงานสร้างสำเร็จและรีเฟรชข้อมูลแล้ว");
console.log("✅ รีเฟรช UI เสร็จสิ้น");
} catch (error) {
console.error("❌ เกิดข้อผิดพลาดในการรีเฟรช UI:", error);
}
}
async function generatePDFReport() {
const today = new Date();
const startDate = new Date(today.getFullYear(), today.getMonth(), 1);
const endDate = new Date(today.getFullYear(), today.getMonth() + 1, 0);
try {
toggleSettingsModal();
showToast("🔄 กำลังสร้างรายงานเดือนปัจจุบัน...");
await createHTMLReportWithDateRange(formatDateForInput(startDate), formatDateForInput(endDate));
setTimeout(() => {
refreshUIAfterReport();
}, 1000);
} catch (error) {
console.error('Error generating report:', error);
showToast('❌ สร้างรายงานไม่สำเร็จ: ' + error.message);
}
}
async function gatherReportDataByDateRange(startDateStr, endDateStr, accountId = 'all') {
const startDate = new Date(startDateStr);
const endDate = new Date(endDateStr);
let filteredTransactions = transactions.filter(t => {
const txDate = new Date(t.rawDate);
return txDate >= startDate && txDate <= endDate;
});
if (accountId !== 'all') {
filteredTransactions = filteredTransactions.filter(t =>
t.accountId === accountId ||
(t.type === 'transfer' && t.transferToAccountId === accountId)
);
}
const income = filteredTransactions.filter(t => t.type === 'income').reduce((sum, t) => sum + t.amount, 0);
const expense = filteredTransactions.filter(t => t.type === 'expense').reduce((sum, t) => sum + t.amount, 0);
const balance = income - expense;
const investmentExpense = filteredTransactions
.filter(t => t.type === 'expense' &&
customCategories.investment.some(c => c.label === t.category))
.reduce((sum, t) => sum + t.amount, 0);
const investmentRate = income > 0 ? (investmentExpense / income) * 100 : 0;
let accountsBalance = [];
if (accountId === 'all') {
accountsBalance = accounts.map(acc => ({
name: acc.name,
balance: getAccountBalance(acc.id),
icon: acc.icon
}));
} else {
const selectedAccount = getAccountById(accountId);
if (selectedAccount) {
accountsBalance = [{
name: selectedAccount.name,
balance: getAccountBalance(accountId),
icon: selectedAccount.icon
}];
}
}
const spendingByCategory = {};
const expenseTransactions = filteredTransactions.filter(t => t.type === 'expense');
expenseTransactions.forEach(t => {
if (!spendingByCategory[t.category]) {
spendingByCategory[t.category] = {
amount: 0,
count: 0,
icon: t.icon,
percentage: 0
};
}
spendingByCategory[t.category].amount += t.amount;
spendingByCategory[t.category].count++;
});
Object.keys(spendingByCategory).forEach(category => {
spendingByCategory[category].percentage = expense > 0 ?
(spendingByCategory[category].amount / expense) * 100 : 0;
});
const allCategories = Object.entries(spendingByCategory)
.map(([category, data]) => ({
category,
...data
}))
.sort((a, b) => b.amount - a.amount);
const spendingByTag = {};
const taggedTransactions = expenseTransactions.filter(t => t.tag && t.tag.trim() !== '');
taggedTransactions.forEach(t => {
const tag = t.tag.trim();
if (!spendingByTag[tag]) {
spendingByTag[tag] = {
amount: 0,
count: 0,
percentage: 0
};
}
spendingByTag[tag].amount += t.amount;
spendingByTag[tag].count++;
});
Object.keys(spendingByTag).forEach(tag => {
spendingByTag[tag].percentage = expense > 0 ?
(spendingByTag[tag].amount / expense) * 100 : 0;
});
const allTags = Object.entries(spendingByTag)
.map(([tag, data]) => ({
tag,
...data
}))
.sort((a, b) => b.amount - a.amount);
const debtData = await getDebtReportDataByDateRange(startDateStr, endDateStr);
return {
startDate: startDateStr,
endDate: endDateStr,
displayStartDate: formatDateForDisplay(startDateStr),
displayEndDate: formatDateForDisplay(endDateStr),
accountId: accountId,
accountName: accountId === 'all' ? 'ทุกบัญชี' : getAccountById(accountId)?.name,
reportDate: new Date().toLocaleDateString('th-TH'),
income,
expense,
balance,
investmentRate: investmentRate.toFixed(1),
transactionCount: filteredTransactions.length,
expenseTransactionCount: expenseTransactions.length,
taggedTransactionCount: taggedTransactions.length,
debt: debtData,
accounts: accountsBalance,
totalBalance: calculateTotalBalance(),
topCategories: allCategories,
topTags: allTags,
categoryPieData: {
labels: allCategories.map(cat => cat.category),
data: allCategories.map(cat => cat.amount),
colors: generateChartColors(allCategories.length)
},
tagPieData: {
labels: allTags.map(tag => tag.tag),
data: allTags.map(tag => tag.amount),
colors: generateChartColors(allTags.length)
}
};
}
async function getDebtReportDataByDateRange(startDateStr, endDateStr) {
const startDate = new Date(startDateStr);
const endDate = new Date(endDateStr);
const rangeDebts = debts.filter(debt => {
const debtStartDate = new Date(debt.startDate);
return debtStartDate >= startDate && debtStartDate <= endDate;
});
let totalDebt = 0;
let totalPaid = 0;
let activeDebts = 0;
rangeDebts.forEach(debt => {
totalDebt += debt.totalAmount;
const debtPayments = payments.filter(p => p.debtId === debt.id);
const paidAmount = debtPayments.reduce((sum, p) => sum + p.amount, 0);
totalPaid += paidAmount;
if (debt.totalAmount - paidAmount > 0) activeDebts++;
});
return {
totalDebt,
totalPaid,
remaining: totalDebt - totalPaid,
debtCount: rangeDebts.length,
activeDebts,
utilization: totalDebt > 0 ? (totalPaid / totalDebt) * 100 : 0,
debts: rangeDebts.slice(0, 5)
};
}
function createPDFHTMLWithDateRange(data, startDateStr, endDateStr, accountId = 'all') {
const html = createPDFHTML(data);
const accountInfo = accountId === 'all' ?
'ทุกบัญชี' :
`บัญชี: ${getAccountById(accountId)?.name || accountId}`;
return html
.replace(
'ประจำเดือน
${data.month} ${data.year}',
`ประจำช่วงเวลา
${data.displayStartDate} - ${data.displayEndDate}`
)
.replace(
'สรุปภาพรวม',
`สรุปภาพรวม (${accountInfo})`
)
.replace(
'📊 รายงานการเงิน Flow Wallet',
`📊 รายงานการเงิน Flow Wallet - ${accountInfo}`
);
}
let isLoggedIn = false;
let currentUser = null;
let saveToLocalEnabled = true;
function updateLocalSaveCheckbox() {
const checkbox = document.getElementById('saveToLocalCheckbox');
const hint = document.getElementById('localSaveHint');
console.log('🔄 updateLocalSaveCheckbox() called');
if (!checkbox) return;
if (!isLoggedIn) {
checkbox.checked = true;
checkbox.disabled = true;
saveToLocalEnabled = true;
checkbox.closest('#localSaveOption')?.classList.add('opacity-50');
if (hint) {
hint.innerHTML = '🔒 โหมดผู้เยี่ยมชม: บันทึกในเครื่องเท่านั้น';
hint.className = 'text-[10px] text-amber-500 mt-0.5';
}
} else {
checkbox.disabled = false;
checkbox.closest('#localSaveOption')?.classList.remove('opacity-50');
const savedPreference = localStorage.getItem('fin_save_local_preference');
checkbox.checked = savedPreference !== null ? savedPreference === 'true' : true;
saveToLocalEnabled = checkbox.checked;
if (hint) {
hint.innerHTML = saveToLocalEnabled
? '💾 บันทึกทั้ง MySQL และในเครื่อง'
: '☁️ บันทึกเฉพาะ MySQL (ไม่เก็บในเครื่อง)';
hint.className = 'text-[10px] text-slate-400 mt-0.5';
}
checkbox.removeEventListener('change', handleCheckboxChange);
checkbox.addEventListener('change', handleCheckboxChange);
}
if (window.financeDB) {
window.financeDB.setSaveToLocalEnabled(saveToLocalEnabled);
}
}
function handleCheckboxChange(e) {
saveToLocalEnabled = e.target.checked;
localStorage.setItem('fin_save_local_preference', e.target.checked);
if (window.financeDB) {
window.financeDB.setSaveToLocalEnabled(saveToLocalEnabled);
}
const hint = document.getElementById('localSaveHint');
if (hint) {
hint.innerHTML = saveToLocalEnabled
? '💾 บันทึกทั้ง MySQL และในเครื่อง'
: '☁️ บันทึกเฉพาะ MySQL (ไม่เก็บในเครื่อง)';
}
console.log('📌 Checkbox changed:', saveToLocalEnabled ? '✅ บันทึกทั้งสองที่' : '☁️ บันทึกเฉพาะ MySQL');
showToast(saveToLocalEnabled ?
'✅ บันทึกทั้ง MySQL และในเครื่อง' :
'☁️ บันทึกเฉพาะ MySQL', 'info');
}
function updateLocalSaveCheckbox() {
const checkbox = document.getElementById('saveToLocalCheckbox');
const hint = document.getElementById('localSaveHint');
console.log('🔄 updateLocalSaveCheckbox() called');
console.log('📦 checkbox found:', !!checkbox);
console.log('🔐 isLoggedIn:', isLoggedIn);
if (!checkbox) return;
if (!isLoggedIn) {
checkbox.checked = true;
checkbox.disabled = true;
saveToLocalEnabled = true;
if (window.financeDB) {
window.financeDB.setSaveToLocalEnabled(true);
}
if (hint) {
hint.innerHTML = '🔒 บังคับบันทึกในเครื่อง (โหมดผู้เยี่ยมชม)';
hint.className = 'text-[10px] text-amber-500 mt-0.5';
}
} else {
checkbox.disabled = false;
const savedPreference = localStorage.getItem('fin_save_local_preference');
if (savedPreference !== null) {
checkbox.checked = savedPreference === 'true';
} else {
checkbox.checked = true;
}
saveToLocalEnabled = checkbox.checked;
if (window.financeDB) {
window.financeDB.setSaveToLocalEnabled(saveToLocalEnabled);
}
if (hint) {
hint.innerHTML = '💡 ถ้าไม่ติ๊ก จะบันทึกเฉพาะบนเซิร์ฟเวอร์ (MySQL)';
hint.className = 'text-[10px] text-slate-400 mt-0.5';
}
checkbox.removeEventListener('change', handleCheckboxChange);
checkbox.addEventListener('change', handleCheckboxChange);
}
console.log('💾 FinanceDB saveToLocalEnabled =', window.financeDB?.saveToLocalEnabled);
}
async function cleanupLocalCache() {
if (!isLoggedIn) return;
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith('fin_cache_')) {
localStorage.removeItem(key);
console.log(`🗑️ ลบ ${key}`);
}
}
localStorage.removeItem('fin_cache_recent');
const allTx = JSON.parse(localStorage.getItem('fin_tx_v5') || '[]');
const backendTx = allTx.filter(t => t.backendId);
localStorage.setItem('fin_tx_v5', JSON.stringify(backendTx));
console.log(`✅ ล้าง cache เสร็จ: เหลือ ${backendTx.length} รายการใน fin_tx_v5`);
}
async function loadUserDataFromBackend() {
if (!isLoggedIn || !navigator.onLine) return;
showToast('🔄 กำลังโหลดข้อมูลจากเซิร์ฟเวอร์...', 'info');
try {
await loadTransactionsFromBackend();
await loadAccountsFromBackend();
await loadCategoriesFromBackend();
await loadBudgetsFromBackend();
await loadDebtsFromBackend();
await loadTagsFromBackend();
await cleanupLocalCache();
showToast('✅ โหลดข้อมูลจากเซิร์ฟเวอร์สำเร็จ', 'success');
updateUI();
} catch (error) {
console.error('Error loading user data:', error);
showToast('⚠️ โหลดข้อมูลบางส่วนไม่สำเร็จ', 'info');
}
}
function injectLocalSaveCheckbox() {
if (document.getElementById('localSaveOption')) return;
const formContainer = document.getElementById('formContainer');
if (!formContainer) {
console.log('⚠️ ไม่พบ formContainer');
return;
}
const submitBtn = document.getElementById('submitBtn');
if (!submitBtn) {
console.log('⚠️ ไม่พบ submitBtn');
return;
}
const checkboxHTML = `
`;
submitBtn.insertAdjacentHTML('beforebegin', checkboxHTML);
console.log('✅ แทรก checkbox สำเร็จ');
if (typeof updateLocalSaveCheckbox === 'function') {
updateLocalSaveCheckbox();
}
}
document.addEventListener('DOMContentLoaded', function() {
setTimeout(injectLocalSaveCheckbox, 1500);
});
const originalManageFormContainer = manageFormContainer;
manageFormContainer = function() {
originalManageFormContainer();
setTimeout(injectLocalSaveCheckbox, 500);
};
document.addEventListener('DOMContentLoaded', function() {
const savedUser = localStorage.getItem('user');
try {
if (savedUser && savedUser !== 'null' && savedUser !== 'undefined') {
currentUser = JSON.parse(savedUser);
isLoggedIn = true;
console.log('🔐 User logged in:', currentUser.username);
if (typeof loadUserDataFromBackend === 'function') {
loadUserDataFromBackend();
}
} else {
isLoggedIn = false;
currentUser = { id: 'guest_' + Date.now() };
console.log('👤 Guest mode');
}
} catch (e) {
console.error('❌ Error parsing user:', e);
localStorage.removeItem('user');
isLoggedIn = false;
currentUser = { id: 'guest_' + Date.now() };
}
updateAuthButtons();
});
function checkLoginStatus() {
const user = localStorage.getItem('user');
updateAuthButtons();
if (user) {
isLoggedIn = true;
currentUser = JSON.parse(user);
return true;
} else {
isLoggedIn = false;
currentUser = { id: 'guest_' + Date.now() };
return false;
}
}
function showAuthSection() {
document.getElementById('auth-section').style.display = 'block';
document.getElementById('app-content').style.display = 'none';
showLogin(); }
function showAppContent() {
document.getElementById('auth-section').style.display = 'none';
document.getElementById('app-content').style.display = 'block';
addLogoutButton();
}
function showLogin() {
document.getElementById('login-form').style.display = 'block';
document.getElementById('register-form').style.display = 'none';
}
function showRegister() {
document.getElementById('login-form').style.display = 'none';
document.getElementById('register-form').style.display = 'block';
}
function addLogoutButton() {
if (!document.getElementById('logout-btn')) {
const logoutBtn = document.createElement('button');
logoutBtn.id = 'logout-btn';
logoutBtn.onclick = logout;
document.body.appendChild(logoutBtn);
}
}
function showToast(message, type = 'success') {
const toast = document.getElementById('toast');
if (type === 'success') {
toast.className = 'fixed top-4 left-1/2 -translate-x-1/2 bg-emerald-600 text-white px-6 py-3 rounded-2xl shadow-2xl text-sm font-bold z-[9999] transition-all duration-300';
} else if (type === 'error') {
toast.className = 'fixed top-4 left-1/2 -translate-x-1/2 bg-rose-600 text-white px-6 py-3 rounded-2xl shadow-2xl text-sm font-bold z-[9999] transition-all duration-300';
} else if (type === 'info') {
toast.className = 'fixed top-4 left-1/2 -translate-x-1/2 bg-blue-600 text-white px-6 py-3 rounded-2xl shadow-2xl text-sm font-bold z-[9999] transition-all duration-300';
}
toast.innerText = message;
toast.style.display = 'block';
toast.style.opacity = '1';
toast.style.transform = 'translateX(-50%) translateY(0)';
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateX(-50%) translateY(-20px)';
setTimeout(() => {
toast.style.display = 'none';
}, 300);
}, 2000);
}
const API_URL = 'https://expense-tracker-backend-ek2d.onrender.com/api';
async function login() {
try {
const username = document.getElementById('login-username').value;
const password = document.getElementById('login-password').value;
if (!username || !password) {
showToast('⚠️ กรุณากรอกชื่อผู้ใช้และรหัสผ่าน', 'error');
return;
}
showToast('🔄 กำลังเข้าสู่ระบบ...', 'info');
const response = await fetch(`${API_URL}/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (response.ok) {
if (currentUser?.id) {
const allTx = await financeDB.getAllTransactions();
const oldUserTx = allTx.filter(t => t.owner_type === 'user' && t.owner_id === currentUser.id);
for (const tx of oldUserTx) {
await financeDB.deleteTransaction(tx.id);
}
}
localStorage.setItem('user', JSON.stringify(data.user));
isLoggedIn = true;
currentUser = data.user;
hideLoginModal();
updateAuthButtons();
showToast('🔄 กำลังโหลดข้อมูลจากเซิร์ฟเวอร์...', 'info');
await loadTransactionsFromBackend();
await loadInitialData();
updateLocalSaveCheckbox();
updateUI();
renderCalendar();
showToast('✅ เข้าสู่ระบบสำเร็จ!', 'success');
} else {
showToast('❌ ' + (data.error || 'ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง'), 'error');
}
} catch (error) {
console.error('Login error:', error);
showToast('❌ เกิดข้อผิดพลาดในการเชื่อมต่อ', 'error');
}
}
async function loadUserDataFromBackend() {
if (!isLoggedIn || !navigator.onLine) {
console.log('⏳ ไม่สามารถโหลดข้อมูลได้ (ไม่มีเน็ตหรือยังไม่ login)');
return;
}
showToast('🔄 กำลังโหลดข้อมูลจากเซิร์ฟเวอร์...', 'info');
try {
try {
await loadTransactionsFromBackend();
} catch (e) {
console.error('Error loading transactions:', e);
}
try {
await loadAccountsFromBackend();
} catch (e) {
console.error('Error loading accounts:', e);
}
try {
await loadCategoriesFromBackend();
} catch (e) {
console.error('Error loading categories:', e);
}
try {
await loadBudgetsFromBackend();
} catch (e) {
console.error('Error loading budgets:', e);
}
try {
await loadDebtsFromBackend();
} catch (e) {
console.error('Error loading debts:', e);
}
try {
await loadTagsFromBackend();
} catch (e) {
console.error('Error loading tags:', e);
}
showToast('✅ โหลดข้อมูลจากเซิร์ฟเวอร์สำเร็จ', 'success');
updateUI();
} catch (error) {
console.error('Error loading user data:', error);
showToast('⚠️ โหลดข้อมูลบางส่วนไม่สำเร็จ', 'info');
}
}
async function loadTransactionsFromBackend() {
if (!isLoggedIn || !navigator.onLine) return;
try {
console.log('📥 กำลังโหลด transactions จาก backend...');
const response = await fetch(`${API_URL}/transactions/${currentUser.id}`);
if (!response.ok) {
console.error('Backend returned error:', response.status);
return;
}
const serverTransactions = await response.json();
console.log(`📦 ข้อมูลจาก backend: ${serverTransactions.length} รายการ`);
if (!Array.isArray(serverTransactions)) {
console.error('Server response is not an array:', serverTransactions);
return;
}
backendTransactions = [];
for (const tx of serverTransactions) {
if (!tx || !tx.id) continue;
const formattedTx = {
id: tx.id.toString(),
amount: parseFloat(tx.amount) || 0,
type: tx.type || 'expense',
category: tx.category || 'อื่นๆ',
icon: tx.icon || '📝',
desc: tx.desc || tx.category || '',
tag: tx.tag || '',
rawDate: tx.rawDate || tx.date || '',
monthKey: tx.monthKey || '',
date: tx.date || tx.rawDate || '',
accountId: tx.accountId ? tx.accountId.toString() : null,
backendId: tx.id,
isFromBackend: true,
createdAt: tx.createdAt || new Date().toISOString(),
updatedAt: tx.updatedAt || new Date().toISOString(),
owner_type: 'user',
owner_id: currentUser.id
};
backendTransactions.push(formattedTx);
}
console.log(`✅ โหลดข้อมูลจาก backend สำเร็จ: ${backendTransactions.length} รายการ`);
if (saveToLocalEnabled) {
for (const tx of backendTransactions) {
await financeDB.saveTransaction(tx);
}
console.log(`✅ บันทึกข้อมูลลงเครื่องแล้ว: ${backendTransactions.length} รายการ`);
}
} catch (error) {
console.error('Error loading transactions from backend:', error);
}
}
async function loadAccountsFromBackend() {
try {
const response = await fetch(`${API_URL}/accounts/${currentUser.id}`);
const serverAccounts = await response.json();
console.log(`📥 โหลด ${serverAccounts.length} accounts จาก backend`);
for (const acc of serverAccounts) {
acc.id = acc.id.toString();
const exists = accounts.some(a => a.id === acc.id);
if (!exists) {
accounts.push(acc);
}
}
saveAccounts();
} catch (error) {
console.error('Error loading accounts:', error);
throw error;
}
}
async function loadCategoriesFromBackend() {
try {
const response = await fetch(`${API_URL}/categories/${currentUser.id}`);
const serverCategories = await response.json();
console.log(`📥 โหลด categories จาก backend:`, serverCategories);
if (serverCategories &&
((serverCategories.income && serverCategories.income.length > 0) ||
(serverCategories.spending && serverCategories.spending.length > 0) ||
(serverCategories.investment && serverCategories.investment.length > 0))) {
customCategories = {
income: serverCategories.income || [],
spending: serverCategories.spending || [],
investment: serverCategories.investment || []
};
} else {
console.log('⚠️ ไม่พบข้อมูล categories จาก backend, ใช้ค่าเริ่มต้น');
customCategories = JSON.parse(JSON.stringify(defaultCategories));
}
localStorage.setItem('fin_custom_cats', JSON.stringify(customCategories));
updateCategorySelect();
} catch (error) {
console.error('Error loading categories:', error);
}
}
async function loadTagsFromBackend() {
if (!isLoggedIn || !navigator.onLine) return;
try {
const response = await fetch(`${API_URL}/tags/${currentUser.id}`);
const serverTags = await response.json();
console.log(`📥 โหลด ${serverTags.length} tags จาก backend`);
for (const tag of serverTags) {
const exists = tags.some(t => t.id === tag.id);
if (!exists) {
tags.push({
id: tag.id.toString(),
name: tag.name,
color: tag.color || '#6366f1'
});
}
}
localStorage.setItem('fin_tags', JSON.stringify(tags));
} catch (error) {
console.error('Error loading tags:', error);
}
}
async function saveTagToBackend(tagData) {
if (!isLoggedIn || !navigator.onLine) return false;
try {
const response = await fetch(`${API_URL}/tags`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user_id: currentUser.id,
name: tagData.name,
color: tagData.color || '#6366f1'
})
});
const result = await response.json();
if (response.ok && result.id && tagData.id.startsWith('tag_')) {
tagData.id = result.id.toString();
localStorage.setItem('fin_tags', JSON.stringify(tags));
}
return response.ok;
} catch (error) {
console.error('Error saving tag:', error);
return false;
}
}
async function deleteTagFromBackend(tagId) {
if (!isLoggedIn || !navigator.onLine) return false;
if (tagId.toString().startsWith('tag_')) return true;
try {
const response = await fetch(`${API_URL}/tags/${tagId}?user_id=${currentUser.id}`, {
method: 'DELETE'
});
return response.ok;
} catch (error) {
console.error('Error deleting tag:', error);
return false;
}
}
async function loadBudgetsFromBackend() {
try {
const monthKey = getMonthKey();
const response = await fetch(`${API_URL}/budgets/${currentUser.id}/${monthKey}`);
const serverBudgets = await response.json();
console.log(`📥 โหลด budgets จาก backend`);
categoryTargets[monthKey] = serverBudgets;
localStorage.setItem('fin_targets_v5', JSON.stringify(categoryTargets));
} catch (error) {
console.error('Error loading budgets:', error);
throw error;
}
}
async function loadDebtsFromBackend() {
try {
const response = await fetch(`${API_URL}/debts/${currentUser.id}`);
const serverDebts = await response.json();
console.log(`📥 โหลด ${serverDebts.length} debts จาก backend`);
for (const debt of serverDebts) {
const exists = debts.some(d => d.id === debt.id);
if (!exists) {
debts.push(debt);
}
}
saveDebtsToStorage();
} catch (error) {
console.error('Error loading debts:', error);
throw error;
}
}
async function saveDebtToBackend(debtData) {
if (!isLoggedIn || !navigator.onLine) return false;
try {
const response = await fetch(`${API_URL}/debts`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user_id: currentUser.id,
name: debtData.name,
categoryId: debtData.categoryId,
tag: debtData.tag,
totalAmount: debtData.totalAmount,
monthlyPayment: debtData.monthlyPayment,
interestRate: debtData.interestRate || 0,
dueDate: debtData.dueDate,
startDate: debtData.startDate,
status: debtData.status || 'open'
})
});
const result = await response.json();
if (response.ok && result.id && debtData.id.startsWith('debt_')) {
const index = debts.findIndex(d => d.id === debtData.id);
if (index !== -1) {
debts[index].id = result.id.toString();
debts[index].backendId = result.id;
saveDebtsToStorage();
}
}
return response.ok;
} catch (error) {
console.error('Error saving debt:', error);
return false;
}
}
async function updateDebtInBackend(debtData) {
if (!isLoggedIn || !navigator.onLine) return false;
if (debtData.id.toString().startsWith('debt_')) {
return saveDebtToBackend(debtData);
}
try {
const response = await fetch(`${API_URL}/debts/${debtData.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user_id: currentUser.id,
name: debtData.name,
categoryId: debtData.categoryId,
tag: debtData.tag,
totalAmount: debtData.totalAmount,
monthlyPayment: debtData.monthlyPayment,
interestRate: debtData.interestRate || 0,
dueDate: debtData.dueDate,
startDate: debtData.startDate,
status: debtData.status,
closedAt: debtData.closedAt
})
});
return response.ok;
} catch (error) {
console.error('Error updating debt:', error);
return false;
}
}
async function deleteDebtFromBackend(debtId) {
if (!isLoggedIn || !navigator.onLine) return false;
if (debtId.toString().startsWith('debt_')) return true;
try {
const response = await fetch(`${API_URL}/debts/${debtId}?user_id=${currentUser.id}`, {
method: 'DELETE'
});
return response.ok;
} catch (error) {
console.error('Error deleting debt:', error);
return false;
}
}
async function register() {
try {
const username = document.getElementById('reg-username')?.value;
const password = document.getElementById('reg-password')?.value;
const confirmPass = document.getElementById('reg-confirm-password')?.value;
if (!username || !password || !confirmPass) {
showToast('⚠️ กรุณากรอกข้อมูลให้ครบ', 'error');
return;
}
if (password !== confirmPass) {
showToast('⚠️ รหัสผ่านไม่ตรงกัน', 'error');
return;
}
if (password.length < 6) {
showToast('⚠️ รหัสผ่านต้องมีอย่างน้อย 6 ตัวอักษร', 'error');
return;
}
showToast('🔄 กำลังสมัครสมาชิก...', 'info');
const response = await fetch(`${API_URL}/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: username.trim(), password: password.trim() })
});
const data = await response.json();
if (response.ok) {
showToast('✅ สมัครสมาชิกสำเร็จ! กรุณาเข้าสู่ระบบ', 'success');
showLoginInModal();
document.getElementById('reg-username').value = '';
document.getElementById('reg-password').value = '';
document.getElementById('reg-confirm-password').value = '';
} else {
showToast('❌ ' + (data.error || 'ไม่สามารถสมัครสมาชิกได้'), 'error');
}
} catch (error) {
console.error('Register error:', error);
showToast('❌ เกิดข้อผิดพลาดในการเชื่อมต่อ', 'error');
}
}
async function logout() {
showConfirm('ออกจากระบบ?', 'ข้อมูลในเครื่องจะยังคงอยู่', async () => {
const queue = JSON.parse(localStorage.getItem('syncQueue') || '[]');
if (queue.length > 0 && navigator.onLine) {
showToast('🔄 กำลังซิงค์ข้อมูลก่อนออกจากระบบ...', 'info');
await processSyncQueue();
}
const userId = currentUser?.id;
localStorage.removeItem('user');
isLoggedIn = false;
currentUser = { id: 'guest_' + Date.now() };
if (userId) {
const allTx = await financeDB.getAllTransactions();
const userTx = allTx.filter(t => t.owner_type === 'user' && t.owner_id === userId);
for (const tx of userTx) {
await financeDB.deleteTransaction(tx.id);
}
console.log(`🗑️ ลบข้อมูล user ${userId} จำนวน ${userTx.length} รายการ`);
}
await loadInitialData();
updateLocalSaveCheckbox();
updateAuthButtons();
hideConfirm();
showToast('👋 ออกจากระบบสำเร็จ', 'success');
updateUI();
});
}
function closeAllModals() {
console.log('Closing all modals...');
const settingsModal = document.getElementById('settingsModal');
if (settingsModal) {
settingsModal.style.display = 'none';
settingsModal.classList.remove('active', 'open', 'show');
}
const mobileMenu = document.getElementById('mobileSideMenu');
if (mobileMenu) {
mobileMenu.style.display = 'none';
const panel = document.getElementById('sideMenuPanel');
if (panel) panel.style.transform = 'translateX(-100%)';
const backdrop = document.getElementById('sideMenuBackdrop');
if (backdrop) backdrop.style.display = 'none';
}
document.querySelectorAll('.modal-backdrop, .menu-backdrop, .fixed.inset-0.bg-black\\/50').forEach(el => {
el.style.display = 'none';
});
console.log('All modals closed');
}
async function addTransaction(transactionData) {
const user = JSON.parse(localStorage.getItem('user'));
if (!user) {
alert('กรุณาเข้าสู่ระบบก่อน');
return;
}
try {
const response = await fetch(`${API_URL}/transactions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...transactionData,
user_id: user.id
})
});
if (response.ok) {
loadUserData();
} else {
alert('ไม่สามารถบันทึกข้อมูลได้');
}
} catch (error) {
console.error('Error adding transaction:', error);
}
}
async function addTransaction(transaction) {
const user = JSON.parse(localStorage.getItem('user'));
const response = await fetch(`${API_URL}/transactions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...transaction,
user_id: user.id
})
});
return response.json();
}
function showLoginModal() {
console.log('🔐 Opening login modal');
const modal = document.getElementById('loginModal');
if (modal) {
modal.classList.remove('hidden');
modal.classList.add('flex');
showLoginInModal();
document.body.style.overflow = 'hidden';
setTimeout(() => {
document.getElementById('login-username')?.focus();
}, 300);
} else {
console.error('❌ loginModal not found!');
}
}
function hideLoginModal() {
console.log('🔐 Closing login modal');
const modal = document.getElementById('loginModal');
if (modal) {
modal.classList.add('hidden');
modal.classList.remove('flex');
document.body.style.overflow = 'auto';
document.getElementById('login-username').value = '';
document.getElementById('login-password').value = '';
document.getElementById('reg-username').value = '';
document.getElementById('reg-password').value = '';
document.getElementById('reg-confirm-password').value = '';
}
else {
console.warn('loginModal not found');
}
}
function showLoginInModal() {
document.getElementById('login-form').style.display = 'block';
document.getElementById('register-form').style.display = 'none';
}
function showRegisterInModal() {
document.getElementById('login-form').style.display = 'none';
document.getElementById('register-form').style.display = 'block';
}
function closeLoginModalOnBackdrop(event) {
if (event.target === event.currentTarget) {
hideLoginModal();
}
}
window.debugTransactions = function() {
console.log('=== DEBUG INFO ===');
console.log('isLoggedIn:', isLoggedIn);
console.log('isShowingBackendData:', isShowingBackendData);
console.log('backendTransactions:', backendTransactions.length, 'รายการ');
console.log('transactions (local):', transactions.length, 'รายการ');
console.log('currentMonthKey:', getMonthKey());
if (backendTransactions.length > 0) {
console.log('เดือนที่มีข้อมูลใน backend:', [...new Set(backendTransactions.map(t => t.monthKey))]);
console.log('ตัวอย่างรายการแรก:', backendTransactions[0]);
}
if (transactions.length > 0) {
console.log('เดือนที่มีข้อมูลใน local:', [...new Set(transactions.map(t => t.monthKey))]);
}
return '✅ Debug info printed to console';
};
window.debugIndexedDB = async function() {
console.log('=== IndexedDB Debug ===');
console.log('financeDB.db exists:', !!financeDB.db);
console.log('financeDB.initialized:', financeDB.initialized);
console.log('financeDB.saveToIndexedDBEnabled:', financeDB.saveToIndexedDBEnabled);
if (financeDB.db) {
console.log('Connection state:', financeDB.db.readyState);
try {
const tx = financeDB.db.transaction(['transactions'], 'readonly');
console.log('✅ สามารถสร้าง transaction ได้');
const store = tx.objectStore('transactions');
const count = await new Promise((resolve) => {
const req = store.count();
req.onsuccess = () => resolve(req.result);
});
console.log(`📊 มี ${count} รายการใน IndexedDB`);
} catch (e) {
console.error('❌ ไม่สามารถ query ได้:', e);
}
}
return 'ตรวจสอบเสร็จ';
};