import React, { useState, createContext, useContext, useEffect } from 'react'; import { ShoppingCart, Home, LayoutDashboard, Package, Trash2, Plus, X, Image as ImageIcon, CheckCircle, Clock, ChevronDown, BarChart3, AlertCircle, Search, Menu, LogOut, User, Settings as SettingsIcon, Calendar as CalendarIcon, ChevronLeft, ChevronRight, Check, Upload, Shield, Briefcase, FileText, Database, Download, UploadCloud, Terminal, UserPlus, Award, Gift, Users, Edit3, ClipboardList, Tag, Percent } from 'lucide-react'; // ============================================================================ // DATA AWAL (PRODUCTION READY - KOSONG UNTUK DATA ASLI) // ============================================================================ const initialCategories = ['Kebaya', 'Jas', 'Batik', 'Gaun', 'Aksesoris']; const initialProducts = []; const initialOrders = []; const initialMembers = []; const initialPrizes = []; const initialPromos = []; // Akun default tetap dipertahankan agar sistem RBAC berfungsi const initialUsers = [ { id: 'U0', username: 'dev', password: 'dev', name: 'Developer System', role: 'developer' }, { id: 'U1', username: 'owner', password: 'owner123', name: 'Owner Aisya', role: 'owner' }, { id: 'U2', username: 'manager', password: 'manager123', name: 'Manajer Operasional', role: 'manager' }, { id: 'U3', username: 'admin', password: 'admin123', name: 'Admin Kasir', role: 'admin' } ]; const initialBrandConfig = { appName: 'Aisya Wardrobe', slogan: 'Sewa Pakaian Premium & Eksklusif', companyBio: 'Aisya Wardrobe adalah penyedia layanan sewa pakaian premium terpercaya. Kami berdedikasi untuk memberikan pengalaman berpakaian terbaik untuk momen spesial Anda dengan koleksi yang selalu terawat, higienis, dan mengikuti tren terkini.', logoUrl: '', themeColor: 'rose', socialMedia: [ { type: 'WhatsApp', value: '081234567890', label: 'Hubungi Kami' }, { type: 'Instagram', value: '@aisyawardrobe', label: 'Follow IG Kami' } ], companyEmail: 'admin@aisyawardrobe.com' }; const AppStateContext = createContext(); const formatRupiah = (number) => new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(number); // ============================================================================ // KOMPONEN GLOBAL & CONTEXT // ============================================================================ const Toast = ({ message, type, onClose }) => { useEffect(() => { const timer = setTimeout(() => onClose(), 3000); return () => clearTimeout(timer); }, [onClose]); return (
{type === 'success' ? : type === 'error' ? : } {message}
); }; const AppStateProvider = ({ children }) => { const [view, setView] = useState('dashboard'); const [products, setProducts] = useState(initialProducts); const [categories, setCategories] = useState(initialCategories); const [orders, setOrders] = useState(initialOrders); const [cart, setCart] = useState([]); const [users, setUsers] = useState(initialUsers); const [loggedInUser, setLoggedInUser] = useState(null); const [members, setMembers] = useState(initialMembers); const [loggedInMember, setLoggedInMember] = useState(null); const [prizes, setPrizes] = useState(initialPrizes); const [promos, setPromos] = useState(initialPromos); const [brandConfig, setBrandConfig] = useState(initialBrandConfig); const [approvals, setApprovals] = useState([]); const [toast, setToast] = useState(null); const [logs, setLogs] = useState([]); const showToast = (message, type = 'success') => setToast({ message, type }); const themeColors = { rose: { bg: 'bg-rose-500', hover: 'hover:bg-rose-600', text: 'text-rose-500', border: 'border-rose-500', light: 'bg-rose-50 text-rose-600', shadow: 'shadow-rose-200' }, blue: { bg: 'bg-blue-500', hover: 'hover:bg-blue-600', text: 'text-blue-500', border: 'border-blue-500', light: 'bg-blue-50 text-blue-600', shadow: 'shadow-blue-200' }, emerald: { bg: 'bg-emerald-500', hover: 'hover:bg-emerald-600', text: 'text-emerald-500', border: 'border-emerald-500', light: 'bg-emerald-50 text-emerald-600', shadow: 'shadow-emerald-200' }, purple: { bg: 'bg-purple-500', hover: 'hover:bg-purple-600', text: 'text-purple-500', border: 'border-purple-500', light: 'bg-purple-50 text-purple-600', shadow: 'shadow-purple-200' }, slate: { bg: 'bg-slate-800', hover: 'hover:bg-slate-900', text: 'text-slate-800', border: 'border-slate-800', light: 'bg-slate-100 text-slate-800', shadow: 'shadow-slate-200' }, }; const cTheme = themeColors[brandConfig.themeColor] || themeColors.rose; const addLog = (action, detail, user = loggedInUser?.name || 'Sistem Otomatis') => { const newLog = { id: `LOG-${Date.now()}-${Math.floor(Math.random()*1000)}`, timestamp: new Date().toLocaleString(), user, action, detail }; setLogs(prev => [newLog, ...prev]); }; const login = (username, password) => { const user = users.find(u => u.username === username && u.password === password); if (user) { setLoggedInUser(user); setView('admin'); showToast(`Akses Diberikan: ${user.name}`, 'success'); addLog('Otorisasi', 'Melakukan login ke Admin Panel', user.name); return true; } return false; }; const logout = () => { addLog('Otorisasi', 'Melakukan logout dari Admin Panel'); setLoggedInUser(null); setView('dashboard'); showToast('Berhasil keluar.', 'info'); }; const memberLogin = (username, password) => { const member = members.find(m => m.username === username && m.password === password && m.status === 'approved'); if (member) { setLoggedInMember(member); setView('member_profile'); showToast(`Selamat datang, ${member.name}!`, 'success'); return true; } return false; }; const memberLogout = () => { setLoggedInMember(null); setView('dashboard'); showToast('Berhasil keluar dari akun.', 'info'); }; const submitMemberRegistration = (formData) => { const newApproval = { id: `APP-${Date.now()}`, actionType: 'REGISTER_MEMBER', payload: formData, requestedBy: formData.name, requesterRole: 'Calon Member', date: new Date().toLocaleString(), status: 'Menunggu' }; setApprovals(prev => [newApproval, ...prev]); showToast('Pendaftaran diajukan. Menunggu persetujuan.', 'info'); }; const redeemPrize = (prize) => { if (!loggedInMember) return; if (loggedInMember.points >= prize.points) { setMembers(prev => prev.map(m => m.id === loggedInMember.id ? {...m, points: m.points - prize.points} : m)); setLoggedInMember(prev => ({...prev, points: prev.points - prize.points})); showToast(`Berhasil menukarkan poin dengan ${prize.name}!`, 'success'); } else { showToast('Poin Anda tidak mencukupi.', 'error'); } }; const exportData = () => { const data = { products, categories, orders, users, members, brandConfig, logs, promos }; const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data)); const downloadAnchorNode = document.createElement('a'); downloadAnchorNode.setAttribute("href", dataStr); downloadAnchorNode.setAttribute("download", `aisya_backup_${new Date().getTime()}.json`); document.body.appendChild(downloadAnchorNode); downloadAnchorNode.click(); downloadAnchorNode.remove(); showToast('Data berhasil dibackup.', 'success'); addLog('Developer', 'Mengekspor backup data JSON'); }; const importData = (jsonData) => { try { const parsed = JSON.parse(jsonData); if(parsed.products) setProducts(parsed.products); if(parsed.categories) setCategories(parsed.categories); if(parsed.orders) setOrders(parsed.orders); if(parsed.users) setUsers(parsed.users); if(parsed.members) setMembers(parsed.members); if(parsed.brandConfig) setBrandConfig(parsed.brandConfig); if(parsed.logs) setLogs(parsed.logs); if(parsed.promos) setPromos(parsed.promos); showToast('Data berhasil dipulihkan.', 'success'); addLog('Developer', 'Memulihkan (Restore) database'); } catch(e) { showToast('Format file JSON tidak valid!', 'error'); } }; const requireApproval = (actionType, payload, successMsg, isDestructive = false) => { if (!loggedInUser) return false; if (loggedInUser.role === 'developer' || loggedInUser.role === 'owner') { executeAction(actionType, payload); showToast(successMsg, 'success'); return true; } if (loggedInUser.role === 'manager') { if (actionType === 'UPDATE_USER' && payload.userId === loggedInUser.id) { requestApproval(actionType, payload, `Perubahan akun Anda memerlukan izin Owner.`); return false; } executeAction(actionType, payload); showToast(successMsg, 'success'); return true; } requestApproval(actionType, payload, `Memerlukan izin Manager/Owner.`); return false; }; const requestApproval = (actionType, payload, msg) => { const newApproval = { id: `APP-${Date.now()}`, actionType, payload, requestedBy: loggedInUser.name, requesterRole: loggedInUser.role, date: new Date().toLocaleString(), status: 'Menunggu' }; setApprovals(prev => [newApproval, ...prev]); showToast(msg, 'info'); addLog('Persetujuan', `Mengajukan izin untuk tindakan: ${actionType}`); }; const executeAction = (actionType, payload) => { switch (actionType) { case 'ADD_PRODUCT': setProducts(prev => [payload, ...prev]); addLog('Inventaris', `Menambahkan produk baru: ${payload.name}`); break; case 'EDIT_PRODUCT': setProducts(prev => prev.map(p => p.id === payload.id ? payload : p)); addLog('Inventaris', `Mengubah rincian produk: ${payload.id}`); break; case 'DELETE_PRODUCT': setProducts(prev => prev.filter(p => p.id !== payload.id)); addLog('Inventaris', `Menghapus produk: ${payload.id}`); break; case 'UPDATE_PRODUCT_STATUS': setProducts(prev => prev.map(p => p.id === payload.id ? { ...p, status: payload.status } : p)); addLog('Inventaris', `Status produk ${payload.id} menjadi ${payload.status}`); break; case 'ADD_CATEGORY': setCategories(prev => [...prev, payload]); addLog('Kategori', `Menambahkan kategori baru: ${payload}`); break; case 'DELETE_CATEGORY': setCategories(prev => prev.filter(c => c !== payload)); addLog('Kategori', `Menghapus kategori: ${payload}`); break; case 'UPDATE_ORDER': setOrders(prev => prev.map(o => o.id === payload.id ? { ...o, status: payload.status } : o)); addLog('Pesanan', `Status pesanan ${payload.id} menjadi ${payload.status}`); break; case 'ADD_PRIZE': setPrizes(prev => [payload, ...prev]); addLog('Hadiah', `Menambahkan hadiah: ${payload.name}`); break; case 'DELETE_PRIZE': setPrizes(prev => prev.filter(p => p.id !== payload.id)); addLog('Hadiah', `Menghapus hadiah: ${payload.id}`); break; case 'ADD_PROMO': setPromos(prev => [payload, ...prev]); addLog('Promo', `Membuat kode promo baru: ${payload.code}`); break; case 'UPDATE_PROMO_STATUS': setPromos(prev => prev.map(p => p.id === payload.id ? { ...p, status: payload.status } : p)); addLog('Promo', `Mengubah status promo ${payload.code} menjadi ${payload.status}`); break; case 'DELETE_PROMO': setPromos(prev => prev.filter(p => p.id !== payload.id)); addLog('Promo', `Menghapus kode promo: ${payload.code}`); break; case 'UPDATE_USER': setUsers(prev => prev.map(u => u.id === payload.userId ? { ...u, name: payload.name, username: payload.username, password: payload.password, role: payload.role || u.role } : u)); if(loggedInUser && loggedInUser.id === payload.userId) setLoggedInUser(prev => ({...prev, name: payload.name, username: payload.username, password: payload.password})); addLog('Pengguna', `Mengubah kredensial staf: ${payload.username}`); break; case 'REGISTER_MEMBER': const year = new Date().getFullYear().toString().slice(-2); const seq = String(members.length + 1).padStart(3, '0'); const newMemberId = `MEM-${year}${seq}`; setMembers(prev => [{ ...payload, id: newMemberId, points: 0, status: 'approved' }, ...prev]); addLog('Member', `Menyetujui pendaftaran member: ${payload.name} (${newMemberId})`); break; default: break; } }; const handleApproval = (id, action) => { const req = approvals.find(a => a.id === id); if (!req) return; if (loggedInUser.role === 'manager' && req.requesterRole === 'manager' && req.actionType === 'UPDATE_USER') return showToast("Hanya Owner yang dapat menyetujui kredensial Manajer.", "error"); if (action === 'approve') { addLog('Persetujuan', `Menyetujui permintaan [${req.actionType}]`); executeAction(req.actionType, req.payload); setApprovals(prev => prev.filter(a => a.id !== id)); showToast('Disetujui.', 'success'); } else { addLog('Persetujuan', `Menolak permintaan [${req.actionType}]`); setApprovals(prev => prev.filter(a => a.id !== id)); showToast('Ditolak.', 'info'); } }; const getAvailableStock = (productId) => { const product = products.find(p => p.id === productId); if (!product || product.status === 'Maintenance') return 0; const rentedAmount = orders.filter(o => ['Menunggu Konfirmasi', 'Siap Diambil', 'Sedang Disewa'].includes(o.status)).reduce((total, order) => { const itemInOrder = order.items.find(i => i.id === productId); return total + (itemInOrder ? itemInOrder.quantity : 0); }, 0); return product.totalStock - rentedAmount; }; const updateOrderStatus = (orderId, newStatus) => { requireApproval('UPDATE_ORDER', { id: orderId, status: newStatus }, `Status diubah menjadi ${newStatus}.`); if (newStatus === 'Selesai' || newStatus === 'Dibatalkan') { const order = orders.find(o => o.id === orderId); if(order) { order.items.forEach(item => executeAction('UPDATE_PRODUCT_STATUS', { id: item.id, status: 'Maintenance' })); addLog('Sistem Otomatis', `Memindahkan pakaian di pesanan ${orderId} ke mode Maintenance.`); } } }; const addToCart = (product) => { if (product.status === 'Maintenance') return showToast('Barang sedang dalam masa perawatan.', 'error'); setCart(prev => { const exist = prev.find(item => item.id === product.id); if (exist) return prev.map(item => item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item); return [...prev, { ...product, quantity: 1 }]; }); showToast(`${product.name} ditambahkan.`, 'success'); }; const processOrder = (formData, promoData) => { const subtotal = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); const totalBase = subtotal * formData.duration; const isDiscount = formData.duration > 7; const durationDiscount = isDiscount ? (totalBase * 0.1) : 0; let promoDiscount = 0; if(promoData) { promoDiscount = promoData.discountType === 'percentage' ? (totalBase * (promoData.discountValue / 100)) : promoData.discountValue; } const totalBiaya = totalBase - durationDiscount - promoDiscount; const earnedPoints = loggedInMember ? Math.floor(totalBiaya / 10000) : 0; const newOrder = { id: `ORD-${2000 + orders.length + 1}`, date: new Date().toISOString().split('T')[0], customer: { name: formData.name, phone: formData.phone, identity: formData.identity, address: formData.address, ktpUrl: formData.ktpUrl }, items: cart.map(c => ({ id: c.id, name: c.name, price: c.price, quantity: c.quantity, image: c.image })), duration: formData.duration, startDate: formData.startDate, endDate: formData.endDate, total: totalBiaya, subtotalBase: totalBase, durationDiscount, promoDiscount, appliedPromoCode: promoData?.code || null, status: 'Menunggu Konfirmasi', memberId: loggedInMember ? loggedInMember.id : null, earnedPoints }; if (loggedInMember) { setMembers(prev => prev.map(m => m.id === loggedInMember.id ? {...m, points: m.points + earnedPoints} : m)); setLoggedInMember(prev => ({...prev, points: prev.points + earnedPoints})); } setOrders([newOrder, ...orders]); setCart([]); setView('success'); addLog('Pelanggan', `Menerima pesanan baru: ${newOrder.id}`); }; const value = { view, setView, products, setProducts, categories, setCategories, orders, setOrders, promos, setPromos, cart, setCart, addToCart, removeFromCart: (id) => setCart(p => p.filter(i => i.id !== id)), updateCartQuantity: (id, d) => setCart(p => p.map(i => i.id === id && i.quantity + d > 0 ? { ...i, quantity: i.quantity + d } : i)), processOrder, getAvailableStock, users, setUsers, loggedInUser, login, logout, members, setMembers, loggedInMember, memberLogin, memberLogout, submitMemberRegistration, prizes, setPrizes, redeemPrize, brandConfig, setBrandConfig, cTheme, approvals, requireApproval, handleApproval, exportData, importData, showToast, updateOrderStatus, logs, addLog }; return {toast && setToast(null)} />}{children}; }; // ============================================================================ // PUBLIC WEBSITE LAYOUT // ============================================================================ const CustomerLayout = ({ children }) => { const { view, setView, cart, brandConfig, cTheme, loggedInUser, loggedInMember } = useContext(AppStateContext); const cartCount = cart.reduce((s, i) => s + i.quantity, 0); const [showNav, setShowNav] = useState(false); return (
setView('dashboard')}>
{brandConfig.logoUrl ? Logo :
} {brandConfig.appName}
{showNav && (
setShowNav(false)}>

Menu Utama

)}
{children}
); }; // ============================================================================ // CUSTOMER PAGES // ============================================================================ const DashboardView = () => { const { setView, brandConfig, cTheme } = useContext(AppStateContext); return (

{brandConfig.appName}

{brandConfig.slogan}

Kualitas Premium

Koleksi terawat dan dijamin higienis untuk momen spesial Anda.

Bayar di Tempat

Transaksi 100% aman, bayar saat Anda mengambil barang di butik.

Poin Reward

Sewa dan kumpulkan poin berharga untuk ditukar hadiah menarik.

Mengenal {brandConfig.appName}

{brandConfig.companyBio}

{brandConfig.logoUrl ? Logo Perusahaan :
}
); }; const CatalogView = () => { const { products, categories, cart, addToCart, getAvailableStock, cTheme } = useContext(AppStateContext); const [activeCategory, setActiveCategory] = useState('Semua'); const [searchQuery, setSearchQuery] = useState(''); const filteredProducts = products.filter(p => p.status !== 'Maintenance' && (activeCategory === 'Semua' || p.category === activeCategory) && (p.name.toLowerCase().includes(searchQuery.toLowerCase()) || p.desc.toLowerCase().includes(searchQuery.toLowerCase()) || p.id.toLowerCase().includes(searchQuery.toLowerCase())) ); return (

Katalog Eksklusif

Temukan pilihan pakaian terbaik untuk momen istimewa Anda.

setSearchQuery(e.target.value)} />
{categories.map(cat => )}
{filteredProducts.length === 0 ? (

Katalog Kosong

Saat ini belum ada produk yang ditambahkan ke kategori ini.

) : (
{filteredProducts.map(product => { const avail = getAvailableStock(product.id); const cartItem = cart.find(i => i.id === product.id); const canAdd = avail > (cartItem ? cartItem.quantity : 0); return (
{product.name} { e.target.src = 'https://placehold.co/400?text=No+Image'; }} />
{product.category}
{avail === 0 &&
Stok Habis
}

{product.name}

{product.id}

{formatRupiah(product.price)} / hari

{product.desc}

Sisa: {avail}
); })}
)}
); }; const CartView = () => { const { cart, removeFromCart, updateCartQuantity, setView, getAvailableStock, cTheme } = useContext(AppStateContext); const subtotal = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); if (cart.length === 0) return (

Keranjang Kosong

); return (

Keranjang Sewa

{cart.map(item => { const avail = getAvailableStock(item.id); return (
{item.name}

{item.name}

{formatRupiah(item.price)} / hari

{item.quantity}
); })}

Ringkasan

Total Item{cart.reduce((s, i) => s + i.quantity, 0)} Pcs
Subtotal / hari{formatRupiah(subtotal)}
); }; const CheckoutView = () => { const { cart, processOrder, setView, cTheme, loggedInMember, showToast, promos } = useContext(AppStateContext); const [formData, setFormData] = useState({ name: loggedInMember?.name || '', identity: loggedInMember?.identity || '', phone: loggedInMember?.phone || '', address: loggedInMember?.address || '', startDate: '', endDate: '', ktpUrl: loggedInMember?.ktpUrl || null }); // Promo State const [promoInput, setPromoInput] = useState(''); const [appliedPromo, setAppliedPromo] = useState(null); const getDuration = () => { if(!formData.startDate || !formData.endDate) return 1; const start = new Date(formData.startDate); const end = new Date(formData.endDate); const diffDays = Math.ceil(Math.abs(end - start) / (1000 * 60 * 60 * 24)) + 1; return diffDays > 0 ? diffDays : 1; }; const duration = getDuration(); const subtotalPerDay = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); const totalBase = subtotalPerDay * duration; const isDiscount = duration > 7; const durationDiscount = isDiscount ? (totalBase * 0.1) : 0; // Promo Calculation Safety Check const isValidPromo = appliedPromo && totalBase >= appliedPromo.minPurchase; let promoDiscountAmount = 0; if (isValidPromo) { promoDiscountAmount = appliedPromo.discountType === 'percentage' ? (totalBase * (appliedPromo.discountValue / 100)) : appliedPromo.discountValue; } const totalBiaya = totalBase - durationDiscount - promoDiscountAmount; const potentialPoints = Math.floor(totalBiaya / 10000); const handleKtpUpload = (e) => { const file = e.target.files[0]; if (file) { const r = new FileReader(); r.onloadend = () => setFormData(p => ({...p, ktpUrl: r.result})); r.readAsDataURL(file); } }; const handleApplyPromo = () => { if (!promoInput.trim()) return; const foundPromo = promos.find(p => p.code.toUpperCase() === promoInput.toUpperCase() && p.status === 'Aktif'); if (!foundPromo) return showToast('Kode voucher tidak ditemukan atau tidak aktif.', 'error'); if (new Date(foundPromo.validUntil) < new Date()) return showToast('Voucher sudah kadaluarsa.', 'error'); if (totalBase < foundPromo.minPurchase) return showToast(`Minimal belanja untuk voucher ini adalah ${formatRupiah(foundPromo.minPurchase)}`, 'error'); setAppliedPromo(foundPromo); showToast('Voucher berhasil digunakan!', 'success'); }; const removePromo = () => { setAppliedPromo(null); setPromoInput(''); }; const handleSubmit = (e) => { e.preventDefault(); if(new Date(formData.endDate) < new Date(formData.startDate)) { showToast("Tanggal akhir tidak valid!", "error"); return; } processOrder({...formData, duration}, isValidPromo ? appliedPromo : null); }; return (

Checkout

{!loggedInMember && (

Dapatkan {potentialPoints} Poin Reward!

Login/daftar member untuk kumpulkan poin.

)}

Data Penyewa

setFormData({...formData, name: e.target.value})} className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:bg-white focus:border-gray-400 text-[16px] transition-all" />
setFormData({...formData, identity: e.target.value})} className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:bg-white focus:border-gray-400 text-[16px] transition-all" />
setFormData({...formData, phone: e.target.value})} className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:bg-white focus:border-gray-400 text-[16px] transition-all" />

Rencana Penyewaan

setFormData({...formData, startDate: e.target.value})} className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:bg-white focus:border-gray-400 text-[16px] transition-all" />
setFormData({...formData, endDate: e.target.value})} className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl outline-none focus:bg-white focus:border-gray-400 text-[16px] transition-all" />

Punya Voucher Promo?

{!isValidPromo ? (
setPromoInput(e.target.value)} className="w-full px-4 py-2 bg-gray-50 border border-gray-200 rounded-xl outline-none text-[16px] focus:bg-white focus:border-gray-400 uppercase" />
) : (

Promo Digunakan

{appliedPromo.code}

)} {appliedPromo && !isValidPromo &&

Voucher dilepas karena minimal belanja tidak terpenuhi.

}

Ringkasan Pesanan

{cart.map(item => (
{item.name}

{item.name}

{item.quantity}x @ {formatRupiah(item.price)}

))}
Subtotal / Hari{formatRupiah(subtotalPerDay)}
Durasi Total{duration} Hari
Total Tagihan Awal{formatRupiah(totalBase)}
{isDiscount &&
Diskon (> 7 Hari)-{formatRupiah(durationDiscount)}
} {isValidPromo &&
Promo Voucher-{formatRupiah(promoDiscountAmount)}
}
Total Akhir {formatRupiah(totalBiaya)}
{loggedInMember &&
Poin Didapat +{potentialPoints}
}
); }; const SuccessView = () => { const { setView, cTheme } = useContext(AppStateContext); return (

Berhasil!

Pesanan diterima ("Menunggu Konfirmasi").

); }; const MemberAuthView = () => { const { memberLogin, submitMemberRegistration, cTheme, showToast } = useContext(AppStateContext); const [tab, setTab] = useState('login'); const [uname, setUname] = useState(''); const [pwd, setPwd] = useState(''); const [formData, setFormData] = useState({ username: '', password: '', name: '', identity: '', phone: '', birthPlace: '', birthDate: '', gender: 'Perempuan', address: '', email: '', socialMedia: '', photoUrl: null, ktpUrl: null }); const handleLogin = (e) => { e.preventDefault(); if(!memberLogin(uname, pwd)) showToast('Username salah atau belum disetujui.', 'error'); }; const handleRegister = (e) => { e.preventDefault(); submitMemberRegistration(formData); setTab('login'); }; const toBase64 = (file, callback) => { const r = new FileReader(); r.onloadend = () => callback(r.result); r.readAsDataURL(file); }; return (
{tab === 'login' && (

Selamat Datang

setUname(e.target.value)} className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl focus:bg-white focus:ring-2 outline-none transition-all text-[16px]" />
setPwd(e.target.value)} className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl focus:bg-white focus:ring-2 outline-none transition-all text-[16px]" />
)} {tab === 'register' && (
setFormData({...formData, username: e.target.value})} className="w-full px-3 py-2 bg-gray-50 border rounded-lg text-[16px] md:text-sm outline-none focus:border-gray-400" />
setFormData({...formData, password: e.target.value})} className="w-full px-3 py-2 bg-gray-50 border rounded-lg text-[16px] md:text-sm outline-none focus:border-gray-400" />
setFormData({...formData, name: e.target.value})} className="w-full px-3 py-2 bg-gray-50 border rounded-lg text-[16px] md:text-sm outline-none focus:border-gray-400" />
setFormData({...formData, identity: e.target.value})} className="w-full px-3 py-2 bg-gray-50 border rounded-lg text-[16px] md:text-sm outline-none focus:border-gray-400" />
setFormData({...formData, phone: e.target.value})} className="w-full px-3 py-2 bg-gray-50 border rounded-lg text-[16px] md:text-sm outline-none focus:border-gray-400" />
)}
); }; const MemberProfileView = () => { const { loggedInMember, memberLogout, cTheme, orders, prizes, redeemPrize } = useContext(AppStateContext); const [tab, setTab] = useState('profile'); if (!loggedInMember) return null; const myOrders = orders.filter(o => o.memberId === loggedInMember.id); return (
{loggedInMember.photoUrl ? {loggedInMember.name} :
}

{loggedInMember.name}

ID: {loggedInMember.id}

Poin Reward

{loggedInMember.points}
{tab === 'profile' && (

Informasi Pribadi

Email

{loggedInMember.email}

WhatsApp

{loggedInMember.phone}

NIK

{loggedInMember.identity}

Alamat

{loggedInMember.address}

)} {tab === 'history' && (

Riwayat Penyewaan

{myOrders.length === 0 ?
Belum ada transaksi.
: myOrders.map(o => (
{o.id} • {o.date} {o.status}
{o.items.map(i =>
{i.quantity}x {i.name}
)}

{formatRupiah(o.total)}

+ {o.earnedPoints} Poin

)) }
)} {tab === 'rewards' && (

Katalog Hadiah

{prizes.length === 0 ?
Katalog hadiah saat ini belum tersedia.
:
{prizes.map(p => { const canRedeem = loggedInMember.points >= p.points; return (
{p.name}

{p.name}

{p.desc}

{p.points}
); })}
}
)}
); }; // ============================================================================ // ADMIN VIEWS // ============================================================================ const AdminLogin = () => { const { login, setView } = useContext(AppStateContext); const [uname, setUname] = useState(''); const [pwd, setPwd] = useState(''); const [err, setErr] = useState(''); const handleLogin = (e) => { e.preventDefault(); if(!login(uname, pwd)) setErr('Kredensial salah!'); }; return (

Login Admin

Masuk untuk mengelola sistem aplikasi.

{err &&
{err}
}
setUname(e.target.value)} className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl outline-none text-[16px] focus:ring-2 focus:bg-white" />
setPwd(e.target.value)} className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl outline-none text-[16px] focus:ring-2 focus:bg-white" />
); }; const AdminLayout = () => { const { loggedInUser, logout, brandConfig, setView } = useContext(AppStateContext); const [activeTab, setActiveTab] = useState('dashboard'); const [showSidebar, setShowSidebar] = useState(false); const menuItems = [ { id: 'dashboard', label: 'Statistik Kinerja', icon: BarChart3 }, { id: 'orders', label: 'Pesanan Masuk', icon: Package }, { id: 'inventory', label: 'Katalog Barang', icon: Briefcase }, { id: 'calendar', label: 'Jadwal Sewa', icon: CalendarIcon }, { id: 'customers', label: 'Database Pelanggan', icon: Users }, { id: 'prizes', label: 'Katalog Hadiah', icon: Gift }, { id: 'promos', label: 'Kelola Promo & Voucher', icon: Tag }, { id: 'approvals', label: 'Persetujuan Sistem', icon: CheckCircle }, { id: 'account', label: 'Setelan Akun', icon: User }, ...(loggedInUser?.role === 'owner' || loggedInUser?.role === 'developer' ? [ { id: 'logs', label: 'Log Sistem', icon: ClipboardList }, { id: 'settings', label: 'Pengaturan Sistem', icon: SettingsIcon } ] : []), ...(loggedInUser?.role === 'developer' ? [{ id: 'developer', label: 'Developer Console', icon: Terminal }] : []) ]; return (
{showSidebar &&
setShowSidebar(false)}>
}

{activeTab.replace('-', ' ')}

{loggedInUser.name}

{loggedInUser.role}

{activeTab === 'dashboard' && } {activeTab === 'orders' && } {activeTab === 'inventory' && } {activeTab === 'calendar' && } {activeTab === 'customers' && } {activeTab === 'prizes' && } {activeTab === 'promos' && } {activeTab === 'approvals' && } {activeTab === 'account' && } {activeTab === 'logs' && (loggedInUser.role === 'owner' || loggedInUser.role === 'developer') && } {activeTab === 'settings' && (loggedInUser.role === 'owner' || loggedInUser.role === 'developer') && } {activeTab === 'developer' && loggedInUser.role === 'developer' && }
); }; // --- ADMIN MODULES --- const AdminStats = () => { const { orders, members } = useContext(AppStateContext); const completed = orders.filter(o => o.status === 'Selesai'); const active = orders.filter(o => ['Menunggu Konfirmasi', 'Siap Diambil', 'Sedang Disewa'].includes(o.status)); const revenue = completed.reduce((s, o) => s + o.total, 0); return (
Pendapatan
{formatRupiah(revenue)}
Pesanan Aktif
{active.length}
Total Transaksi
{orders.length}
Total Member
{members.filter(m=>m.status === 'approved').length}

Aktivitas Terakhir

{orders.length === 0 ?
Belum ada aktivitas pesanan tercatat.
: orders.slice(0, 5).map(o => (

{o.customer.name}

{o.id}

{o.status}
))}
); }; const AdminOrderManager = () => { const { orders, updateOrderStatus } = useContext(AppStateContext); const [tab, setTab] = useState('aktif'); const [expandedId, setExpandedId] = useState(null); const displayedOrders = tab === 'aktif' ? orders.filter(o => ['Menunggu Konfirmasi', 'Siap Diambil', 'Sedang Disewa'].includes(o.status)) : orders.filter(o => ['Selesai', 'Dibatalkan'].includes(o.status)); return (
{displayedOrders.length === 0 ?
Belum ada data pesanan.
: displayedOrders.map(order => (
setExpandedId(expandedId === order.id ? null : order.id)}>
{order.memberId ? : }

{order.customer.name} {order.memberId && MEMBER}

{order.id} • {order.startDate}

{order.status}
{expandedId === order.id && (
Pelanggan

{order.customer.name}

{order.customer.phone}

NIK: {order.customer.identity}

Waktu Sewa

Mulai: {order.startDate}

Selesai: {order.endDate}

Durasi: {order.duration} Hari

Tindakan Admin
Total Tagihan{formatRupiah(order.total)}
)}
))}
); }; const AdminInventory = () => { const { products, categories, requireApproval, getAvailableStock, cTheme } = useContext(AppStateContext); const [invTab, setInvTab] = useState('katalog'); const [activeCategory, setActiveCategory] = useState('Semua'); const [searchQuery, setSearchQuery] = useState(''); const [showAddForm, setShowAddForm] = useState(false); const [newCategoryName, setNewCategoryName] = useState(''); const [expandedId, setExpandedId] = useState(null); const [editingId, setEditingId] = useState(null); const [editData, setEditData] = useState({ price: '', totalStock: '' }); const [formData, setFormData] = useState({ name: '', price: '', category: categories[0] || '', desc: '', image: null, totalStock: 1 }); const handleImageUpload = (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onloadend = () => setFormData(prev => ({ ...prev, image: reader.result })); reader.readAsDataURL(file); } }; const generateId = (cat) => { const prefix = cat ? cat.substring(0,2).toUpperCase() : 'XX'; const count = products.filter(p => p.id.startsWith(prefix)).length + 1; return `${prefix}-${1000 + count}`; }; const handleAddProduct = (e) => { e.preventDefault(); const newProduct = { id: generateId(formData.category), ...formData, price: parseInt(formData.price), totalStock: parseInt(formData.totalStock), status: 'Tersedia' }; if(requireApproval('ADD_PRODUCT', newProduct, 'Produk berhasil ditambahkan.')) { setShowAddForm(false); setFormData({ name: '', price: '', category: categories[0] || '', desc: '', image: null, totalStock: 1 }); } }; const handleAddCategory = (e) => { e.preventDefault(); if (newCategoryName.trim() && !categories.includes(newCategoryName.trim())) { requireApproval('ADD_CATEGORY', newCategoryName.trim(), 'Kategori baru berhasil ditambahkan.'); setNewCategoryName(''); } }; const handleSaveEdit = (p) => { if(editData.price && editData.totalStock) { requireApproval('EDIT_PRODUCT', {...p, price: parseInt(editData.price), totalStock: parseInt(editData.totalStock)}, 'Perubahan produk disimpan.'); setEditingId(null); } }; const filteredProducts = products.filter(p => p.status !== 'Maintenance' && (activeCategory === 'Semua' || p.category === activeCategory) && (p.name.toLowerCase().includes(searchQuery.toLowerCase()) || p.id.toLowerCase().includes(searchQuery.toLowerCase()))); const maintenanceProducts = products.filter(p => p.status === 'Maintenance'); return (
{invTab === 'katalog' && ( <>

Kelola Kategori Pakaian

{categories.length === 0 && Belum ada kategori.} {categories.map(cat => (
))}
setNewCategoryName(e.target.value)} required className="flex-1 px-4 py-2.5 bg-gray-50 border rounded-xl text-[16px] md:text-sm focus:outline-none focus:border-gray-400" />
setSearchQuery(e.target.value)} className="w-full pl-9 pr-4 py-2.5 bg-white border border-gray-200 rounded-xl text-[16px] md:text-sm focus:outline-none focus:border-gray-400 shadow-sm" />
{showAddForm && (

Tambah Produk Baru

setFormData({...formData, name: e.target.value})} className="w-full px-3 py-2 bg-gray-50 border rounded-lg text-[16px] md:text-sm outline-none focus:border-gray-400" />
setFormData({...formData, price: e.target.value})} className="w-full px-3 py-2 bg-gray-50 border rounded-lg text-[16px] md:text-sm outline-none focus:border-gray-400" />
setFormData({...formData, totalStock: e.target.value})} className="w-full px-3 py-2 bg-gray-50 border rounded-lg text-[16px] md:text-sm outline-none focus:border-gray-400" />
)} {filteredProducts.length === 0 ?
Belum ada barang di katalog.
:
{filteredProducts.map(p => { const avail = getAvailableStock(p.id); const isExpanded = expandedId === p.id; const isEditing = editingId === p.id; return (
{setExpandedId(isExpanded ? null : p.id); setEditingId(null);}}> {p.name}

{p.name}

{p.id} • {p.category}

{formatRupiah(p.price)}

0?'bg-green-100 text-green-700':'bg-red-100 text-red-700'}`}>Tersedia: {avail}/{p.totalStock}
{isExpanded && (

{p.desc}

{isEditing ? (
setEditData({...editData, price: e.target.value})} className="w-full mt-1 px-3 py-1.5 border rounded-lg text-[16px] md:text-sm" />
setEditData({...editData, totalStock: e.target.value})} className="w-full mt-1 px-3 py-1.5 border rounded-lg text-[16px] md:text-sm" />
) : (
)}
)}
); })}
} )} {invTab === 'maintenance' && (

Barang Dalam Perawatan (Maintenance)

Barang di daftar ini disembunyikan dari katalog pelanggan.

{maintenanceProducts.length === 0 ? (
Semua barang dalam kondisi baik.
) : (
{maintenanceProducts.map(p => (
Item

{p.name}

{p.id}

))}
)}
)}
); }; const AdminPromo = () => { const { promos, requireApproval, cTheme } = useContext(AppStateContext); const [formData, setFormData] = useState({ code: '', discountType: 'percentage', discountValue: '', minPurchase: '', validUntil: '' }); const handleAdd = (e) => { e.preventDefault(); requireApproval('ADD_PROMO', { id: `PRM-${Date.now()}`, code: formData.code.toUpperCase(), discountType: formData.discountType, discountValue: parseInt(formData.discountValue), minPurchase: parseInt(formData.minPurchase), validUntil: formData.validUntil, status: 'Aktif' }, 'Voucher promo berhasil dibuat.'); setFormData({ code: '', discountType: 'percentage', discountValue: '', minPurchase: '', validUntil: '' }); }; return (

Buat Voucher & Promo Baru

setFormData({...formData, code: e.target.value})} className="w-full px-3 py-2 bg-gray-50 border rounded-lg text-[16px] md:text-sm outline-none focus:border-gray-400 uppercase" />
setFormData({...formData, discountValue: e.target.value})} className="w-full px-3 py-2 bg-gray-50 border rounded-lg text-[16px] md:text-sm outline-none focus:border-gray-400" />
setFormData({...formData, minPurchase: e.target.value})} className="w-full px-3 py-2 bg-gray-50 border rounded-lg text-[16px] md:text-sm outline-none focus:border-gray-400" />
setFormData({...formData, validUntil: e.target.value})} className="w-full px-3 py-2 bg-gray-50 border rounded-lg text-[16px] md:text-sm outline-none focus:border-gray-400" />
{promos.length === 0 ?
Belum ada promo yang dibuat.
: promos.map(p => (

{p.code}

Diskon {p.discountType === 'percentage' ? `${p.discountValue}%` : formatRupiah(p.discountValue)}

Min. Belanja: {formatRupiah(p.minPurchase)}

Kadaluarsa: {p.validUntil}

))}
); }; const AdminCalendar = () => { const { orders } = useContext(AppStateContext); const [search, setSearch] = useState(''); const [currentMonth, setCurrentMonth] = useState(new Date().getMonth()); const [currentYear, setCurrentYear] = useState(new Date().getFullYear()); const [selectedDate, setSelectedDate] = useState(null); const filteredOrders = orders.filter(order => { if (!search) return true; const s = search.toLowerCase(); return order.items.some(item => item.name.toLowerCase().includes(s) || item.id.toLowerCase().includes(s)); }); const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate(); const firstDay = new Date(currentYear, currentMonth, 1).getDay(); const nextMonth = () => { if (currentMonth === 11) { setCurrentMonth(0); setCurrentYear(currentYear + 1); } else setCurrentMonth(currentMonth + 1); }; const prevMonth = () => { if (currentMonth === 0) { setCurrentMonth(11); setCurrentYear(currentYear - 1); } else setCurrentMonth(currentMonth - 1); }; const monthNames = ["Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"]; const getOrdersForDate = (day) => { const dateStr = `${currentYear}-${String(currentMonth + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; const targetDate = new Date(dateStr); return filteredOrders.filter(o => { const start = new Date(o.startDate); const end = new Date(o.endDate || o.startDate); if(!o.endDate) end.setDate(end.getDate() + (o.duration - 1)); return targetDate >= start && targetDate <= end && ['Menunggu Konfirmasi', 'Siap Diambil', 'Sedang Disewa'].includes(o.status); }); }; return (

Jadwal Sewa (Event)

Pantau jadwal keluar-masuk pakaian.

setSearch(e.target.value)} className="w-full pl-9 pr-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-[16px] md:text-sm focus:outline-none focus:border-rose-500" />

{monthNames[currentMonth]} {currentYear}

{['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab'].map(d =>
{d}
)}
{Array.from({ length: firstDay }).map((_, i) =>
)} {Array.from({ length: daysInMonth }).map((_, i) => { const day = i + 1; const dayOrders = getOrdersForDate(day); const hasOrders = dayOrders.length > 0; return (
hasOrders && setSelectedDate({ day, month: currentMonth, year: currentYear, orders: dayOrders })} className={`p-2 md:p-4 rounded-xl border flex flex-col items-center justify-center min-h-[60px] md:min-h-[80px] transition-all ${hasOrders ? 'bg-rose-50 border-rose-200 cursor-pointer hover:bg-rose-100 shadow-sm' : 'bg-white border-gray-100 text-gray-400'}`}> {day} {hasOrders && {dayOrders.length} Sewa}
); })}
{selectedDate && (
setSelectedDate(null)}>
e.stopPropagation()}>

Jadwal: {selectedDate.day} {monthNames[selectedDate.month]} {selectedDate.year}

{selectedDate.orders.map(o => (

{o.customer.name}

{o.id}

{o.status}
    {o.items.map(item => (
  • {item.name}

    {item.quantity}x {item.name}

    {item.id}

  • ))}
))}
)}
); }; const AdminCustomers = () => { const { members, orders, approvals, handleApproval, loggedInUser, cTheme } = useContext(AppStateContext); const [tab, setTab] = useState('member'); const approvedMembers = members.filter(m => m.status === 'approved'); const nonMembers = orders.filter(o => !o.memberId).reduce((acc, order) => { const existing = acc.find(c => c.identity === order.customer.identity); if (existing) { existing.totalSpent += order.total; existing.orderCount += 1; } else { acc.push({ ...order.customer, totalSpent: order.total, orderCount: 1 }); } return acc; }, []); const memberApps = approvals.filter(a => a.actionType === 'REGISTER_MEMBER'); return (
{tab === 'member' && ( {approvedMembers.length === 0 ? : approvedMembers.map(m => ( ))}
ProfilKontakDemografiPoin
Belum ada member.
{m.photoUrl ? Pic :
}

{m.name}

{m.id}

{m.phone}

{m.email}

{m.gender}

{m.birthPlace}, {m.birthDate}

{m.points}
)} {tab === 'nonmember' && ( {nonMembers.length === 0 ? : nonMembers.map((nm, idx) => ( ))}
Nama PelangganKTP / NIKKontakRiwayat Order
Tidak ada data.
{nm.name} {nm.identity} {nm.phone}

{nm.orderCount}x Transaksi

{formatRupiah(nm.totalSpent)}

)} {tab === 'approval' && (
{memberApps.length === 0 ?
Tidak ada pendaftaran baru.
: memberApps.map(app => (
{app.payload.photoUrl ? foto :
}

{app.requestedBy}

{app.payload.identity} • {app.payload.phone}

))}
)}
); }; const AdminPrizes = () => { const { prizes, requireApproval, cTheme } = useContext(AppStateContext); const [formData, setFormData] = useState({ name: '', points: '', desc: '', image: null }); const handleImageUpload = (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onloadend = () => setFormData(p => ({ ...p, image: reader.result })); reader.readAsDataURL(file); } }; const handleAdd = (e) => { e.preventDefault(); if(requireApproval('ADD_PRIZE', { id: `PRZ-${Date.now()}`, name: formData.name, points: parseInt(formData.points), desc: formData.desc, image: formData.image || 'https://placehold.co/200?text=Prize' }, 'Hadiah ditambahkan.')) { setFormData({ name: '', points: '', desc: '', image: null }); } }; return (

Tambah Hadiah Baru

setFormData({...formData, name: e.target.value})} className="w-full px-3 py-2 bg-gray-50 border rounded-lg text-[16px] md:text-sm outline-none focus:border-gray-400" />
setFormData({...formData, points: e.target.value})} className="w-full px-3 py-2 bg-gray-50 border rounded-lg text-[16px] md:text-sm outline-none focus:border-gray-400" />
{prizes.length === 0 ?
Katalog hadiah poin kosong.
: prizes.map(p => (
prize

{p.name}

{p.desc}

{p.points} Poin
))}
); }; const AdminApprovals = () => { const { approvals, handleApproval, loggedInUser } = useContext(AppStateContext); const sysApps = approvals.filter(a => a.actionType !== 'REGISTER_MEMBER'); if (sysApps.length === 0) return
Tidak ada antrean sistem.
; return (

Persetujuan Sistem ({sysApps.length})

{sysApps.map(app => { const canApprove = loggedInUser.role === 'owner' || loggedInUser.role === 'developer' || (loggedInUser.role === 'manager' && !(app.requesterRole === 'manager' && app.actionType === 'UPDATE_USER')); return (
Menunggu Izin{app.id}

{app.actionType.replace('_', ' ')}

Oleh: {app.requestedBy} {app.requesterRole}

{canApprove ? (
) : (
Menunggu Owner
)}
); })}
); }; const AdminAccountSettings = () => { const { loggedInUser, requireApproval } = useContext(AppStateContext); const [formData, setFormData] = useState({ name: loggedInUser.name, username: loggedInUser.username, password: loggedInUser.password }); const handleSubmit = (e) => { e.preventDefault(); requireApproval('UPDATE_USER', { userId: loggedInUser.id, name: formData.name, username: formData.username, password: formData.password }, 'Profil diupdate.', true); }; return (

Setelan Akun Pribadi

setFormData({...formData, name: e.target.value})} className="w-full px-4 py-3 bg-gray-50 border rounded-xl text-[16px] md:text-sm outline-none focus:bg-white" />
setFormData({...formData, username: e.target.value})} className="w-full px-4 py-3 bg-gray-50 border rounded-xl text-[16px] md:text-sm outline-none focus:bg-white" />
setFormData({...formData, password: e.target.value})} className="w-full px-4 py-3 bg-gray-50 border rounded-xl text-[16px] md:text-sm outline-none focus:bg-white" />
); }; const AdminLogs = () => { const { logs } = useContext(AppStateContext); return (

Log Sistem & Aktivitas

Memantau riwayat perubahan data dan status (Eksklusif untuk Owner & Developer).

{logs.length === 0 ? : logs.map(log => ( )) }
Waktu KejadianPenggunaKategori MenuDetail Aktivitas
Belum ada riwayat aktivitas tercatat.
{log.timestamp} {log.user} {log.action} {log.detail}
); }; const AdminSystemSettings = () => { const { brandConfig, setBrandConfig, users, requireApproval, showToast, loggedInUser, addLog } = useContext(AppStateContext); const [bConfig, setBConfig] = useState(brandConfig); const handleSaveBrand = (e) => { e.preventDefault(); setBrandConfig(bConfig); showToast('Konfigurasi Toko Disimpan.', 'success'); addLog('Pengaturan', 'Menyimpan perubahan konfigurasi dan profil perusahaan toko.'); }; const addSocial = () => setBConfig({...bConfig, socialMedia: [...bConfig.socialMedia, {type: 'Instagram', value: '', label: ''}]}); const updateSocial = (index, field, val) => { const newS = [...bConfig.socialMedia]; newS[index][field] = val; setBConfig({...bConfig, socialMedia: newS}); }; const removeSocial = (index) => setBConfig({...bConfig, socialMedia: bConfig.socialMedia.filter((_, i) => i !== index)}); return (

Identitas Toko (Whitelabel)

setBConfig({...bConfig, appName: e.target.value})} className="w-full px-4 py-2.5 bg-gray-50 border rounded-xl outline-none text-[16px] md:text-sm" />
setBConfig({...bConfig, slogan: e.target.value})} className="w-full px-4 py-2.5 bg-gray-50 border rounded-xl outline-none text-[16px] md:text-sm" />