SmartRepair-TH

ระบบจัดการงานซ่อมที่ออกแบบให้ร้านซ่อมทำงานเป็นระบบมากขึ้น

SmartRepair-TH คือระบบเว็บสำหรับบันทึก ค้นหา และจัดการข้อมูลงานซ่อม โดยเชื่อมต่อกับ Firebase ของระบบ

การเข้าสู่ระบบ: รองรับ Email และ Username โดยระบบจะตรวจสอบ Authentication และ Profile ก่อนกำหนดหน้าการใช้งาน
Firebase
Authentication และ Firestore
QR / Barcode
สแกนข้อมูลรุ่นหรือ IMEI
ค้นหางาน
ค้นหาด้วยเบอร์โทรศัพท์
Responsive
มือถือ แท็บเล็ต Desktop

Product Information

SmartRepair-TH ทำอะไรได้บ้าง

บันทึกงานซ่อม

บันทึกข้อมูลลูกค้า รุ่น/IMEI อาการเสีย และราคา

ค้นหางาน

ค้นหาด้วยเบอร์โทรศัพท์

Scanner

อ่าน QR / Barcode / IMEI

LINE

สร้างข้อมูลสำหรับส่งเข้า LINE

คู่มือการใช้งาน

ขั้นตอนหลัก

  1. 1 เข้าสู่ระบบด้วย Email หรือ Username
  2. 2 ตรวจสอบ Profile และ Role
  3. 3 เข้า Dashboard หรือหน้าบันทึกงาน
  4. 4 บันทึก ค้นหา หรือส่งข้อมูลงานซ่อม

โครงสร้างข้อมูลที่ใช้ในหน้านี้

ยึดตาม HTML ปัจจุบัน

repairs
name · phone · model · issue · price · createdAt
members
username · email · role · active · ownerUid · createdAt
ระบบนี้จะไม่สร้าง Field ใหม่โดยอัตโนมัตินอกเหนือจากโครงสร้างที่มีอยู่ในหน้าเว็บนี้

งานซ่อมล่าสุด

รายการล่าสุดจาก Firestore

กำลังโหลดข้อมูล...

สถานะระบบ

Authentication เชื่อมต่อ
Firestore กำลังตรวจสอบ
Repair listener กำลังตรวจสอบ
สิทธิ์การเข้าถึงข้อมูลจริงต้องถูกควบคุมที่ Firebase Security Rules ด้วย

นำเข้า / ส่งออกข้อมูล

ใช้กับข้อมูล collection repairs

ส่งออกข้อมูล

Export จาก Firestore

นำเข้าข้อมูล

JSON หรือ CSV

Import จะใช้ฟิลด์ที่รองรับในหน้าเว็บนี้: name, phone, model, issue, price และไม่เขียน Password

เพิ่มสมาชิก

จัดการ Profile สมาชิก

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); } $('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)}
}); refreshIcons(); }