APS
import React, { useState, useEffect } from 'react';
import { ShoppingCart, Plus, Box, Trash2, Search, Receipt, Store, CheckCircle, X, QrCode, Banknote, Printer, Loader2 } from 'lucide-react';
// --- PASTE URL WEB APP ANDA DI SINI ---
// Pastikan URL berakhiran /exec
const SCRIPT_URL = 'https://script.google.com/macros/s/AKfycbwNShER635nllYQxWygb8tBjWtaZjHH7xXdEt3ezLHacHWGPRVSF41GfZDbNV9BZ0dHMQ/exec';
export default function App() {
const [activeTab, setActiveTab] = useState('kasir');
const [products, setProducts] = useState([]); // Kosongkan awalnya, akan diisi dari Sheets
const [cart, setCart] = useState([]);
const [searchQuery, setSearchQuery] = useState('');
const [notification, setNotification] = useState(null);
const [isLoading, setIsLoading] = useState(false); // State untuk efek Loading
// Ambil data dari Google Sheets saat aplikasi pertama kali dibuka
useEffect(() => {
if (SCRIPT_URL !== 'PASTE_URL_WEB_APP_ANDA_DISINI') {
fetchCatalog();
}
}, []);
const fetchCatalog = async () => {
setIsLoading(true);
try {
const response = await fetch(SCRIPT_URL);
const result = await response.json();
if (result.status === 'success') {
setProducts(result.data);
}
} catch (error) {
console.error('Fetch error:', error);
showNotification('Gagal mengambil data dari server', 'error');
}
setIsLoading(false);
};
// State untuk Pembayaran
const [showPaymentModal, setShowPaymentModal] = useState(false);
const [paymentMethod, setPaymentMethod] = useState('cash'); // 'cash' atau 'qris'
const [cashAmount, setCashAmount] = useState('');
const [logoError, setLogoError] = useState(false);
// State untuk Nota/Receipt
const [showReceiptModal, setShowReceiptModal] = useState(false);
const [receiptData, setReceiptData] = useState(null);
// State untuk Token Listrik
const [tokenListrik, setTokenListrik] = useState('');
// State untuk form tambah katalog
const [newItem, setNewItem] = useState({
name: '',
price: '',
stock: ''
});
// --- Fungsi Format ---
const formatRupiah = (number) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(number);
};
const handleTokenChange = (e) => {
const rawValue = e.target.value.replace(/\D/g, '');
const truncated = rawValue.slice(0, 20);
const formatted = truncated.match(/.{1,4}/g)?.join('-') || truncated;
setTokenListrik(formatted);
};
// --- Fungsi Notifikasi ---
const showNotification = (message, type = 'success') => {
setNotification({ message, type });
setTimeout(() => setNotification(null), 3000);
};
// --- Fungsi Kasir ---
const addToCart = (product) => {
if (product.stock <= 0) {
showNotification('Stok barang habis!', 'error');
return;
}
setCart(prevCart => {
const existingItem = prevCart.find(item => item.id === product.id);
if (existingItem) {
if (existingItem.qty >= product.stock) {
showNotification('Maksimal stok tercapai!', 'error');
return prevCart;
}
return prevCart.map(item =>
item.id === product.id ? { ...item, qty: item.qty + 1 } : item
);
}
return [...prevCart, { ...product, qty: 1 }];
});
};
const removeFromCart = (id) => {
setCart(prevCart => prevCart.filter(item => item.id !== id));
};
const updateCartQty = (id, newQty) => {
if (newQty < 1) return;
const product = products.find(p => p.id === id);
if (product && newQty > product.stock) {
showNotification('Melebihi stok yang ada!', 'error');
return;
}
setCart(prevCart =>
prevCart.map(item => item.id === id ? { ...item, qty: newQty } : item)
);
};
const cartTotal = cart.reduce((sum, item) => sum + (item.price * item.qty), 0);
const handleCheckoutClick = () => {
if (cart.length === 0) {
showNotification('Keranjang masih kosong!', 'error');
return;
}
setShowPaymentModal(true);
setPaymentMethod('cash');
setCashAmount('');
setTokenListrik('');
};
const processPayment = async () => {
let amount = 0;
let change = 0;
if (paymentMethod === 'cash') {
amount = parseInt(cashAmount) || 0;
if (amount < cartTotal) {
showNotification('Uang tunai kurang dari total belanja!', 'error');
return;
}
change = amount - cartTotal;
}
const transactionData = {
date: new Date().toLocaleString('id-ID'),
items: [...cart],
total: cartTotal,
paymentMethod,
cashAmount: amount,
change,
tokenListrik
};
setIsLoading(true);
try {
// Menggunakan mode no-cors atau memastikan headers kompatibel dengan Apps Script
await fetch(SCRIPT_URL, {
method: 'POST',
mode: 'no-cors', // Menghindari isu preflight CORS di beberapa browser
headers: { 'Content-Type': 'text/plain' },
body: JSON.stringify({ action: 'checkout', transaction: transactionData })
});
// Optimistic update karena no-cors tidak mengembalikan response body
setProducts(prevProducts => {
let updatedProducts = [...prevProducts];
cart.forEach(cartItem => {
const index = updatedProducts.findIndex(p => p.id === cartItem.id);
if (index !== -1) {
updatedProducts[index].stock -= cartItem.qty;
}
});
return updatedProducts;
});
setReceiptData(transactionData);
setCart([]);
setShowPaymentModal(false);
setShowReceiptModal(true);
showNotification(`Transaksi berhasil diproses!`, 'success');
} catch (error) {
console.error('Checkout error:', error);
showNotification('Gagal mengirim transaksi ke server', 'error');
}
setIsLoading(false);
};
// --- Fungsi Katalog ---
const handleAddProduct = async (e) => {
e.preventDefault();
if (!newItem.name || !newItem.price || !newItem.stock) {
showNotification('Harap isi semua kolom!', 'error');
return;
}
const newProduct = {
id: Date.now(),
name: newItem.name,
price: parseInt(newItem.price),
stock: parseInt(newItem.stock)
};
setIsLoading(true);
try {
await fetch(SCRIPT_URL, {
method: 'POST',
mode: 'no-cors',
headers: { 'Content-Type': 'text/plain' },
body: JSON.stringify({ action: 'add_product', product: newProduct })
});
setProducts([...products, newProduct]);
setNewItem({ name: '', price: '', stock: '' });
showNotification('Barang berhasil ditambahkan!', 'success');
} catch (error) {
showNotification('Gagal menyimpan barang ke server', 'error');
}
setIsLoading(false);
};
const handleDeleteProduct = async (id) => {
setIsLoading(true);
try {
await fetch(SCRIPT_URL, {
method: 'POST',
mode: 'no-cors',
headers: { 'Content-Type': 'text/plain' },
body: JSON.stringify({ action: 'delete_product', id: id })
});
setProducts(products.filter(p => p.id !== id));
setCart(cart.filter(item => item.id !== id));
showNotification('Barang dihapus.', 'success');
} catch (error) {
showNotification('Gagal menghapus barang dari server', 'error');
}
setIsLoading(false);
};
const filteredProducts = products.filter(product =>
product.name.toLowerCase().includes(searchQuery.toLowerCase())
);
return (
{/* Header */}
{/* Notification Toast */}
{notification && (
)}
{/* Main Content */}
{/* --- VIEW: KASIR (POS) --- */}
{activeTab === 'kasir' && (
{paymentMethod === 'cash' ? (
) : (
)}
)}
{/* --- MODAL NOTA (RECEIPT) --- */}
{showReceiptModal && receiptData && (
)}
{/* --- VIEW: KATALOG --- */}
{activeTab === 'katalog' && (
)}
);
}
{!logoError ? (
setLogoError(true)}
/>
) : (
)}
Toko Adela PS
{notification.type === 'success' ? : }
{notification.message}
)}
{/* Loading Overlay */}
{isLoading && (
Memproses sinkronisasi data...
{/* Daftar Produk */}
setSearchQuery(e.target.value)}
/>
{/* Keranjang (Cart) */}
{cart.reduce((sum, item) => sum + item.qty, 0)} Item
)}
{/* --- MODAL PEMBAYARAN --- */}
{showPaymentModal && (
{filteredProducts.map(product => (
addToCart(product)}
className="bg-white rounded-xl shadow-sm border border-slate-200 p-4 cursor-pointer hover:shadow-md hover:border-blue-300 transition-all active:scale-95 flex flex-col h-full"
>
))}
{filteredProducts.length === 0 && (
{product.name}
{formatRupiah(product.price)}
Stok: {product.stock}
{product.stock > 0 ? (
) : (
Habis
)}
Tidak ada barang yang cocok dengan pencarian.
)}
Keranjang
{cart.reduce((sum, item) => sum + item.qty, 0)} Item
{cart.length === 0 ? (
) : (
cart.map(item => (
))
)}
Keranjang kosong
{item.name}
{formatRupiah(item.price)}
{item.qty}
Total Harga
{formatRupiah(cartTotal)}
Pilih Metode Pembayaran
Total Tagihan
{formatRupiah(cartTotal)}
setCashAmount(e.target.value)}
/>
{cashAmount && parseInt(cashAmount) >= cartTotal && (
Kembalian:
{formatRupiah(parseInt(cashAmount) - cartTotal)}
)}
Scan QR Code ini menggunakan aplikasi M-Banking atau E-Wallet pelanggan.
Isi hanya jika pelanggan membeli token listrik.
{/* Area Struk yang akan dicetak */}
{/* Bagian Token Listrik pada Nota */}
{receiptData.tokenListrik && (
)}
{/* Tombol Aksi (Disembunyikan saat dicetak) */}
Toko Adela PS
Waktu Belanja: {receiptData.date}
{receiptData.items.map(item => (
{formatRupiah(item.price * item.qty)}
))}
{item.name}
{item.qty} x {formatRupiah(item.price)}
Total Tagihan
{formatRupiah(receiptData.total)}
Metode Pembayaran
{receiptData.paymentMethod}
{receiptData.paymentMethod === 'cash' && (
<>
Tunai
{formatRupiah(receiptData.cashAmount)}
Kembalian
{formatRupiah(receiptData.change)}
>
)}
TOKEN LISTRIK
{receiptData.tokenListrik}
Terima kasih sudah belanja 🙏
Ditunggu kedatangannya lagi ya!
Tambah Barang Baru
Daftar Katalog Barang
| Nama Barang | Harga | Stok | Aksi |
|---|---|---|---|
| {product.name} | {formatRupiah(product.price)} | 10 ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}> {product.stock} | |
| Belum ada barang di katalog. Silakan tambah barang baru. | |||
Komentar
Posting Komentar