<<<<<<< HEAD
let currentUser = null;
let repairUnsubscribe = null;
let scanner = null;
let cachedRepairs = [];
=======
>>>>>>> origin/main
เพิ่มสมาชิก
จัดการ Profile สมาชิก
<<<<<<< HEAD
try {
await db.collection('repairs').add({ name, phone, model, issue, price, createdAt: firebase.firestore.FieldValue.serverTimestamp() });
alert('บันทึกสำเร็จ');
} catch (e) {
alert('Error: ' + e.message);
}
});
function toast(message, type = 'info') {
const el = $('toast');
el.textContent = message;
el.className = 'toast fixed bottom-5 right-5 z-[60] max-w-sm rounded-xl px-4 py-3 text-sm text-white shadow-xl show';
if (type === 'error') el.classList.add('bg-rose-600');
else if (type === 'success') el.classList.add('bg-emerald-600');
else el.classList.add('bg-slate-900');
setTimeout(() => el.classList.remove('show'), 3200);
}
function friendlyAuthError(error) {
const map = {
'auth/invalid-email': 'รูปแบบอีเมลไม่ถูกต้อง',
'auth/user-disabled': 'บัญชีนี้ถูกระงับ',
'auth/user-not-found': 'ไม่พบบัญชีผู้ใช้',
'auth/wrong-password': 'รหัสผ่านไม่ถูกต้อง',
'auth/invalid-credential': 'ข้อมูลเข้าสู่ระบบไม่ถูกต้อง',
'auth/email-already-in-use': 'อีเมลนี้ถูกใช้งานแล้ว',
'auth/weak-password': 'รหัสผ่านต้องมีความยาวอย่างน้อย 6 ตัวอักษร',
'auth/network-request-failed': 'ไม่สามารถเชื่อมต่อเครือข่ายได้'
};
return map[error && error.code] || (error && error.message) || 'เกิดข้อผิดพลาด';
}
try {
const snapshot = await db.collection('repairs').where('phone', '==', phone).orderBy('createdAt', 'desc').get();
if (!snapshot.empty) {
const latestDoc = snapshot.docs[0].data();
document.getElementById('lbl_name').textContent = latestDoc.name;
document.getElementById('lbl_model').textContent = latestDoc.model;
document.getElementById('lbl_issue').textContent = latestDoc.issue;
document.getElementById('lbl_price').textContent = latestDoc.price;
alert(`พบข้อมูลทั้งหมด ${snapshot.size} รายการ`);
} else {
alert('ไม่พบข้อมูล');
['lbl_name', 'lbl_model', 'lbl_issue', 'lbl_price'].forEach(id => document.getElementById(id).textContent = '-');
}
} catch (e) {
alert('Error: ' + e.message);
}
});
function formatDate(value) {
if (!value) return '-';
const d = value.toDate ? value.toDate() : new Date(value);
if (Number.isNaN(d.getTime())) return '-';
const now = new Date();
const dKey = todayKey(d);
const nowKey = todayKey(now);
const time = d.toLocaleTimeString('th-TH', {
timeZone: 'Asia/Bangkok',
hour: '2-digit',
minute: '2-digit'
}) + ' น.';
if (dKey === nowKey) return 'วันนี้ ' + time;
return d.toLocaleDateString('th-TH', {
timeZone: 'Asia/Bangkok',
year: 'numeric',
month: '2-digit',
day: '2-digit'
}) + ' ' + time;
}
function todayKey(d = new Date()) {
return new Intl.DateTimeFormat('en-CA', {
timeZone: 'Asia/Bangkok',
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).format(d);
}
function escapeHtml(value) {
return String(value ?? '').replace(/[&<>"']/g, c => ({
'&':'&','<':'<','>':'>','"':'"',"'":'''
}[c]));
}
function updateCurrentDateTime() {
const now = new Date();
const dateText = now.toLocaleDateString('th-TH', {
timeZone: 'Asia/Bangkok',
year: 'numeric',
month: 'long',
day: 'numeric'
});
const timeText = now.toLocaleTimeString('th-TH', {
timeZone: 'Asia/Bangkok',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
const dayText = new Intl.DateTimeFormat('th-TH', {
timeZone: 'Asia/Bangkok',
weekday: 'long'
}).format(now);
if ($('current-date')) $('current-date').textContent = dateText;
if ($('current-time')) $('current-time').textContent = timeText + ' น.';
if ($('current-day')) $('current-day').textContent = dayText;
}
updateCurrentDateTime();
setInterval(updateCurrentDateTime, 1000);
function getUserRole(profile) {
if (!profile) return null;
return String(profile.role || '').toLowerCase();
}
async function loadProfile(user) {
/*
* New member profile design:
* members/{uid}
* { username, email, role, active, ownerUid, groupId, createdAt }
*
* This is a new application schema; it is not claimed to exist in
* the original project.
*/
try {
const snap = await db.collection('members').doc(user.uid).get();
if (snap.exists) return { id: snap.id, ...snap.data() };
} catch (e) {
console.warn('Profile lookup unavailable:', e);
}
return null;
}
async function resolveMemberUsername(username) {
const normalized = username.trim().toLowerCase();
const snap = await db.collection('members')
.where('username', '==', normalized)
.limit(1)
.get();
if (snap.empty) throw new Error('ไม่พบ Username ของสมาชิก');
const data = snap.docs[0].data();
if (data.active === false) throw new Error('สมาชิกนี้ถูกระงับการใช้งาน');
if (!data.email) throw new Error('สมาชิกนี้ไม่มี Email สำหรับ Firebase Authentication');
return data.email;
}
async function loginOwner(email, password) {
await auth.signInWithEmailAndPassword(email.trim(), password);
}
async function loginMember(username, password) {
const email = await resolveMemberUsername(username);
await auth.signInWithEmailAndPassword(email, password);
}
$('login-form').addEventListener('submit', async e => {
e.preventDefault();
const btn = $('btn-login');
const email = $('login-identifier').value.trim();
const password = $('login-password').value;
btn.disabled = true;
btn.textContent = 'กำลังเข้าสู่ระบบ...';
try {
await auth.signInWithEmailAndPassword(email, password);
} catch (error) {
toast(friendlyAuthError(error), 'error');
} finally {
btn.disabled = false;
btn.textContent = 'เข้าสู่ระบบ';
}
});
auth.onAuthStateChanged(async user => {
currentUser = user;
if (!user) {
cleanupListeners();
showView('public');
return;
}
$('owner-email').textContent = user.email || '-';
showView('owner');
startOwnerListeners();
});
function cleanupListeners() {
if (repairUnsubscribe) { repairUnsubscribe(); repairUnsubscribe = null; }
}
function ownerScopeQuery() {
return db.collection('repairs')
.where('ownerUid', '==', currentUser.uid)
.orderBy('createdAt', 'desc')
.limit(100);
}
function startOwnerListeners() {
if (repairUnsubscribe) repairUnsubscribe();
$('owner-firestore-state').textContent = 'กำลังเชื่อมต่อ';
$('owner-listener-state').textContent = 'กำลังเชื่อมต่อ';
repairUnsubscribe = ownerScopeQuery().onSnapshot(snapshot => {
cachedRepairs = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
$('owner-firestore-state').textContent = 'เชื่อมต่อแล้ว';
$('owner-listener-state').textContent = 'ทำงานอยู่';
renderOwnerStats();
renderRecentRepairs();
renderRepairTable();
}, error => {
console.error(error);
$('owner-firestore-state').textContent = 'อ่านข้อมูลไม่ได้';
$('owner-listener-state').textContent = 'หยุดทำงาน';
renderEmptyOwnerData('ไม่สามารถอ่านข้อมูล repairs ได้: ' + friendlyAuthError(error));
});
}
function renderEmptyOwnerData(message) {
$('owner-recent-repairs').innerHTML = `
${escapeHtml(message)}
`;
$('owner-repair-table').innerHTML = `
| ${escapeHtml(message)} |
`;
}
function renderOwnerStats() {
const repairs = cachedRepairs;
const today = todayKey();
$('owner-stat-total').textContent = repairs.length;
$('owner-stat-today').textContent = repairs.filter(r => {
if (!r.createdAt) return false;
const date = r.createdAt.toDate ? r.createdAt.toDate() : new Date(r.createdAt);
return todayKey(date) === today;
}).length;
const completed = repairs.filter(r => {
const status = String(r.status || '').toLowerCase();
return ['completed', 'complete', 'เสร็จแล้ว', 'ซ่อมเสร็จสิ้น'].includes(status);
}).length;
$('owner-stat-completed').textContent = completed;
}
function renderRecentRepairs() {
const rows = cachedRepairs.slice(0, 8);
if (!rows.length) {
$('owner-recent-repairs').innerHTML = `
ยังไม่มีข้อมูลงานซ่อม
Firestore ปัจจุบันยังไม่มีข้อมูลตามที่ตรวจสอบไว้
`;
lucide.createIcons();
return;
}
$('owner-recent-repairs').innerHTML = rows.map(r => `
${escapeHtml(r.name || '-')}
${escapeHtml(r.phone || r.id || '-')} · ${escapeHtml(r.model || '-')}
${escapeHtml(r.price ?? '-')} บาท
${formatDate(r.createdAt)}
=======
>>>>>>> origin/main