https://docs.google.com/spreadsheets/d/1NMYh7DTCzUi1E0xWVVwvjJ3c6usHAXgck8Y7ZfyJTfI/edit?gid=0#gid=0
Sales.gs
/**
* Memproses checkout: Simpan ke sheet Sales dan kurangi stok di sheet Products.
*/
function processCheckout(orderData) {
try {
const ss = SpreadsheetApp.openById(CONFIG.SPREADSHEET_ID);
const salesSheet = ss.getSheetByName('Sales');
const productSheet = ss.getSheetByName('Products');
// 1. Simpan Data Penjualan
// Kolom: id, date, customer_name, total_amount, items_json
salesSheet.appendRow([
Utilities.getUuid(),
new Date(),
orderData.customerName,
orderData.totalAmount,
JSON.stringify(orderData.items)
]);
// 2. Update Stok Produk (Sederhana)
const productData = productSheet.getDataRange().getValues();
orderData.items.forEach(item => {
for (let i = 1; i < productData.length; i++) {
if (productData[i][0] == item.id) { // Cocokkan ID
const currentStock = productData[i][3];
productSheet.getRange(i + 1, 4).setValue(currentStock - item.qty);
break;
}
}
});
return { success: true, message: 'Pembayaran berhasil diproses!' };
} catch (err) {
return { success: false, message: err.toString() };
}
}
Checkout.html
<div class="max-w-4xl mx-auto">
<div class="mb-8">
<h1 class="text-3xl font-bold">Checkout</h1>
<p class="text-slate-500">Selesaikan pesanan Anda di bawah ini.</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12">
<!-- Ringkasan Pesanan -->
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-200">
<h2 class="text-xl font-bold mb-4">Ringkasan Pesanan</h2>
<div id="checkout-items" class="space-y-4 mb-6">
<!-- Item keranjang akan dimuat di sini -->
</div>
<div class="border-t pt-4 flex justify-between items-center font-bold text-lg text-blue-600">
<span>Total Bayar:</span>
<span id="checkout-total">Rp 0</span>
</div>
</div>
<!-- Form Pembayaran -->
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-200">
<h2 class="text-xl font-bold mb-4">Data Pengiriman</h2>
<form id="paymentForm" onsubmit="handlePayment(this); return false;">
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-1">Nama Lengkap</label>
<input type="text" name="customerName" required class="w-full p-3 border rounded-xl focus:ring-2 focus:ring-blue-500 outline-none">
</div>
<div>
<label class="block text-sm font-medium mb-1">Alamat Pengiriman</label>
<textarea name="address" required class="w-full p-3 border rounded-xl focus:ring-2 focus:ring-blue-500 outline-none h-24"></textarea>
</div>
<div>
<label class="block text-sm font-medium mb-1">Metode Pembayaran</label>
<select name="method" class="w-full p-3 border rounded-xl focus:ring-2 focus:ring-blue-500 outline-none">
<option value="Transfer Bank">Transfer Bank</option>
<option value="E-Wallet">E-Wallet (Gopay/OVO)</option>
<option value="COD">Bayar di Tempat (COD)</option>
</select>
</div>
<button type="submit" id="payBtn" class="w-full bg-blue-600 text-white py-4 rounded-xl font-bold hover:bg-blue-700 transition transform hover:-translate-y-1">
Bayar Sekarang
</button>
</div>
</form>
<div id="payStatus" class="mt-4 text-center hidden"></div>
</div>
</div>
</div>
<script>
let cartData = [];
let totalAmount = 0;
function loadCheckout() {
cartData = JSON.parse(localStorage.getItem('cart') || '[]');
const container = document.getElementById('checkout-items');
if (cartData.length === 0) {
container.innerHTML = '<p class="text-slate-400">Keranjang Anda kosong.</p>';
document.getElementById('payBtn').disabled = true;
document.getElementById('payBtn').classList.add('opacity-50');
return;
}
totalAmount = cartData.reduce((acc, item) => acc + (item.price * item.qty), 0);
document.getElementById('checkout-total').innerText = 'Rp ' + totalAmount.toLocaleString('id-ID');
container.innerHTML = cartData.map(item => `
<div class="flex justify-between items-center text-sm border-b border-slate-50 pb-2">
<div>
<p class="font-semibold">${item.name}</p>
<p class="text-slate-400">${item.qty} x Rp ${item.price.toLocaleString('id-ID')}</p>
</div>
<p class="font-bold">Rp ${(item.price * item.qty).toLocaleString('id-ID')}</p>
</div>
`).join('');
}
function handlePayment(form) {
const btn = document.getElementById('payBtn');
const msg = document.getElementById('payStatus');
btn.disabled = true;
btn.innerText = 'Memproses...';
msg.classList.remove('hidden');
msg.innerHTML = '<p class="text-blue-500 animate-pulse">Menghubungkan ke server...</p>';
const orderData = {
customerName: form.customerName.value,
totalAmount: totalAmount,
items: cartData
};
if (typeof google !== 'undefined' && google.script && google.script.run) {
google.script.run.withSuccessHandler(res => {
if (res.success) {
localStorage.removeItem('cart');
updateCartUI();
msg.innerHTML = '<div class="p-4 bg-green-50 text-green-700 rounded-xl font-bold">✓ Pesanan Berhasil! Terima kasih telah berbelanja.</div>';
form.reset();
setTimeout(() => {
window.location.href = '<?= ScriptApp.getService().getUrl() ?>?page=home';
}, 3000);
} else {
btn.disabled = false;
btn.innerText = 'Bayar Sekarang';
msg.innerHTML = '<p class="text-red-600">Terjadi kesalahan: ' + res.message + '</p>';
}
}).processCheckout(orderData);
} else {
// Mock for Preview
setTimeout(() => {
msg.innerHTML = '<p class="text-orange-600 font-bold">(!) Mode Pratinjau: Pembayaran disimulasikan sukses.</p>';
localStorage.removeItem('cart');
updateCartUI();
}, 1500);
}
}
loadCheckout();
</script>
Update Code.gs
/**
* Konfigurasi Global Aplikasi
* Diletakkan di atas agar mudah diakses oleh fungsi manapun.
*/
const CONFIG = {
// Gunakan try-catch atau pastikan skrip terikat dengan Sheet
SPREADSHEET_ID: SpreadsheetApp.getActiveSpreadsheet() ? SpreadsheetApp.getActiveSpreadsheet().getId() : "ID_MANUAL_JIKA_DIPERLUKAN",
FOLDER_FOTO_ID: '1kJJfHH8_QK0vRSmQ1orWYvf7M05bahfu'
};
function doGet(e) {
// Pastikan e.parameter ada untuk menghindari error saat testing tanpa URL param
const params = e ? e.parameter : {};
const page = params.page || 'home';
let title = 'Toko Online - MiniMart';
// Mapping Judul (Lebih bersih jika halaman bertambah banyak)
const titles = {
'admin': 'Dashboard Admin - MiniMart',
'checkout': 'Checkout & Pembayaran - MiniMart',
'home': 'Toko Online - MiniMart'
};
if (titles[page]) {
title = titles[page];
}
// Pastikan file 'Index' ada di daftar file proyek Anda
const template = HtmlService.createTemplateFromFile('Index');
template.page = page;
return template.evaluate()
.setTitle(title)
.addMetaTag('viewport', 'width=device-width, initial-scale=1')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
function include(filename) {
// Tambahkan try-catch agar jika file HTML tidak ada, aplikasi tidak langsung mati
try {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
} catch (err) {
return ``;
}
}
Update Index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
body { font-family: 'Plus Jakarta Sans', sans-serif; }
.nav-active { @apply text-blue-600 font-semibold; }
.glass-nav {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
</style>
</head>
<body class="bg-slate-50 text-slate-900">
<!-- Navbar -->
<nav class="glass-nav border-b border-slate-200 sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16 items-center">
<div class="flex items-center gap-8">
<a href="<?= ScriptApp.getService().getUrl() ?>?page=home" class="flex items-center gap-2">
<div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"></path></svg>
</div>
<span class="text-xl font-bold tracking-tight text-slate-900 text-[18px]">MiniMart</span>
</a>
<div class="hidden md:flex space-x-8">
<a href="<?= ScriptApp.getService().getUrl() ?>?page=home" class="text-sm transition hover:text-blue-600 <?= page === 'home' ? 'nav-active' : 'text-slate-600' ?>">Beranda</a>
<a href="<?= ScriptApp.getService().getUrl() ?>?page=admin" class="text-sm transition hover:text-blue-600 <?= page === 'admin' ? 'nav-active' : 'text-slate-600' ?>">Dashboard Admin</a>
</div>
</div>
<div class="flex items-center gap-4">
<!-- Ikon Keranjang yang mengarah ke halaman Checkout -->
<a href="<?= ScriptApp.getService().getUrl() ?>?page=checkout" class="p-2 text-slate-600 hover:bg-slate-100 rounded-full transition relative">
<span id="cart-count" class="absolute top-0 right-0 bg-blue-600 text-white text-[10px] font-bold rounded-full h-4 w-4 flex items-center justify-center border-2 border-white">0</span>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>
</a>
</div>
</div>
</div>
</nav>
<!-- Hero Section (Hanya tampil di Home) -->
<? if (page === 'home') { ?>
<section class="relative overflow-hidden bg-white pt-16 pb-20 lg:pt-24 lg:pb-32">
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-full h-full pointer-events-none overflow-hidden">
<div class="absolute -top-[10%] -left-[10%] w-[40%] h-[40%] bg-blue-50 rounded-full blur-3xl opacity-50"></div>
<div class="absolute bottom-0 right-0 w-[30%] h-[30%] bg-indigo-50 rounded-full blur-3xl opacity-50"></div>
</div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative text-center">
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-50 text-blue-700 mb-6">
🎉 Promo Ramadhan: Diskon hingga 50%
</span>
<h1 class="text-4xl md:text-6xl font-extrabold tracking-tight text-slate-900 mb-6 leading-tight">
Belanja Kebutuhan Harian <br/>
<span class="text-blue-600">Lebih Cepat & Mudah</span>
</h1>
<p class="text-lg text-slate-600 max-w-2xl mx-auto mb-10">
Dapatkan produk berkualitas tinggi langsung dari genggaman Anda. Pengiriman cepat, transaksi aman, dan harga yang selalu bersaing setiap harinya.
</p>
<div class="flex flex-col sm:flex-row justify-center gap-4">
<a href="#katalog" class="px-8 py-4 bg-blue-600 text-white rounded-xl font-bold shadow-lg shadow-blue-200 hover:bg-blue-700 transition transform hover:-translate-y-1">
Mulai Belanja
</a>
<a href="<?= ScriptApp.getService().getUrl() ?>?page=admin" class="px-8 py-4 bg-white text-slate-700 border border-slate-200 rounded-xl font-bold hover:bg-slate-50 transition">
Kelola Toko
</a>
</div>
</div>
</section>
<? } ?>
<!-- Main Content Area -->
<main id="katalog" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 min-h-[60vh]">
<? if (page === 'home') { ?>
<?!= include('Home'); ?>
<? } else if (page === 'admin') { ?>
<div class="bg-white rounded-2xl p-8 shadow-sm border border-slate-200">
<?!= include('Admin'); ?>
</div>
<? } else if (page === 'checkout') { ?>
<?!= include('Checkout'); ?>
<? } ?>
</main>
<!-- Footer -->
<footer class="bg-white border-t border-slate-200 pt-16 pb-8">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 md:grid-cols-4 gap-12 mb-12">
<div class="col-span-1 md:col-span-2">
<div class="flex items-center gap-2 mb-4">
<div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center text-white font-bold">M</div>
<span class="text-xl font-bold">MiniMart</span>
</div>
<p class="text-slate-500 max-w-sm">
Solusi e-commerce modern menggunakan Google Apps Script. Membantu UMKM untuk go-digital dengan mudah dan efisien.
</p>
</div>
<div>
<h4 class="font-bold mb-4">Navigasi</h4>
<ul class="space-y-2 text-slate-600 text-sm">
<li><a href="<?= ScriptApp.getService().getUrl() ?>?page=home" class="hover:text-blue-600 transition">Beranda</a></li>
<li><a href="#katalog" class="hover:text-blue-600 transition">Katalog</a></li>
<li><a href="<?= ScriptApp.getService().getUrl() ?>?page=admin" class="hover:text-blue-600 transition">Dashboard Admin</a></li>
</ul>
</div>
<div>
<h4 class="font-bold mb-4">Bantuan</h4>
<ul class="space-y-2 text-slate-600 text-sm">
<li><a href="#" class="hover:text-blue-600 transition">FAQ</a></li>
<li><a href="#" class="hover:text-blue-600 transition">Hubungi Kami</a></li>
</ul>
</div>
</div>
<div class="border-t border-slate-100 pt-8 flex flex-col md:flex-row justify-between items-center gap-4">
<p class="text-slate-400 text-sm">© 2024 MiniMart Ecosystem. All rights reserved.</p>
<div class="flex gap-6 grayscale opacity-50">
<span class="text-xs font-bold italic">VISA</span>
<span class="text-xs font-bold italic">MASTERCARD</span>
<span class="text-xs font-bold italic">GOPAY</span>
</div>
</div>
</div>
</footer>
<script>
/**
* Memperbarui antarmuka jumlah keranjang dari localStorage.
*/
function updateCartUI() {
try {
const cart = JSON.parse(localStorage.getItem('cart') || '[]');
const count = cart.reduce((acc, item) => acc + (item.qty || 0), 0);
const cartBadge = document.getElementById('cart-count');
if (cartBadge) {
cartBadge.innerText = count;
// Badge tetap terlihat agar pengguna tahu ada akses ke checkout
}
} catch (e) {
console.error("Gagal update UI Keranjang:", e);
}
}
window.addEventListener('load', updateCartUI);
</script>
</body>
</html>


Tidak ada komentar:
Posting Komentar