import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { initializeApp } from 'firebase/app';
import {
getAuth,
signInAnonymously,
signInWithCustomToken,
onAuthStateChanged
} from 'firebase/auth';
import {
getFirestore,
collection,
addDoc,
onSnapshot,
doc,
updateDoc,
deleteDoc,
setDoc,
increment,
serverTimestamp
} from 'firebase/firestore';
import {
ShoppingBag, Clock, Trash2, Gift, User, LogOut, ArrowLeft,
X, Phone, Lock, Loader2, Landmark, ConciergeBell,
ChevronDown, Tag, Activity, Sparkles, MapPin, IdCard, Home, Leaf, Mail,
Calendar, CalendarCheck, Waves, LayoutDashboard, Receipt, Plus, TrendingUp,
TrendingDown, WifiOff, Trophy, RefreshCcw, Box, Copy, Upload,
CheckCircle, Eye, Zap, Play, Check, CreditCard, DollarSign, Ticket
} from 'lucide-react';
// --- Safe Firebase Initialization ---
let app, auth, db;
let initError = null;
try {
if (typeof __firebase_config !== 'undefined') {
const config = JSON.parse(__firebase_config);
app = initializeApp(config);
auth = getAuth(app);
db = getFirestore(app);
} else {
initError = "Firebase Config Missing";
}
} catch (e) {
console.error("Firebase Init Error:", e);
initError = e.message;
}
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
// --- CONFIGURATION ---
const DUITNOW_QR_URL = "https://i.ibb.co/p99J4w8/IMG-20260204-143833-346.jpg";
const BUSINESS_BANK_DETAILS = {
bankName: "RHB Bank",
accountName: "NUR NAJWA NAZMINA BINTI AZMAN",
accountNumber: "11306160255276"
};
const BACKGROUNDS = {
welcome: "https://images.unsplash.com/photo-1610832958506-aa56368176cf?q=80&w=2670&auto=format&fit=crop",
physio: "https://images.unsplash.com/photo-1576091160399-112ba8d25d1d?q=80&w=2670&auto=format&fit=crop",
massage: "https://images.unsplash.com/photo-1544161515-4ab6ce6db874?q=80&w=2670&auto=format&fit=crop",
juice: "https://images.unsplash.com/photo-1621506289937-a8e4df240d0b?q=80&w=2574&auto=format&fit=crop",
swimming: "https://images.unsplash.com/photo-1530549387789-4c1017266635?q=80&w=2670&auto=format&fit=crop",
sports: "https://images.unsplash.com/photo-1626224583764-84786c71971e?q=80&w=2670&auto=format&fit=crop"
};
const SERVICES = [
{
id: 'juice', name: 'Fruit Juice', emoji: '๐',
color: 'bg-orange-50 text-orange-600', gradient: 'from-orange-400 to-amber-500',
description: 'Freshly squeezed goodness', subText: '7am - 6pm (Delivery @ 8pm)'
},
{
id: 'physio', name: 'Physiotherapy', emoji: '๐ฉบ',
color: 'bg-teal-50 text-teal-600', gradient: 'from-teal-500 to-emerald-600',
description: 'Professional recovery & rehab'
},
{
id: 'massage', name: 'Massage Service', emoji: '๐',
color: 'bg-purple-50 text-purple-600', gradient: 'from-purple-500 to-indigo-600',
description: 'Relax & rejuvenate'
},
{
id: 'swimming', name: 'Swimming', emoji: '๐',
color: 'bg-blue-50 text-blue-600', gradient: 'from-blue-400 to-cyan-500',
description: 'Coaching & Equipment Rental'
},
{
id: 'sports', name: 'Sports Rental', emoji: '๐ธ',
color: 'bg-red-50 text-red-600', gradient: 'from-red-500 to-pink-600',
description: 'Rackets & Balls (4 Hours)'
},
];
const JUICE_MENU = [
{ id: 'j1', type: 'juice', name: "Fresh Watermelon", emoji: "๐", color: "bg-red-50 text-red-600" },
{ id: 'j2', type: 'juice', name: "Squeezed Orange", emoji: "๐", color: "bg-orange-50 text-orange-600" },
{ id: 'j3', type: 'juice', name: "Zesty Lemonade", emoji: "๐", color: "bg-yellow-50 text-yellow-600" },
{ id: 'j4', type: 'juice', name: "Tropical Pineapple", emoji: "๐", color: "bg-amber-50 text-amber-600" },
];
const PHYSIO_MENU = [
{ id: 'p1', type: 'physio', name: "Physiotherapy Session", emoji: "๐ฉบ", color: "bg-teal-50 text-teal-600", basePrice: 0 }
];
const MASSAGE_MENU = [
{ id: 'm1', type: 'massage', name: "Express Massage (30m)", emoji: "๐", color: "bg-purple-50 text-purple-600", price: 35.00 },
{ id: 'm2', type: 'massage', name: "Full Body (60m)", emoji: "๐ง", color: "bg-indigo-50 text-indigo-600", price: 70.00 },
{ id: 'm3', type: 'massage', name: "Royal Full Body (120m)", emoji: "๐", color: "bg-amber-50 text-amber-600", price: 130.00 },
];
const SWIMMING_MENU = [
{ id: 's1', type: 'swimming_coaching', name: "Swimming Coaching", emoji: "๐โโ๏ธ", color: "bg-blue-50 text-blue-600", price: 15.00 },
{ id: 's2', type: 'swimming_rental', name: "Swimming Goggles", emoji: "๐ฅฝ", color: "bg-cyan-50 text-cyan-600", price: 3.00, stockKey: 'swim_goggles' },
{ id: 's3', type: 'swimming_rental', name: "Kickboard", emoji: "๐โโ๏ธ", color: "bg-sky-50 text-sky-600", price: 3.00, stockKey: 'swim_kickboard' },
{ id: 's4', type: 'swimming_rental', name: "Swimming Cap", emoji: "๐งข", color: "bg-indigo-50 text-indigo-600", price: 2.00, stockKey: 'swim_cap' },
{ id: 's5', type: 'swimming_rental', name: "Pool Noodles", emoji: "๐", color: "bg-yellow-50 text-yellow-600", price: 3.00, stockKey: 'swim_noodles' },
];
const SPORTS_MENU = [
{ id: 'sp1', type: 'sports_rental', name: "Badminton Racket", emoji: "๐ธ", color: "bg-red-50 text-red-600", price: 3.00, bundle: true, stockKey: 'racket_badminton' },
{ id: 'sp2', type: 'sports_rental', name: "Squash Racket", emoji: "๐พ", color: "bg-orange-50 text-orange-600", price: 3.00, bundle: true, stockKey: 'racket_squash' },
{ id: 'sp3', type: 'sports_rental', name: "Tennis Racket", emoji: "๐พ", color: "bg-green-50 text-green-600", price: 3.00, bundle: true, stockKey: 'racket_tennis' },
{ id: 'sp4', type: 'sports_rental', name: "Squash Ball", emoji: "โซ", color: "bg-gray-50 text-gray-800", price: 2.00 },
{ id: 'sp5', type: 'sports_rental', name: "Tennis Ball", emoji: "๐พ", color: "bg-yellow-50 text-yellow-600", price: 2.00 },
{ id: 'sp6', type: 'sports_sale', name: "Shuttlecock (Buy)", emoji: "๐ธ", color: "bg-stone-50 text-stone-600", price: 5.00 },
];
const INITIAL_STOCK = {
racket_badminton: 4,
racket_squash: 2,
racket_tennis: 2,
swim_goggles: 4,
swim_kickboard: 4,
swim_cap: 4,
swim_noodles: 4
};
// --- Main App Component ---
export default function App() {
const [user, setUser] = useState(null);
const [viewMode, setViewMode] = useState('welcome');
const [selectedServiceId, setSelectedServiceId] = useState(null);
const [isOffline, setIsOffline] = useState(false);
const [cart, setCart] = useState([]);
const [orders, setOrders] = useState([]);
const [expenses, setExpenses] = useState([]);
const [inventory, setInventory] = useState(INITIAL_STOCK);
const [activeVoucher, setActiveVoucher] = useState(null);
const [isLoading, setIsLoading] = useState(false);
// Dashboard & Auth
const [staffTab, setStaffTab] = useState('orders');
const [staffFilterService, setStaffFilterService] = useState('all');
const [staffPassword, setStaffPassword] = useState('');
const [staffAuthError, setStaffAuthError] = useState('');
// Forms for Expenses
const [expenseName, setExpenseName] = useState('');
const [expenseCost, setExpenseCost] = useState('');
const [expenseCategory, setExpenseCategory] = useState('stock');
// UI State
const [isCartExpanded, setIsCartExpanded] = useState(false);
const [customizingItem, setCustomizingItem] = useState(null);
// Customization State
const [sugarLevel, setSugarLevel] = useState('No Sugar');
const [cupSize, setCupSize] = useState('Medium');
const [physioRate, setPhysioRate] = useState('public');
const [massageArea, setMassageArea] = useState('Head & Shoulder');
const [massageAddOn, setMassageAddOn] = useState(false);
const [swimCapType, setSwimCapType] = useState('Silicone');
// Customer Data
const [customerName, setCustomerName] = useState('');
const [customerPhone, setCustomerPhone] = useState('');
const [customerEmail, setCustomerEmail] = useState('');
const [customerBlock, setCustomerBlock] = useState('');
const [roomNumber, setRoomNumber] = useState('');
const [matricNo, setMatricNo] = useState('');
const [staffId, setStaffId] = useState('');
const [homeAddress, setHomeAddress] = useState('');
const [massageClientType, setMassageClientType] = useState('student');
// Booking & Payment
const [deliveryMode, setDeliveryMode] = useState('lobby');
const [paymentMethod, setPaymentMethod] = useState('cash');
const [voucherInput, setVoucherInput] = useState('');
const [voucherMsg, setVoucherMsg] = useState('');
const [appliedVoucher, setAppliedVoucher] = useState(null);
const [discountPercent, setDiscountPercent] = useState(0);
// Modals
const [showPaymentModal, setShowPaymentModal] = useState(false);
const [receiptImage, setReceiptImage] = useState(null);
const [viewingReceipt, setViewingReceipt] = useState(null);
// Game State
const [showGameModal, setShowGameModal] = useState(false);
const [gameBoard, setGameBoard] = useState(Array(9).fill(null));
const [gameStatus, setGameStatus] = useState('intro');
const [isPlayerTurn, setIsPlayerTurn] = useState(true);
const [trialCustomer, setTrialCustomer] = useState({ name: '', matric: '', choice: 'Watermelon' });
// --- Logic Helpers ---
const cartHasMobileService = useMemo(() => cart.some(item => ['physio', 'massage'].includes(item.type)), [cart]);
const cartHasCoaching = useMemo(() => cart.some(item => item.type === 'swimming_coaching'), [cart]);
const cartHasAppointment = useMemo(() => cartHasMobileService || cartHasCoaching, [cartHasMobileService, cartHasCoaching]);
const getEffectiveClientType = () => {
if (cart.some(i => i.type === 'physio')) {
const item = cart.find(i => i.type === 'physio');
if (item.variant?.includes('Student')) return 'student';
if (item.variant?.includes('Staff')) return 'staff';
return 'public';
}
return cart.some(i => i.type === 'massage') ? massageClientType : 'public';
};
const availableDates = useMemo(() => {
const dates = [];
const today = new Date();
for (let i = 0; i < 7; i++) {
const d = new Date(today);
d.setDate(today.getDate() + i);
dates.push(d);
}
return dates;
}, []);
const getSlotsForItem = (dateObj, itemType) => {
if (!dateObj) return [];
const day = dateObj.getDay();
if (['physio', 'massage'].includes(itemType)) {
if (day === 1 || day === 5) return ['22:00'];
if (day >= 2 && day <= 4) return ['20:00', '21:00'];
if (day === 6) return ['18:00', '19:00', '20:00', '21:00'];
} else if (itemType === 'swimming_coaching') {
if (day === 1 || day === 5) return ['20:00'];
}
return [];
};
const isSlotBooked = (dateStr, timeStr, currentCartIndex = -1) => {
const inOrders = orders.some(o => o.status !== 'cancelled' && o.items?.some(i => i.date === dateStr && i.time === timeStr));
const inCart = cart.some((c, idx) => idx !== currentCartIndex && c.date === dateStr && c.time === timeStr);
return inOrders || inCart;
};
const setItemSchedule = (index, dateObj, timeStr) => {
setCart(prev => prev.map((item, i) => {
if (i === index) {
return {
...item,
dateObj,
date: dateObj?.toLocaleDateString() || null,
time: timeStr || null
};
}
return item;
}));
};
const initiateAdd = (item) => {
if (item.stockKey && inventory[item.stockKey] <= 0) {
alert("Sorry, this item is currently out of stock!");
return;
}
setCustomizingItem(item);
setSugarLevel('No Sugar');
setCupSize('Medium');
setPhysioRate('public');
setMassageArea('Head & Shoulder');
setMassageAddOn(false);
setSwimCapType('Silicone');
};
const getPrice = () => {
if (!customizingItem) return 0;
if (customizingItem.type === 'juice') {
if (cupSize === 'Small') return 5.00;
if (cupSize === 'Medium') return 7.00;
if (cupSize === 'Large') return 8.00;
}
if (customizingItem.type === 'physio') {
if (physioRate === 'student') return 25.00;
if (physioRate === 'staff') return 45.00;
return 80.00;
}
if (customizingItem.type === 'massage') {
return (customizingItem.price + (massageAddOn ? 6.00 : 0));
}
return customizingItem.price || 0;
};
const generateVariantString = () => {
if (!customizingItem) return "Standard";
if (customizingItem.type === 'juice') {
return `${cupSize}, ${sugarLevel}`;
}
if (customizingItem.type === 'physio') {
const labels = { student: 'IIUM Student', staff: 'IIUM Staff', public: 'Public' };
return labels[physioRate] || "Standard";
}
if (customizingItem.type === 'massage') {
let details = "";
if (customizingItem.id === 'm1') details += `Area: ${massageArea}`;
if (massageAddOn) details += `${details ? ', ' : ''}+10mins Add-on`;
return details || "Standard";
}
if (customizingItem.id === 's4') {
return `Type: ${swimCapType}`;
}
return "Standard";
};
const confirmAddToCart = () => {
if (!customizingItem) return;
const finalPrice = getPrice();
const variantDetails = generateVariantString();
setCart(prev => [...prev, {
id: customizingItem.id,
qty: 1,
variant: variantDetails,
price: finalPrice,
type: customizingItem.type,
name: customizingItem.name,
stockKey: customizingItem.stockKey || null,
bundle: customizingItem.bundle || false,
date: null,
time: null
}]);
setCustomizingItem(null);
};
const handleApplyVoucher = () => {
const code = (voucherInput || '').trim().toUpperCase();
if (code.includes('JACKFER')) {
setDiscountPercent(0.10);
setAppliedVoucher({ code, percent: 10 });
setVoucherMsg('Voucher applied: 10% off โ
');
} else {
setDiscountPercent(0);
setAppliedVoucher(null);
setVoucherMsg('Invalid voucher code โ');
}
};
const calculateTotal = () => {
const subtotal = cart.reduce((acc, item) => {
if (item.bundle) {
return acc + (Math.floor(item.qty/2)*5) + ((item.qty%2)*item.price);
}
return acc + (item.price * item.qty);
}, 0);
const discountAmount = subtotal * discountPercent;
let deliveryFee = (!cartHasAppointment && deliveryMode === 'room') ? 0.50 : 0;
return subtotal + deliveryFee - discountAmount;
};
const removeFromCart = (index) => {
setCart(prev => prev.filter((_, i) => i !== index));
};
const handleCheckout = () => {
if (!customerName || !customerPhone || !customerEmail) {
alert("Please fill in basic contact details (Name, Phone, Email).");
return;
}
for (const item of cart) {
if (['physio', 'massage', 'swimming_coaching'].includes(item.type)) {
if (!item.date || !item.time) {
alert(`Please select a date and time for your ${item.name} appointment.`);
return;
}
}
}
if (paymentMethod === 'qr') {
setShowPaymentModal(true);
} else {
submitOrder();
}
};
const submitOrder = async () => {
if (!user) return;
setIsLoading(true);
try {
const finalTotal = calculateTotal();
// Clean undefined fields for Firestore
const cleanedCart = cart.map(({ dateObj, ...rest }) => ({
...rest,
id: rest.id || null,
stockKey: rest.stockKey || null,
date: rest.date || null,
time: rest.time || null
}));
const orderData = {
items: cleanedCart,
subtotal: finalTotal + (finalTotal * discountPercent),
discount: finalTotal * discountPercent,
total: finalTotal,
status: 'pending',
timestamp: Date.now(),
customerCode: Math.floor(Math.random()*9000)+1000,
userId: user.uid,
receiptImage: paymentMethod === 'qr' ? receiptImage : null,
customerDetails: {
name: customerName || 'N/A',
phone: customerPhone || 'N/A',
email: customerEmail || 'N/A',
matricNo: matricNo || 'N/A',
staffId: staffId || 'N/A',
block: customerBlock || 'N/A',
roomNumber: roomNumber || 'N/A',
homeAddress: homeAddress || 'N/A',
deliveryMode,
paymentMethod,
bank: paymentMethod === 'qr' ? 'RHB QR' : 'Cash',
clientType: cartHasMobileService ? getEffectiveClientType() : 'standard'
}
};
await addDoc(collection(db, 'artifacts', appId, 'public', 'data', 'orders'), orderData);
// Update inventory safely
const invRef = doc(db, 'artifacts', appId, 'public', 'data', 'inventory_main', 'stock');
for(let i of cart) {
if(i.stockKey) {
await updateDoc(invRef, {[i.stockKey]: increment(-i.qty)});
}
}
// Send Email via EmailJS
if (customerEmail && customerEmail !== 'N/A') {
fetch('https://api.emailjs.com/api/v1.0/email/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
service_id: 'service_3kj1ch1',
template_id: 'template_kd13abi',
user_id: 'diDwLTYFVg4PBmisN',
template_params: {
customer_name: customerName,
customer_email: customerEmail,
total_amount: finalTotal.toFixed(2),
voucher_code: `JACKFER${Math.floor(Math.random()*100)}`
}
})
}).catch(e => console.error("Email API silent fail:", e));
}
setActiveVoucher({ code: `JACKFER${Math.floor(Math.random()*100)}`, amount: "10%", email: customerEmail });
setCart([]);
setReceiptImage(null);
setShowPaymentModal(false);
setIsCartExpanded(false);
} catch(e) {
console.error("Database Error:", e);
alert("There was an error submitting your order. Please refresh and try again.");
} finally {
setIsLoading(false);
}
};
const handleReceiptUpload = (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const MAX_WIDTH = 600;
canvas.width = MAX_WIDTH;
canvas.height = img.height * (MAX_WIDTH / img.width);
canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);
setReceiptImage(canvas.toDataURL('image/jpeg', 0.6));
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
};
const handleStaffLogin = () => {
if (staffPassword === 'jackfer2025') {
setViewMode('staff');
setStaffPassword('');
setStaffAuthError('');
} else {
setStaffAuthError('Invalid Admin Password');
}
};
const toggleOrderStatus = async (id, cur) => {
await updateDoc(doc(db, 'artifacts', appId, 'public', 'data', 'orders', id), { status: cur === 'pending' ? 'completed' : 'pending' });
};
const addExpense = async () => {
if (!user || !expenseName || !expenseCost) return;
await addDoc(collection(db, 'artifacts', appId, 'public', 'data', 'expenses'), {
item: expenseName,
cost: parseFloat(expenseCost),
category: expenseCategory,
timestamp: Date.now(),
userId: user.uid
});
setExpenseName('');
setExpenseCost('');
};
const deleteExpense = async (id) => {
if(user) await deleteDoc(doc(db, 'artifacts', appId, 'public', 'data', 'expenses', id));
};
// Staff Memo Filters
const filteredStaffOrders = useMemo(() => {
return orders.filter(o => staffFilterService === 'all' || o.items.some(i => i.type.includes(staffFilterService)));
}, [orders, staffFilterService]);
const verifyOrders = useMemo(() => {
return orders.filter(o => o.customerDetails?.paymentMethod === 'qr' || o.receiptImage);
}, [orders]);
const financialStats = useMemo(() => {
const totalRevenue = orders.filter(o => o.status === 'completed').reduce((acc, o) => acc + o.total, 0);
const totalExpenses = expenses.reduce((acc, e) => acc + e.cost, 0);
return { totalRevenue, totalExpenses, netProfit: totalRevenue - totalExpenses };
}, [orders, expenses]);
// Tic Tac Toe Init
const initGame = () => {
setGameBoard(Array(9).fill(null));
setGameStatus('playing');
setIsPlayerTurn(true);
};
// --- Auth & Listeners ---
useEffect(() => {
if (!auth) return;
const initAuth = async () => {
try {
if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
await signInWithCustomToken(auth, __initial_auth_token);
} else {
await signInAnonymously(auth);
}
} catch (e) {
console.error("Auth:", e);
}
};
initAuth();
return onAuthStateChanged(auth, setUser);
}, []);
const AnimatedServiceLogo = () => (
);
const handleSelectService = (serviceId) => {
setSelectedServiceId(serviceId);
setViewMode('customer_menu');
};
if (initError) {
return {initError}
;
}
return (
{/* WELCOME SCREEN */}
{viewMode === 'welcome' && (
Bisnes Jackfer
Premium Wellness & Freshness
)}
{/* STAFF AUTH SCREEN */}
{viewMode === 'staff_auth' && (
Staff Login
setStaffPassword(e.target.value)}
placeholder="Enter Password"
className="w-full bg-slate-50 border-2 border-slate-100 p-4 rounded-xl mb-4 text-center font-bold outline-none focus:border-gray-900 transition-all text-gray-900"
/>
{staffAuthError && (
{staffAuthError}
)}
)}
{/* CUSTOMER HUB (Service Selection) */}
{viewMode === 'customer_hub' && (
{SERVICES.map((s) => (
))}
)}
{/* SPECIFIC MENU SCREEN */}
{viewMode === 'customer_menu' && (
Menu
{/* Notice Banners */}
{selectedServiceId === 'juice' && (
Operation Hours: 7:00 AM - 6:00 PM
Note: All orders will be delivered starting from 8:00 PM.
)}
{/* Game Banner for Juice */}
{selectedServiceId === 'juice' && (
{ setShowGameModal(true); initGame(); }} className="mb-8 bg-gradient-to-r from-orange-400 via-red-500 to-pink-500 rounded-[2rem] p-6 text-white cursor-pointer shadow-lg relative overflow-hidden group hover:scale-[1.02] transition-transform duration-300">
Free Trial
FIGHT FOR JUICE!
Beat the bot in Tic-Tac-Toe to win.
)}
{/* Items Grid */}
{(selectedServiceId==='juice'?JUICE_MENU:selectedServiceId==='physio'?PHYSIO_MENU:selectedServiceId==='massage'?MASSAGE_MENU:selectedServiceId==='swimming'?SWIMMING_MENU:SPORTS_MENU).map(item => {
const isSoldOut = item.stockKey && (inventory[item.stockKey] || 0) <= 0;
return (
{item.emoji}
{item.name}
{item.type === 'juice' ? 'Custom Sugar' : item.type === 'physio' ? 'Student/Staff Rates' : `RM ${item.price?.toFixed(2)}`}
{item.stockKey && (
{isSoldOut ? 'Sold Out' : `Stock: ${inventory[item.stockKey]}`}
)}
)})}
{/* FLOATING CART BAR */}
{cart.length > 0 && !isCartExpanded && (
setIsCartExpanded(true)}>
{cart.length}
Checkout Amount
RM {calculateTotal().toFixed(2)}
)}
)}
{/* STAFF DASHBOARD */}
{viewMode === 'staff' && (
{/* ADMIN ORDERS TAB */}
{staffTab === 'orders' && filteredStaffOrders.map(o => (
#{o.customerCode}
{o.status}
{o.items?.map((i,x)=>(
{i.qty}x
{i.name}
{i.variant}
{i.date && (
{i.date} @ {i.time}
)}
RM {i.price?.toFixed(2)}
))}
{o.customerDetails?.name}
{o.customerDetails?.phone}
{o.customerDetails?.homeAddress !== 'N/A' && (
{o.customerDetails?.homeAddress}
)}
RM {o.total?.toFixed(2)}
))}
{/* ADMIN VERIFY TAB */}
{staffTab === 'verify' && verifyOrders.map(order => (
#{order.customerCode}
{order.customerDetails.paymentMethod}
{order.customerDetails.name} โข RM {order.total.toFixed(2)}
{order.items?.map((i,x)=>(
{i.qty}x {i.name}
))}
order.receiptImage && setViewingReceipt(order.receiptImage)}>
{order.receiptImage ? (
<>
>
) : (
No Image
)}
))}
{/* ADMIN EXPENSES & FINANCE TAB */}
{staffTab === 'expenses' && (
Total Revenue
RM {financialStats.totalRevenue.toFixed(2)}
Total Expenses
RM {financialStats.totalExpenses.toFixed(2)}
Net Profit
RM {financialStats.netProfit.toFixed(2)}
Recent Transactions
{expenses.length === 0 ? (
No financial records found.
) : expenses.map(e => (
{e.item}
{new Date(e.timestamp).toLocaleDateString()}
RM {e.cost.toFixed(2)}
))}
)}
)}
{/* CUSTOMIZATION MODAL */}
{customizingItem && (
{customizingItem.name}
{/* JUICE OPTIONS */}
{customizingItem.type === 'juice' && (
<>
{['Small','Medium','Large'].map(s=>(
))}
{['Normal','Less','No'].map(s=>(
))}
>
)}
{/* PHYSIO OPTIONS */}
{customizingItem.type === 'physio' && (
{[{id:'student',l:'IIUM Student',p:'25'},{id:'staff',l:'IIUM Staff',p:'45'},{id:'public',l:'Public',p:'80'}].map(r=>(
))}
)}
{/* MASSAGE OPTIONS */}
{customizingItem.type === 'massage' && (
{customizingItem.id==='m1' && (
{['Head & Shoulder','Shoulder & Back','Lower Body'].map(a=>(
))}
)}
)}
{/* SWIMMING OPTIONS */}
{customizingItem.id === 's4' && (
{['Silicone','Mesh'].map(t=>(
))}
)}
{/* Add To Cart Button */}
)}
{/* CHECKOUT CART DRAWER */}
{isCartExpanded && (
Checkout
{cart.map((item, i) => {
const requiresAppointment = ['physio', 'massage', 'swimming_coaching'].includes(item.type);
return (
{item.qty}
{item.name}
{item.variant}
{item.bundle &&
Bundle Applied}
{/* Per-Item Appointment Picker */}
{requiresAppointment && (
Select Booking Date & Time
{availableDates.map((date, dIdx) => {
const isSel = item.dateObj && date.toDateString() === item.dateObj.toDateString();
const disabled = getSlotsForItem(date, item.type).length === 0;
return (
);
})}
{item.dateObj && (
{getSlotsForItem(item.dateObj, item.type).map(time => {
const booked = isSlotBooked(item.dateObj.toLocaleDateString(), time, i);
const selected = item.time === time;
return (
);
})}
)}
)}
);
})}
{/* VOUCHER SECTION */}
Reward Voucher
setVoucherInput(e.target.value)} placeholder="Enter JACKFER..." className="flex-1 p-5 rounded-[1.5rem] border-2 border-lime-200 text-sm font-bold uppercase outline-none focus:ring-4 focus:ring-lime-500/10 transition-all bg-white" />
{voucherMsg &&
0 ? 'text-lime-700' : 'text-red-500'}`}> {voucherMsg}
}
{/* CLIENT DETAILS */}
{/* PAYMENT METHOD */}
Payment Options
{/* CHECKOUT BUTTON */}
)}
{/* QR MODAL */}
{showPaymentModal && (
DuitNow Payment
Bank ServiceRHB BANK
Acc Number11306160255276
Total to PayRM {calculateTotal().toFixed(2)}
)}
{/* GAME MODAL */}
{showGameModal && (
Fight!
{gameStatus === 'won' ? (
You Won!
Claim your reward now!
) : (
<>
{gameBoard.map((c, i) => (
))}
{gameStatus==='playing'? (isPlayerTurn?"Your Turn (X)":"Bot Turn") : (gameStatus==='lost'?"Defeat!":"Draw!")}
{gameStatus!=='playing' &&
}
>
)}
)}
{/* VIEW RECEIPT (ADMIN) */}
{viewingReceipt && (
setViewingReceipt(null)}>
Click to Close
)}
{/* ORDER CONFIRMATION */}
{activeVoucher && (
Success!
Your order is on the way! Your discount voucher has been emailed.
Next Order Code
{activeVoucher.code}
)}
);
}