import React, { useState, useEffect, useMemo } from 'react';
import { initializeApp } from "firebase/app";
import {
getAuth, signInWithEmailAndPassword, createUserWithEmailAndPassword,
signOut, onAuthStateChanged, signInAnonymously, signInWithCustomToken
} from "firebase/auth";
import {
getFirestore, collection, doc, setDoc, getDoc, onSnapshot,
deleteDoc, updateDoc, addDoc, query, orderBy
} from "firebase/firestore";
import {
Home, ShoppingCart, Users, Receipt, PieChart, Settings,
Plus, Edit, Trash2, Search, LogOut, Globe, ChevronDown, Check, UserPlus, AlertCircle, Loader2,
ArrowLeft, Eye, CreditCard, User, Phone, FileText, Calendar, Wallet, TrendingUp, ShoppingBag
} from 'lucide-react';
// --- Firebase Configuration ---
const firebaseConfig = {
apiKey: "AIzaSyDoG1aWaNi9QNe0oMeGCEM7BRCaOhnZB_w",
authDomain: "naynnay.firebaseapp.com",
projectId: "naynnay",
storageBucket: "naynnay.firebasestorage.app",
messagingSenderId: "558678602864",
appId: "1:558678602864:web:f8c6b9f38d4edda08430b5",
measurementId: "G-R3SYVK6PS2"
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
// Safe App ID Initialization
const rawAppId = typeof __app_id !== 'undefined' ? __app_id : null;
const APP_ID = (rawAppId && rawAppId !== "null") ? String(rawAppId) : 'nay-n-nay-pos';
// --- UI Design Constants ---
const styles = {
bgApp: "bg-[#ffeaea] min-h-screen text-[#5c3a3a] font-sans",
neoCard: "bg-[#ffeaea] rounded-2xl shadow-[8px_8px_16px_#d9c7c7,-8px_-8px_16px_#ffffff] border border-white/40",
neoInput: "w-full bg-[#ffeaea] rounded-xl shadow-[inset_5px_5px_10px_#d9c7c7,inset_-5px_-5px_10px_#ffffff] p-4 focus:outline-none focus:ring-2 focus:ring-red-300 transition-all text-[#5c3a3a] placeholder-[#b89c9c]",
neoBtn: "bg-[#ffeaea] rounded-xl shadow-[5px_5px_10px_#d9c7c7,-5px_-5px_10px_#ffffff] active:shadow-[inset_5px_5px_10px_#d9c7c7,inset_-5px_-5px_10px_#ffffff] p-3 font-bold transition-all flex items-center justify-center gap-2 disabled:opacity-50",
neoBtnPrimary: "bg-[#ff6b6b] text-white rounded-xl shadow-[5px_5px_10px_#d9c7c7,-5px_-5px_10px_#ffffff] active:shadow-[inset_5px_5px_10px_#cc5555,inset_-5px_-5px_10px_#ff8282] p-3 font-bold transition-all flex items-center justify-center gap-2 disabled:opacity-50 hover:bg-[#ff5252]",
headerText: "text-2xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-red-600 to-red-400 drop-shadow-sm",
};
// --- Dictionary for i18n ---
const dict = {
my: {
system: "စနစ်", purchase: "ဝယ်ယူမှု", sale: "ရောင်းချမှု",
customer: "ဖောက်သည်", expense: "အသုံးစရိတ်", report: "အစီရင်ခံစာ",
login: "အကောင့်ဝင်ရန်", signup: "အကောင့်သစ်ဖွင့်ရန်", email: "အီးမေးလ်", password: "စကားဝှက်",
guestLogin: "Guest (အကောင့်မပါဘဲ) ဝင်မည်", logout: "ထွက်မည်", langSwitch: "English သို့ပြောင်းရန်",
add: "အသစ်ထည့်မည်", save: "သိမ်းမည်", cancel: "မလုပ်တော့ပါ", edit: "ပြင်မည်", delete: "ဖျက်မည်",
search: "ရှာဖွေရန်...", name: "အမည်", cost: "အရင်း", price: "ရောင်းဈေး", qty: "အရေအတွက်",
expDate: "သက်တမ်းကုန်ရက်", total: "စုစုပေါင်း", paid: "ငွေချေပြီး", unpaid: "ငွေမချေရသေး",
serviceFee: "ဝန်ဆောင်ခ", payAmount: "ပေးချေငွေ", balance: "လက်ကျန်ငွေ",
age: "အသက်", gender: "ကျား/မ", phone: "ဖုန်းနံပါတ်", diagnosis: "ရောဂါ/မှတ်ချက်",
daily: "နေ့စဉ်", weekly: "အပတ်စဉ်", monthly: "လစဉ်", yearly: "နှစ်စဉ်", grandTotal: "စုစုပေါင်း အသားတင်",
bestSelling: "အရောင်းရဆုံး ပစ္စည်းများ", inventory: "လက်ကျန် ပစ္စည်းတန်ဖိုး",
deductProfit: "အမြတ်ထဲမှ နုတ်မည်", date: "ရက်စွဲ", selectCustomer: "ဖောက်သည်ရွေးပါ",
addToCart: "ခြင်းထဲထည့်မည်", payBalance: "လက်ကျန်ငွေရှင်းမည်",
details: "မှတ်တမ်းကြည့်မည်", salesHistory: "ဝယ်ယူမှု မှတ်တမ်းများ", payNow: "ငွေချေမည်", paymentAmount: "ပေးသွင်းငွေ ပမာဏ"
},
en: {
system: "System", purchase: "Purchase", sale: "Sale",
customer: "Customer", expense: "Expense", report: "Report",
login: "Log In", signup: "Sign Up", email: "Email", password: "Password",
guestLogin: "Login as Guest", logout: "Log Out", langSwitch: "မြန်မာစာသို့ပြောင်းရန်",
add: "Add New", save: "Save", cancel: "Cancel", edit: "Edit", delete: "Delete",
search: "Search...", name: "Name", cost: "Cost", price: "Price", qty: "Quantity",
expDate: "Expiry Date", total: "Total", paid: "Paid", unpaid: "Unpaid",
serviceFee: "Service Fee", payAmount: "Pay Amount", balance: "Balance",
age: "Age", gender: "Gender", phone: "Phone", diagnosis: "Diagnosis",
daily: "Daily", weekly: "Weekly", monthly: "Monthly", yearly: "Yearly", grandTotal: "Grand Total",
bestSelling: "Best Selling", inventory: "Inventory Asset",
deductProfit: "Deduct from Profit", date: "Date", selectCustomer: "Select Customer",
addToCart: "Add to Cart", payBalance: "Pay Balance",
details: "View Details", salesHistory: "Detailed Sales History", payNow: "Pay Now", paymentAmount: "Payment Amount"
}
};
// ============================================================================
// MAIN APP COMPONENT
// ============================================================================
export default function App() {
const [user, setUser] = useState(null);
const [lang, setLang] = useState('my');
const [currentTab, setCurrentTab] = useState('customer'); // Default Tab
const t = dict[lang];
// Custom Dialog State
const [dialog, setDialog] = useState({ isOpen: false, type: 'alert', message: '', onConfirm: null, isProcessing: false });
const showAlert = (message) => setDialog({ isOpen: true, type: 'alert', message, onConfirm: null, isProcessing: false });
const showConfirm = (message, onConfirm) => setDialog({ isOpen: true, type: 'confirm', message, onConfirm, isProcessing: false });
const closeDialog = () => setDialog(prev => ({ ...prev, isOpen: false }));
// Global Data States
const [products, setProducts] = useState([]);
const [sales, setSales] = useState([]);
const [customers, setCustomers] = useState([]);
const [expenses, setExpenses] = useState([]);
// Auth Listener
useEffect(() => {
const initAuth = async () => {
if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
try { await signInWithCustomToken(auth, __initial_auth_token); } catch(e){ console.error(e); }
}
};
initAuth();
const unsubscribe = onAuthStateChanged(auth, (u) => {
setUser(u);
if(u && currentTab === 'system') setCurrentTab('customer');
});
return () => unsubscribe();
}, []);
// Data Fetching (FIXED MAPPING LOGIC TO GUARANTEE IDs)
useEffect(() => {
if (!user || !APP_ID) return;
const safeUserId = String(user.uid);
const errHandler = (err) => console.error("Firebase Sync Error:", err);
// Note: { ...d.data(), id: d.id } ensures that document ID overwrites any accidental null ID in the data itself.
const unSubP = onSnapshot(collection(db, 'artifacts', APP_ID, 'users', safeUserId, 'products'), snap => setProducts(snap.docs.map(d => ({...d.data(), id: d.id}))), errHandler);
const unSubS = onSnapshot(collection(db, 'artifacts', APP_ID, 'users', safeUserId, 'sales'), snap => setSales(snap.docs.map(d => ({...d.data(), id: d.id}))), errHandler);
const unSubC = onSnapshot(collection(db, 'artifacts', APP_ID, 'users', safeUserId, 'customers'), snap => setCustomers(snap.docs.map(d => ({...d.data(), id: d.id}))), errHandler);
const unSubE = onSnapshot(collection(db, 'artifacts', APP_ID, 'users', safeUserId, 'expenses'), snap => setExpenses(snap.docs.map(d => ({...d.data(), id: d.id}))), errHandler);
return () => { unSubP(); unSubS(); unSubC(); unSubE(); };
}, [user]);
// Deep Data Connection: Map all sales dynamically into each customer
const customersWithData = useMemo(() => {
return customers.map(c => {
const custSales = sales.filter(s => s.customerId === c.id);
const balance = custSales.reduce((sum, s) => sum + (Number(s.balance) || 0), 0);
const totalSpent = custSales.reduce((sum, s) => sum + (Number(s.grandTotal) || 0), 0);
return { ...c, balance, totalSpent, custSales };
});
}, [customers, sales]);
// Auth Handlers
const handleAuth = async (e, isLogin, email, password) => {
e.preventDefault();
try {
if (isLogin) await signInWithEmailAndPassword(auth, email, password);
else await createUserWithEmailAndPassword(auth, email, password);
} catch (error) { showAlert("အကောင့်ဝင်ရာတွင် အမှားဖြစ်နေပါသည်:\n" + error.message); }
};
const handleGuestLogin = async () => {
try { await signInAnonymously(auth); } catch (error) { showAlert("Guest အနေဖြင့် ဝင်ရောက်ရာတွင် အမှားဖြစ်နေပါသည်:\n" + error.message); }
};
return (
<>
{!user ? (
) : (
{/* Sidebar Navigation */}
{/* Main Content Area */}
{currentTab === 'system' &&
}
{currentTab === 'purchase' &&
}
{currentTab === 'sale' &&
}
{currentTab === 'customer' &&
}
{currentTab === 'expense' &&
}
{currentTab === 'report' &&
}
)}
{/* Global Dialog Modal */}
{dialog.isOpen && (
{dialog.type === 'confirm' ? 'သေချာပါသလား?' : 'အသိပေးချက်'}
{dialog.message}
{dialog.type === 'confirm' && (
)}
)}
>
);
}
// ============================================================================
// COMPONENT: AUTH SCREEN
// ============================================================================
const AuthScreen = ({ handleAuth, handleGuest, t, lang, setLang }) => {
const [isLogin, setIsLogin] = useState(true);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
return (
Nay N Nay
POS
OR
);
};
// ============================================================================
// COMPONENT: SYSTEM TAB
// ============================================================================
const SystemTab = ({ user, t, lang, setLang }) => (
{t.system}
Logged in as: {user.isAnonymous ? "Guest User" : user.email}
);
// ============================================================================
// COMPONENT: PURCHASE TAB (Products)
// ============================================================================
const PurchaseTab = ({ t, products, userId, showAlert, showConfirm }) => {
const [search, setSearch] = useState('');
const [showForm, setShowForm] = useState(false);
const [form, setForm] = useState({ id: null, name: '', cost: '', price: '', qty: '', expDate: '' });
const sortedProducts = useMemo(() => {
return [...products].filter(p => (p.name || '').toLowerCase().includes(search.toLowerCase())).sort((a, b) => (a.name || '').localeCompare(b.name || ''));
}, [products, search]);
const handleSubmit = async (e) => {
e.preventDefault();
try {
const data = { name: form.name, cost: Number(form.cost), price: Number(form.price), quantity: Number(form.qty), expiredDate: form.expDate };
const safeUserId = String(userId);
if (form.id) await updateDoc(doc(db, 'artifacts', APP_ID, 'users', safeUserId, 'products', String(form.id)), data);
else await addDoc(collection(db, 'artifacts', APP_ID, 'users', safeUserId, 'products'), data);
setShowForm(false); setForm({ id: null, name: '', cost: '', price: '', qty: '', expDate: '' });
showAlert("အောင်မြင်စွာ သိမ်းဆည်းပြီးပါပြီ။");
} catch (err) { showAlert("Data သိမ်းဆည်းရာတွင် အမှားဖြစ်နေပါသည်: \n" + err.message); }
};
const deleteItem = (id) => {
if (!id) return;
showConfirm('ဤပစ္စည်းကို စာရင်းမှ ဖယ်ရှားရန် သေချာပါသလား?', async () => {
try { await deleteDoc(doc(db, 'artifacts', APP_ID, 'users', String(userId), 'products', String(id))); showAlert("အောင်မြင်စွာ ဖယ်ရှားပြီးပါပြီ။"); }
catch (err) { showAlert("ဖျက်ရာတွင် အမှားဖြစ်နေပါသည်: \n" + err.message); }
});
};
const getRowStyle = (p) => {
if (!p.expiredDate) return styles.neoCard;
const today = new Date(); const exp = new Date(p.expiredDate);
const monthsDiff = (exp.getFullYear() - today.getFullYear()) * 12 + (exp.getMonth() - today.getMonth());
const isLowQty = p.quantity < 10; const isExpiring = monthsDiff < 3 && monthsDiff >= 0;
if (isLowQty && isExpiring) return "bg-gradient-to-r from-red-200 to-yellow-200 border-red-300";
if (isLowQty) return "bg-red-100 border-red-300";
if (isExpiring) return "bg-yellow-100 border-yellow-300";
return styles.neoCard;
};
return (
{t.purchase} (ပစ္စည်းစာရင်း)
{showForm && (
)}
setSearch(e.target.value)} className={`${styles.neoInput} pl-12`} />
{sortedProducts.length === 0 &&
No products found.
}
{sortedProducts.map(p => (
{p.name}
{t.cost}: {p.cost} | {t.price}: {p.price} | {t.qty}: {p.quantity} | EXP: {p.expiredDate}
))}
);
};
// ============================================================================
// COMPONENT: EXPENSE TAB
// ============================================================================
const ExpenseTab = ({ t, expenses, userId, showAlert, showConfirm }) => {
const [showForm, setShowForm] = useState(false);
const [form, setForm] = useState({ id: null, name: '', amount: '', deductProfit: true });
const handleSubmit = async (e) => {
e.preventDefault();
try {
const data = { name: form.name, amount: Number(form.amount), deductProfit: form.deductProfit, date: new Date().toISOString() };
const safeUserId = String(userId);
if (form.id) await updateDoc(doc(db, 'artifacts', APP_ID, 'users', safeUserId, 'expenses', String(form.id)), data);
else await addDoc(collection(db, 'artifacts', APP_ID, 'users', safeUserId, 'expenses'), data);
setShowForm(false); setForm({ id: null, name: '', amount: '', deductProfit: true });
showAlert("အသုံးစရိတ် သိမ်းဆည်းပြီးပါပြီ။");
} catch (err) { showAlert("Data သိမ်းဆည်းရာတွင် အမှားဖြစ်နေပါသည်:\n" + err.message); }
};
const deleteItem = (id) => {
if (!id) return;
showConfirm('ဤအသုံးစရိတ်ကို ဖယ်ရှားရန် သေချာပါသလား?', async () => {
try { await deleteDoc(doc(db, 'artifacts', APP_ID, 'users', String(userId), 'expenses', String(id))); showAlert("အောင်မြင်စွာ ဖယ်ရှားပြီးပါပြီ။"); }
catch (err) { showAlert("ဖျက်ရာတွင် အမှားဖြစ်နေပါသည်:\n" + err.message); }
});
};
return (
{t.expense}
{showForm && (
)}
{expenses.sort((a,b)=>new Date(b.date)-new Date(a.date)).map(e => (
{e.name}
Amount: {e.amount} | {e.deductProfit ? 'Deducts Profit' : 'Does not deduct'}
))}
);
};
// ============================================================================
// COMPONENT: REPORT TAB
// ============================================================================
const ReportTab = ({ t, products, sales, expenses }) => {
const [filter, setFilter] = useState('daily');
const filteredData = useMemo(() => {
const now = new Date(); let fSales = []; let fExpenses = [];
const isSameDay = (d1, d2) => d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate();
const isSameWeek = (d1, d2) => {
const oneJan = new Date(d1.getFullYear(),0,1); const w1 = Math.ceil((((d1.getTime() - oneJan.getTime()) / 86400000) + oneJan.getDay()+1)/7);
const oneJan2 = new Date(d2.getFullYear(),0,1); const w2 = Math.ceil((((d2.getTime() - oneJan2.getTime()) / 86400000) + oneJan2.getDay()+1)/7);
return w1 === w2 && d1.getFullYear() === d2.getFullYear();
};
const isSameMonth = (d1, d2) => d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth();
const isSameYear = (d1, d2) => d1.getFullYear() === d2.getFullYear();
fSales = sales.filter(s => { const d = new Date(s.date); if(filter==='daily') return isSameDay(d, now); if(filter==='weekly') return isSameWeek(d, now); if(filter==='monthly') return isSameMonth(d, now); if(filter==='yearly') return isSameYear(d, now); return true; });
fExpenses = expenses.filter(e => { if(!e.deductProfit) return false; const d = new Date(e.date); if(filter==='daily') return isSameDay(d, now); if(filter==='weekly') return isSameWeek(d, now); if(filter==='monthly') return isSameMonth(d, now); if(filter==='yearly') return isSameYear(d, now); return true; });
let totalSalePrice = 0; let totalCost = 0; let totalServiceFee = 0; let itemSalesCount = {};
fSales.forEach(s => {
totalServiceFee += Number(s.serviceFee || 0);
s.items.forEach(i => { totalSalePrice += (i.price * i.qty); totalCost += (i.cost * i.qty); itemSalesCount[i.productId] = (itemSalesCount[i.productId] || 0) + i.qty; });
});
const totalExpense = fExpenses.reduce((sum, e) => sum + Number(e.amount), 0);
const netProfit = (totalSalePrice - totalCost) + totalServiceFee - totalExpense;
const bestSellers = Object.keys(itemSalesCount).map(id => { const p = products.find(x => x.id === id); return { name: p ? p.name : 'Unknown', qty: itemSalesCount[id] }; }).sort((a,b) => b.qty - a.qty).slice(0, 10);
return { totalSalePrice: totalSalePrice + totalServiceFee, netProfit, bestSellers, totalExpense };
}, [sales, expenses, products, filter]);
const inventoryAsset = useMemo(() => { return products.reduce((sum, p) => sum + (p.cost * p.quantity), 0); }, [products]);
return (
{t.report}
{['daily', 'weekly', 'monthly', 'yearly', 'grand'].map(f => (
))}
Total Sales ({filter})
{filteredData.totalSalePrice.toLocaleString()}
Net Profit
{filteredData.netProfit.toLocaleString()}
(Sales - Cost + Service Fee - Deductible Expenses)
{t.bestSelling} (Top 10)
{filteredData.bestSellers.length === 0 &&
No data
}
{filteredData.bestSellers.map((b, i) => (
{i+1}. {b.name}{b.qty} sold
))}
{t.inventory}
{inventoryAsset.toLocaleString()}
);
};
// ============================================================================
// COMPONENT: SALE TAB
// ============================================================================
const SaleTab = ({ t, products, customersWithData, sales, userId, showAlert, showConfirm }) => {
const [cart, setCart] = useState([]);
const [selectedProduct, setSelectedProduct] = useState('');
const [qty, setQty] = useState(1);
const [customerId, setCustomerId] = useState('');
const [serviceFee, setServiceFee] = useState(0);
const [payAmountInput, setPayAmountInput] = useState('');
// Real-time Calculation
const itemsTotal = cart.reduce((sum, item) => sum + (item.price * item.qty), 0);
const grandTotal = itemsTotal + Number(serviceFee || 0);
const payAmount = Number(payAmountInput || 0);
const balance = grandTotal - payAmount;
const isPaid = balance <= 0 && grandTotal > 0;
const addToCart = () => {
const p = products.find(x => x.id === selectedProduct);
if (!p) return;
if (p.quantity < qty) return showAlert('စတော့စာရင်းတွင် ပစ္စည်းအရေအတွက် မလောက်ပါ!');
const existing = cart.find(c => c.productId === p.id);
if (existing) {
if (p.quantity < existing.qty + Number(qty)) return showAlert('စတော့စာရင်းတွင် ပစ္စည်းအရေအတွက် မလောက်ပါ!');
setCart(cart.map(c => c.productId === p.id ? { ...c, qty: c.qty + Number(qty) } : c));
} else {
setCart([...cart, { productId: p.id, name: p.name, price: p.price, cost: p.cost, qty: Number(qty) }]);
}
setQty(1); setSelectedProduct('');
};
const removeCart = (idx) => setCart(cart.filter((_, i) => i !== idx));
const handleCheckout = async () => {
if (!customerId) return showAlert('ကျေးဇူးပြု၍ Customer အမည်ကို ရွေးချယ်ပါ။ (မရှိပါက Customer စာမျက်နှာတွင် အရင်ထည့်ပါ)');
if (cart.length === 0) return showAlert('ရောင်းချရန် ပစ္စည်းများကို ခြင်းထဲသို့ အရင်ထည့်ပါ။');
if (payAmount > grandTotal) return showAlert('ပေးချေငွေသည် စုစုပေါင်းကျသင့်ငွေထက် များနေပါသည်။');
try {
const customerObj = customersWithData.find(c => c.id === customerId);
const safeUserId = String(userId);
const saleDateString = new Date().toISOString();
const saleData = {
customerId: customerId,
customerName: customerObj ? customerObj.name : 'Unknown',
date: saleDateString,
items: cart,
itemsTotal: itemsTotal,
serviceFee: Number(serviceFee),
grandTotal: grandTotal,
payAmount: payAmount,
balance: balance > 0 ? balance : 0,
status: isPaid ? 'Paid' : 'Unpaid'
};
for (let item of cart) {
const p = products.find(x => x.id === item.productId);
if (p && p.id) {
await updateDoc(doc(db, 'artifacts', APP_ID, 'users', safeUserId, 'products', String(p.id)), {
quantity: p.quantity - item.qty
});
}
}
await addDoc(collection(db, 'artifacts', APP_ID, 'users', safeUserId, 'sales'), saleData);
setCart([]); setCustomerId(''); setServiceFee(0); setPayAmountInput('');
showAlert("အောင်မြင်စွာ ရောင်းချပြီးပါပြီ။\nသက်ဆိုင်ရာ Customer ၏ မှတ်တမ်းထဲသို့ တိုက်ရိုက် ရောက်ရှိသွားပါမည်။");
} catch (err) { showAlert("ရောင်းချရာတွင် အမှားဖြစ်နေပါသည်: \n" + err.message); }
};
const deleteSale = (sale) => {
if (!sale || !sale.id) return;
showConfirm('ဤရောင်းချမှုမှတ်တမ်းကို ဖျက်ပြီး ပစ္စည်းအရေအတွက်ကို စာရင်းထဲသို့ ပြန်ပေါင်းထည့်မလား?', async () => {
try {
const safeUserId = String(userId);
for (let item of sale.items) {
const p = products.find(x => x.id === item.productId);
if (p && p.id) {
await updateDoc(doc(db, 'artifacts', APP_ID, 'users', safeUserId, 'products', String(p.id)), {
quantity: p.quantity + item.qty
});
}
}
await deleteDoc(doc(db, 'artifacts', APP_ID, 'users', safeUserId, 'sales', String(sale.id)));
showAlert("ရောင်းချမှုမှတ်တမ်းကို ဖျက်သိမ်းပြီး ပစ္စည်းများကို ပြန်လည်ပေါင်းထည့်ပြီးပါပြီ။");
} catch (err) { showAlert("ဖျက်ရာတွင် အမှားဖြစ်နေပါသည်:\n" + err.message); }
});
};
return (
{t.sale} (ဘေလ်ဖွင့်ရန်)
{cart.length === 0 &&
ခြင်းထဲတွင် ပစ္စည်းမရှိသေးပါ
}
{cart.map((c, i) => (
{c.name}
{c.price} x {c.qty}
{(c.price * c.qty).toLocaleString()}
))}
ယနေ့ အရောင်းမှတ်တမ်းများ
{sales.length === 0 &&
မှတ်တမ်းမရှိသေးပါ။
}
{sales.sort((a,b) => new Date(b.date) - new Date(a.date)).slice(0, 15).map(s => {
const customerNameDisplay = s.customerName || 'Unknown';
return (
{customerNameDisplay}
{new Date(s.date).toLocaleTimeString('en-US', {hour:'2-digit', minute:'2-digit'})}
{s.status}
{s.items.map((i, idx) => (
{i.name} x{i.qty}
))}
Total: {s.grandTotal.toLocaleString()}
{s.balance > 0 && Bal: {s.balance.toLocaleString()}}
);
})}
);
};
// ============================================================================
// COMPONENT: CUSTOMER TAB
// ============================================================================
const CustomerTab = ({ t, customersWithData, sales, userId, showAlert, showConfirm }) => {
const [activeView, setActiveView] = useState('list');
const [search, setSearch] = useState('');
const [sortOrder, setSortOrder] = useState('alpha');
const [form, setForm] = useState({ id: null, name: '', age: '', gender: 'Male', phone1: '', phone2: '', phone3: '', diagnosis: '' });
const [selectedCustomerId, setSelectedCustomerId] = useState(null);
const [payAmountInput, setPayAmountInput] = useState('');
const sortedCustomers = useMemo(() => {
let res = [...customersWithData].filter(c => (c.name || '').toLowerCase().includes(search.toLowerCase()));
if (sortOrder === 'alpha') res.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
else res.sort((a, b) => b.balance - a.balance);
return res;
}, [customersWithData, search, sortOrder]);
const openForm = (c = null) => {
if (c) {
setForm({
id: c.id,
name: c.name,
age: c.age || '',
gender: c.gender || 'Male',
phone1: c.phone1 || '',
phone2: c.phone2 || '',
phone3: c.phone3 || '',
diagnosis: c.diagnosis || ''
});
} else {
setForm({ id: null, name: '', age: '', gender: 'Male', phone1: '', phone2: '', phone3: '', diagnosis: '' });
}
setActiveView('form');
};
const openDetail = (id) => {
setSelectedCustomerId(id);
setPayAmountInput('');
setActiveView('detail');
};
const handleSaveCustomer = async (e) => {
e.preventDefault();
try {
const safeUserId = String(userId);
// FIXED: Extract id out of form to prevent overwriting document properties incorrectly
const { id, ...customerData } = form;
if (id) {
await updateDoc(doc(db, 'artifacts', APP_ID, 'users', safeUserId, 'customers', String(id)), customerData);
showAlert("Customer အချက်အလက်များကို အောင်မြင်စွာ ပြင်ဆင်ပြီးပါပြီ။");
} else {
await addDoc(collection(db, 'artifacts', APP_ID, 'users', safeUserId, 'customers'), customerData);
showAlert("Customer အချက်အလက်ကို အောင်မြင်စွာ သိမ်းဆည်းပြီးပါပြီ။");
}
setActiveView('list');
} catch (err) { showAlert("Data သိမ်းဆည်းရာတွင် အမှားဖြစ်နေပါသည်:\n" + err.message); }
};
const handleDeleteCustomer = (id) => {
if (!id) return;
showConfirm('ဤဖောက်သည်၏ အချက်အလက်များကို အပြီးတိုင် ဖယ်ရှားရန် သေချာပါသလား?', async () => {
try {
await deleteDoc(doc(db, 'artifacts', APP_ID, 'users', String(userId), 'customers', String(id)));
showAlert("အောင်မြင်စွာ ဖယ်ရှားပြီးပါပြီ။");
if(activeView === 'detail') setActiveView('list');
} catch (err) { showAlert("ဖျက်ရာတွင် အမှားဖြစ်နေပါသည်:\n" + err.message); }
});
};
const handleIndividualPayment = (customerId, totalUnpaidBalance) => {
const payment = Number(payAmountInput);
if (!payment || payment <= 0) return showAlert("ပေးသွင်းငွေ ပမာဏကို မှန်ကန်စွာ ထည့်ပါ။");
if (payment > totalUnpaidBalance) return showAlert(`ပေးသွင်းငွေသည် လက်ကျန်အကြွေး (${totalUnpaidBalance.toLocaleString()}) ထက် များနေပါသည်။`);
showConfirm(`${payment.toLocaleString()} ကျပ် ငွေချေရန် သေချာပါသလား?\n\n(စနစ်မှ အဟောင်းဆုံး အကြွေးဘေလ်များကို အရင်ဆုံး အလိုအလျောက် ဖြတ်တောက် ရှင်းလင်းပေးသွားပါမည်။)`, async () => {
try {
const safeUserId = String(userId);
let amountLeftToDistribute = payment;
const unpaidSales = sales
.filter(s => s.customerId === customerId && s.status === 'Unpaid' && Number(s.balance) > 0)
.sort((a, b) => new Date(a.date) - new Date(b.date));
for (let s of unpaidSales) {
if (amountLeftToDistribute <= 0) break;
let currentSaleBalance = Number(s.balance);
let currentPaidAmount = Number(s.payAmount || 0);
if (amountLeftToDistribute >= currentSaleBalance) {
amountLeftToDistribute -= currentSaleBalance;
await updateDoc(doc(db, 'artifacts', APP_ID, 'users', safeUserId, 'sales', String(s.id)), {
balance: 0, payAmount: currentPaidAmount + currentSaleBalance, status: 'Paid'
});
} else {
await updateDoc(doc(db, 'artifacts', APP_ID, 'users', safeUserId, 'sales', String(s.id)), {
balance: currentSaleBalance - amountLeftToDistribute, payAmount: currentPaidAmount + amountLeftToDistribute
});
amountLeftToDistribute = 0;
}
}
setPayAmountInput('');
showAlert("ငွေချေမှု အောင်မြင်ပါသည်။\nသက်ဆိုင်ရာ အရောင်းမှတ်တမ်းများတွင် အကြွေးများ အလိုအလျောက် ရှင်းလင်းသွားပါပြီ။");
} catch (err) { showAlert("ငွေချေရာတွင် အမှားဖြစ်နေပါသည်:\n" + err.message); }
});
};
if (activeView === 'form') {
return (
{form.id ? 'ဖောက်သည် အချက်အလက် ပြင်ဆင်ရန်' : 'ဖောက်သည် အသစ်ထည့်ရန်'}
);
}
if (activeView === 'detail' && selectedCustomerId) {
const customer = customersWithData.find(c => c.id === selectedCustomerId);
if (!customer) { setActiveView('list'); return null; }
const customerSales = customer.custSales.sort((a, b) => new Date(b.date) - new Date(a.date));
return (
ဖောက်သည် အသေးစိတ်မှတ်တမ်း
{customer.name}
{customer.gender} {customer.age && `| ${customer.age} နှစ်`}
{customer.phone1 || 'ဖုန်းနံပါတ် မရှိပါ'}
{customer.phone2 &&
{customer.phone2}
}
{customer.phone3 &&
{customer.phone3}
}
{customer.diagnosis && (
)}
စုစုပေါင်း ဝယ်ယူထားမှု
{customer.totalSpent.toLocaleString()}
ဝယ်ယူမှု အကြိမ်အရေအတွက်
{customer.custSales.length} ကြိမ်
လက်ရှိ အကြွေးကျန်
0 ? 'text-red-600' : 'text-green-600'}`}>
{customer.balance.toLocaleString()}
{customer.balance > 0 ? (
ငွေချေရန် ပမာဏ
setPayAmountInput(e.target.value)}
placeholder="0"
className={`${styles.neoInput} text-center font-bold text-xl h-14 text-green-700 bg-white/70`}
/>
မှတ်ချက်: အဟောင်းဆုံး ဝယ်ယူမှုအကြွေးများမှစ၍ အလိုအလျောက် အဆင့်ဆင့် ဖြတ်တောက် ရှင်းလင်းသွားပါမည်။
) : (
အကြွေးကင်းရှင်းပါသည်
)}
ဝယ်ယူမှု မှတ်တမ်းများ
{customerSales.length === 0 && (
ဝယ်ယူမှု မှတ်တမ်းမရှိသေးပါ။
)}
{customerSales.map((s, index) => (
#{customerSales.length - index}
{new Date(s.date).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' })}
|
{new Date(s.date).toLocaleTimeString('en-US', { hour: '2-digit', minute:'2-digit' })}
{s.status === 'Paid' ? 'ငွေချေပြီး' : 'အကြွေး'}
{s.items.map((i, idx) => (
{i.name}
x{i.qty}
{(i.price * i.qty).toLocaleString()}
))}
ပစ္စည်းတန်ဖိုး:{s.itemsTotal.toLocaleString()}
ဝန်ဆောင်ခ:{s.serviceFee || 0}
စုစုပေါင်း (Total):{s.grandTotal.toLocaleString()}
ပေးချေပြီး:{s.payAmount ? s.payAmount.toLocaleString() : 0}
0 ? 'text-red-500 text-lg' : 'text-gray-400'}`}>
အကြွေးကျန်:{s.balance.toLocaleString()}
))}
);
}
return (
ဖောက်သည်စာရင်း (Customers)
{sortedCustomers.length === 0 &&
ဖောက်သည်စာရင်း မရှိသေးပါ။
}
{sortedCustomers.map(c => (
openDetail(c.id)}>
{c.name.charAt(0).toUpperCase()}
{c.name}
{c.phone1 || '-'}
လက်ကျန်ငွေ
0 ? 'text-red-500' : 'text-green-500'}`}>{c.balance > 0 ? c.balance.toLocaleString() : '0'}
{c.totalSpent.toLocaleString()}
{c.custSales.length} ကြိမ်
))}
);
};