Skip to content

Instantly share code, notes, and snippets.

@Xnuvers007
Created May 29, 2025 16:32
Show Gist options
  • Save Xnuvers007/f69e3df6480c4dd5d17d85f432109b3b to your computer and use it in GitHub Desktop.
Save Xnuvers007/f69e3df6480c4dd5d17d85f432109b3b to your computer and use it in GitHub Desktop.
food ordering
import customtkinter as ctk
import tkinter as tk
from tkinter import messagebox
from PIL import Image, ImageTk
import json
import os
from datetime import datetime, timedelta
import requests
from io import BytesIO
import hashlib
import threading
# Set tema aplikasi
ctk.set_appearance_mode("System")
ctk.set_default_color_theme("blue")
class FoodOrderApp(ctk.CTk):
def __init__(self):
super().__init__()
# Konfigurasi window
self.title("RasaKita - Aplikasi Pemesanan Makanan")
self.geometry("1000x650")
self.minsize(800, 600)
# Data aplikasi
self.cart = []
self.menu_data = self.load_menu()
self.image_cache = {} # Cache untuk menyimpan gambar
# User data
self.users_file = "users.json"
self.users = self.load_users()
self.current_user = self.check_saved_session()
# Membuat container utama
self.main_container = ctk.CTkFrame(self)
self.main_container.pack(fill="both", expand=True, padx=10, pady=10)
# Membuat sidebar
self.create_sidebar()
# Membuat area konten dengan scrollbar
self.content_container = ctk.CTkFrame(self.main_container)
self.content_container.pack(side="right", fill="both", expand=True, padx=10)
# Scrollable content frame
self.content_frame = ctk.CTkScrollableFrame(self.content_container)
self.content_frame.pack(fill="both", expand=True)
# Menampilkan halaman home sebagai default
self.show_home()
def load_users(self):
"""Load users from file or create empty users dict"""
try:
if os.path.exists(self.users_file) and os.path.getsize(self.users_file) > 0:
with open(self.users_file, "r") as f:
return json.load(f)
else:
# Create empty users file
with open(self.users_file, "w") as f:
json.dump([], f)
return []
except Exception as e:
print(f"Error loading users: {e}")
# Create empty users file if there was an error
with open(self.users_file, "w") as f:
json.dump([], f)
return []
def save_users(self):
"""Save users to file"""
with open(self.users_file, "w") as f:
json.dump(self.users, f)
def hash_password(self, password):
"""Create a secure hash of the password"""
return hashlib.sha256(password.encode()).hexdigest()
def check_saved_session(self):
"""Check if there's a saved session and load it"""
if os.path.exists("session.json"):
try:
with open("session.json", "r") as f:
session = json.load(f)
# Check if session is still valid
expiry = datetime.strptime(session["expiry"], "%Y-%m-%d %H:%M:%S")
if expiry > datetime.now():
# Session still valid
return session["user"]
except:
pass
# If we reach here, no valid session found
if os.path.exists("session.json"):
os.remove("session.json")
return None
def save_session(self, user):
"""Save user session"""
session = {
"user": user,
"expiry": (datetime.now() + timedelta(days=7)).strftime("%Y-%m-%d %H:%M:%S")
}
with open("session.json", "w") as f:
json.dump(session, f)
def clear_session(self):
"""Clear user session"""
if os.path.exists("session.json"):
os.remove("session.json")
self.current_user = None
# Tambahkan method show_cart
def show_cart(self):
self.clear_content()
# Header
header = ctk.CTkLabel(self.content_frame, text="Keranjang Belanja",
font=ctk.CTkFont(size=24, weight="bold"))
header.pack(pady=(20, 10))
# Cek jika keranjang kosong
if not self.cart:
empty_label = ctk.CTkLabel(self.content_frame,
text="Keranjang belanja Anda kosong",
font=ctk.CTkFont(size=16))
empty_label.pack(pady=100)
shop_button = ctk.CTkButton(self.content_frame, text="Belanja Sekarang",
command=self.show_menu)
shop_button.pack(pady=20)
return
# Jika ada item, tampilkan dalam scrollable frame
cart_scroll = ctk.CTkScrollableFrame(self.content_frame)
cart_scroll.pack(fill="both", expand=True, padx=20, pady=20)
# Tabel header
header_frame = ctk.CTkFrame(cart_scroll)
header_frame.pack(fill="x", pady=(0, 10))
headers = ["Produk", "Harga", "Jumlah", "Subtotal"]
weights = [3, 1, 1, 1]
for i, header_text in enumerate(headers):
header_label = ctk.CTkLabel(header_frame, text=header_text,
font=ctk.CTkFont(weight="bold"))
header_label.grid(row=0, column=i, padx=10, pady=5, sticky="w")
header_frame.grid_columnconfigure(i, weight=weights[i])
# Tampilkan item keranjang
total_price = 0
for i, item in enumerate(self.cart):
item_frame = ctk.CTkFrame(cart_scroll)
item_frame.pack(fill="x", pady=5)
# Load image
ctk_img = self.load_image_from_url(item["image"], size=(60, 40))
# Product column with image and name
product_frame = ctk.CTkFrame(item_frame, fg_color="transparent")
product_frame.grid(row=0, column=0, padx=10, pady=10, sticky="w")
img_label = ctk.CTkLabel(product_frame, text="", image=ctk_img)
img_label.pack(side="left", padx=(0, 10))
name_label = ctk.CTkLabel(product_frame, text=item["name"])
name_label.pack(side="left", fill="x", expand=True)
# Price
price_label = ctk.CTkLabel(item_frame, text=f"Rp {item['price']:,}")
price_label.grid(row=0, column=1, padx=10, pady=10)
# Quantity
qty_frame = ctk.CTkFrame(item_frame)
qty_frame.grid(row=0, column=2, padx=10, pady=10)
minus_btn = ctk.CTkButton(qty_frame, text="-", width=30, height=30,
command=lambda i=item: self.update_cart_quantity(i, -1))
minus_btn.pack(side="left")
qty_label = ctk.CTkLabel(qty_frame, text=str(item["quantity"]), width=30)
qty_label.pack(side="left", padx=5)
plus_btn = ctk.CTkButton(qty_frame, text="+", width=30, height=30,
command=lambda i=item: self.update_cart_quantity(i, 1))
plus_btn.pack(side="left")
# Subtotal
subtotal = item["price"] * item["quantity"]
total_price += subtotal
subtotal_label = ctk.CTkLabel(item_frame, text=f"Rp {subtotal:,}")
subtotal_label.grid(row=0, column=3, padx=10, pady=10)
# Delete button
delete_btn = ctk.CTkButton(item_frame, text="✕", width=30, height=30,
fg_color="#FF5252", hover_color="#FF0000",
command=lambda i=item: self.remove_from_cart(i))
delete_btn.grid(row=0, column=4, padx=10, pady=10)
# Configure grid weights
for col, weight in enumerate(weights):
item_frame.grid_columnconfigure(col, weight=weight)
# Footer dengan total dan checkout
footer_frame = ctk.CTkFrame(self.content_frame)
footer_frame.pack(fill="x", padx=20, pady=20)
# Total
total_label = ctk.CTkLabel(footer_frame, text="Total:",
font=ctk.CTkFont(weight="bold", size=16))
total_label.pack(side="left", padx=10, pady=10)
total_amount = ctk.CTkLabel(footer_frame, text=f"Rp {total_price:,}",
font=ctk.CTkFont(weight="bold", size=16))
total_amount.pack(side="left", padx=10, pady=10)
# Checkout button
checkout_btn = ctk.CTkButton(footer_frame, text="Checkout",
font=ctk.CTkFont(weight="bold"),
command=self.show_checkout)
checkout_btn.pack(side="right", padx=10, pady=10)
# Clear cart button
clear_btn = ctk.CTkButton(footer_frame, text="Kosongkan Keranjang",
fg_color="#FF5252", hover_color="#FF0000",
command=self.clear_cart)
clear_btn.pack(side="right", padx=10, pady=10)
def update_cart_quantity(self, item, change):
"""Update quantity of an item in cart"""
for cart_item in self.cart:
if cart_item["id"] == item["id"]:
cart_item["quantity"] += change
if cart_item["quantity"] <= 0:
self.cart.remove(cart_item)
self.update_cart_count()
self.show_cart() # Refresh cart view
return
def remove_from_cart(self, item):
"""Remove an item from cart"""
self.cart = [cart_item for cart_item in self.cart if cart_item["id"] != item["id"]]
self.update_cart_count()
self.show_cart() # Refresh cart view
def clear_cart(self):
"""Clear all items from cart"""
if messagebox.askyesno("Konfirmasi", "Apakah Anda yakin ingin mengosongkan keranjang?"):
self.cart = []
self.update_cart_count()
self.show_cart() # Refresh cart view
def show_checkout(self):
"""Show checkout page"""
self.clear_content()
# Header
header = ctk.CTkLabel(self.content_frame, text="Checkout",
font=ctk.CTkFont(size=24, weight="bold"))
header.pack(pady=(20, 30))
# Form frame
checkout_frame = ctk.CTkFrame(self.content_frame)
checkout_frame.pack(fill="both", expand=True, padx=20, pady=20)
# Dua kolom: form dan ringkasan pesanan
left_frame = ctk.CTkFrame(checkout_frame)
left_frame.pack(side="left", fill="both", expand=True, padx=(0, 10), pady=10)
right_frame = ctk.CTkFrame(checkout_frame)
right_frame.pack(side="right", fill="both", expand=True, padx=(10, 0), pady=10)
# Form pengiriman
shipping_label = ctk.CTkLabel(left_frame, text="Informasi Pengiriman",
font=ctk.CTkFont(size=16, weight="bold"))
shipping_label.pack(pady=10, padx=20, anchor="w")
# Form fields in scrollable frame
form_scroll = ctk.CTkScrollableFrame(left_frame)
form_scroll.pack(fill="both", expand=True, padx=20, pady=10)
# Nama
name_frame = ctk.CTkFrame(form_scroll, fg_color="transparent")
name_frame.pack(fill="x", pady=5)
name_label = ctk.CTkLabel(name_frame, text="Nama Penerima:")
name_label.pack(anchor="w")
self.checkout_name = ctk.CTkEntry(name_frame)
if self.current_user:
self.checkout_name.insert(0, self.current_user["name"])
self.checkout_name.pack(fill="x", pady=5)
# Alamat
address_frame = ctk.CTkFrame(form_scroll, fg_color="transparent")
address_frame.pack(fill="x", pady=5)
address_label = ctk.CTkLabel(address_frame, text="Alamat Pengiriman:")
address_label.pack(anchor="w")
self.checkout_address = ctk.CTkTextbox(address_frame, height=60)
self.checkout_address.pack(fill="x", pady=5)
# Telepon
phone_frame = ctk.CTkFrame(form_scroll, fg_color="transparent")
phone_frame.pack(fill="x", pady=5)
phone_label = ctk.CTkLabel(phone_frame, text="Nomor Telepon:")
phone_label.pack(anchor="w")
self.checkout_phone = ctk.CTkEntry(phone_frame)
if self.current_user:
self.checkout_phone.insert(0, self.current_user["phone"])
self.checkout_phone.pack(fill="x", pady=5)
# Metode pembayaran
payment_frame = ctk.CTkFrame(form_scroll, fg_color="transparent")
payment_frame.pack(fill="x", pady=5)
payment_label = ctk.CTkLabel(payment_frame, text="Metode Pembayaran:")
payment_label.pack(anchor="w")
self.payment_var = tk.StringVar(value="transfer")
payment_methods = [
("Transfer Bank", "transfer"),
("COD (Bayar di Tempat)", "cod"),
("E-Wallet", "ewallet")
]
for text, value in payment_methods:
radio = ctk.CTkRadioButton(payment_frame, text=text, variable=self.payment_var, value=value)
radio.pack(anchor="w", pady=2)
# Catatan
notes_frame = ctk.CTkFrame(form_scroll, fg_color="transparent")
notes_frame.pack(fill="x", pady=5)
notes_label = ctk.CTkLabel(notes_frame, text="Catatan (opsional):")
notes_label.pack(anchor="w")
self.checkout_notes = ctk.CTkTextbox(notes_frame, height=60)
self.checkout_notes.pack(fill="x", pady=5)
# Ringkasan pesanan
summary_label = ctk.CTkLabel(right_frame, text="Ringkasan Pesanan",
font=ctk.CTkFont(size=16, weight="bold"))
summary_label.pack(pady=10, padx=20, anchor="w")
# List item
items_frame = ctk.CTkScrollableFrame(right_frame, height=200)
items_frame.pack(fill="x", padx=20, pady=10)
# Tampilkan item pesanan
total = 0
for item in self.cart:
item_frame = ctk.CTkFrame(items_frame, fg_color="transparent")
item_frame.pack(fill="x", pady=5)
subtotal = item["price"] * item["quantity"]
total += subtotal
name_label = ctk.CTkLabel(item_frame, text=f"{item['name']} x{item['quantity']}")
name_label.pack(side="left")
price_label = ctk.CTkLabel(item_frame, text=f"Rp {subtotal:,}")
price_label.pack(side="right")
# Total
total_frame = ctk.CTkFrame(right_frame, fg_color="transparent")
total_frame.pack(fill="x", padx=20, pady=10)
total_label = ctk.CTkLabel(total_frame, text="Total:", font=ctk.CTkFont(weight="bold"))
total_label.pack(side="left")
total_amount = ctk.CTkLabel(total_frame, text=f"Rp {total:,}", font=ctk.CTkFont(weight="bold"))
total_amount.pack(side="right")
# Buttons
button_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent")
button_frame.pack(fill="x", padx=20, pady=20)
back_btn = ctk.CTkButton(button_frame, text="Kembali ke Keranjang",
command=self.show_cart)
back_btn.pack(side="left", padx=10)
order_btn = ctk.CTkButton(button_frame, text="Selesaikan Pesanan",
font=ctk.CTkFont(weight="bold"),
command=self.process_order)
order_btn.pack(side="right", padx=10)
def process_order(self):
"""Process checkout order"""
# Validasi input
name = self.checkout_name.get().strip()
address = self.checkout_address.get("1.0", "end-1c").strip()
phone = self.checkout_phone.get().strip()
payment = self.payment_var.get()
notes = self.checkout_notes.get("1.0", "end-1c").strip()
if not name or not address or not phone:
messagebox.showerror("Error", "Nama, alamat, dan nomor telepon harus diisi!")
return
if not phone.isdigit() or len(phone) < 10:
messagebox.showerror("Error", "Nomor telepon tidak valid!")
return
# Buat pesanan
order = {
"id": datetime.now().strftime("%Y%m%d%H%M%S"),
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"customer": {
"name": name,
"address": address,
"phone": phone
},
"items": self.cart.copy(),
"payment": payment,
"notes": notes,
"total": sum(item["price"] * item["quantity"] for item in self.cart),
"status": "pending"
}
# Jika user login, simpan pesanan ke akun
if self.current_user:
user_id = self.current_user["id"]
for i, user in enumerate(self.users):
if user["id"] == user_id:
if "orders" not in user:
user["orders"] = []
user["orders"].append(order)
self.users[i] = user
self.save_users()
break
# Tampilkan konfirmasi
messagebox.showinfo("Pesanan Berhasil", f"Terima kasih! Pesanan Anda telah diterima.\n\nNo. Pesanan: {order['id']}")
# Reset keranjang
self.cart = []
self.update_cart_count()
# Kembali ke beranda
self.show_home()
def load_menu(self):
# Data menu dengan URL gambar dari internet
return [
{"id": 1, "name": "Nasi Goreng Spesial", "price": 25000, "category": "Makanan Utama",
"description": "Nasi goreng dengan telur, ayam, dan sayuran segar",
"image": "https://images.unsplash.com/photo-1512058564366-18510be2db19?w=500"},
{"id": 2, "name": "Mie Goreng", "price": 22000, "category": "Makanan Utama",
"description": "Mie goreng dengan bumbu khas dan potongan ayam",
"image": "https://images.unsplash.com/photo-1583057341912-a0b5c9189e3e?w=500"},
{"id": 3, "name": "Ayam Bakar", "price": 35000, "category": "Makanan Utama",
"description": "Ayam bakar dengan bumbu rempah khas Indonesia",
"image": "https://images.unsplash.com/photo-1632662993592-9846bc3f8c4f?w=500"},
{"id": 4, "name": "Es Teh Manis", "price": 8000, "category": "Minuman",
"description": "Teh manis dingin yang menyegarkan",
"image": "https://images.unsplash.com/photo-1499638673689-79a0b5115d87?w=500"},
{"id": 5, "name": "Jus Alpukat", "price": 15000, "category": "Minuman",
"description": "Jus alpukat segar dengan susu dan gula aren",
"image": "https://images.unsplash.com/photo-1622483767028-3f66f32aef97?w=500"},
{"id": 6, "name": "Pisang Goreng", "price": 12000, "category": "Makanan Ringan",
"description": "Pisang goreng crispy dengan topping keju atau coklat",
"image": "https://images.unsplash.com/photo-1603348890732-6c5227b2f186?w=500"},
{"id": 7, "name": "Sate Ayam", "price": 30000, "category": "Makanan Utama",
"description": "Sate ayam dengan bumbu kacang khas",
"image": "https://images.unsplash.com/photo-1529563021893-cc83c992d75d?w=500"},
{"id": 8, "name": "Bakso", "price": 25000, "category": "Makanan Utama",
"description": "Bakso daging sapi dengan kuah gurih",
"image": "https://images.unsplash.com/photo-1587131782738-de30ea91a542?w=500"},
{"id": 9, "name": "Es Jeruk", "price": 10000, "category": "Minuman",
"description": "Jus jeruk segar dingin",
"image": "https://images.unsplash.com/photo-1613478223719-2ab802602423?w=500"},
]
def create_sidebar(self):
# Sidebar container
self.sidebar = ctk.CTkFrame(self.main_container, width=200, corner_radius=10)
self.sidebar.pack(side="left", fill="y", padx=10, pady=10)
# Logo aplikasi
self.logo_label = ctk.CTkLabel(self.sidebar, text="RasaKita",
font=ctk.CTkFont(size=24, weight="bold"))
self.logo_label.pack(pady=(20, 30))
# Menu navigasi
self.nav_home = ctk.CTkButton(self.sidebar, text="Beranda",
command=self.show_home)
self.nav_home.pack(pady=10, padx=20, fill="x")
self.nav_menu = ctk.CTkButton(self.sidebar, text="Menu Makanan",
command=self.show_menu)
self.nav_menu.pack(pady=10, padx=20, fill="x")
self.nav_cart = ctk.CTkButton(self.sidebar, text="Keranjang",
command=self.show_cart)
self.nav_cart.pack(pady=10, padx=20, fill="x")
# Update profile button text based on login status
profile_text = f"Profil ({self.current_user['name']})" if self.current_user else "Login / Daftar"
self.nav_profile = ctk.CTkButton(self.sidebar, text=profile_text,
command=self.show_profile)
self.nav_profile.pack(pady=10, padx=20, fill="x")
# Tampilkan jumlah item di keranjang
self.cart_count = ctk.CTkLabel(self.sidebar, text="0 item")
self.cart_count.pack(pady=(5, 20))
# Tema aplikasi
self.appearance_mode_label = ctk.CTkLabel(self.sidebar, text="Tema:")
self.appearance_mode_label.pack(pady=(20, 0))
self.appearance_mode_menu = ctk.CTkOptionMenu(self.sidebar,
values=["Light", "Dark", "System"],
command=self.change_appearance_mode)
self.appearance_mode_menu.pack(pady=10, padx=20)
self.appearance_mode_menu.set("System")
# Footer sidebar
self.version_label = ctk.CTkLabel(self.sidebar, text="v1.0.0")
self.version_label.pack(side="bottom", pady=10)
def change_appearance_mode(self, new_appearance_mode):
ctk.set_appearance_mode(new_appearance_mode)
def clear_content(self):
# Hapus semua widget dari frame konten
for widget in self.content_frame.winfo_children():
widget.destroy()
# Fungsi untuk memuat gambar dari URL
def load_image_from_url(self, url, size=(150, 100)):
"""Load image from URL with caching and convert to CTkImage"""
if url in self.image_cache:
return self.image_cache[url]
try:
# Jika URL dimulai dengan http, download gambar
if url.startswith(('http://', 'https://')):
# Gunakan header dan timeout yang lebih baik untuk Unsplash
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
response = requests.get(url, headers=headers, timeout=10, stream=True)
response.raise_for_status() # Raise exception for 4XX/5XX responses
img = Image.open(BytesIO(response.content))
img = img.convert('RGB') # Ensure image is in RGB mode
else:
# Jika bukan URL, coba load dari file lokal
if os.path.exists(url):
img = Image.open(url)
img = img.convert('RGB')
else:
# Jika file tidak ada, gunakan placeholder
img = self.create_placeholder_image(size, item_name="Image")
# Resize gambar
img = img.resize(size, Image.Resampling.LANCZOS)
# Convert to CTkImage
ctk_img = ctk.CTkImage(light_image=img, dark_image=img, size=size)
# Cache the image
self.image_cache[url] = ctk_img
return ctk_img
except Exception as e:
print(f"Error loading image {url}: {e}")
# Create a placeholder image
img = self.create_placeholder_image(size, f"{e.__class__.__name__}")
ctk_img = ctk.CTkImage(light_image=img, dark_image=img, size=size)
return ctk_img
def create_placeholder_image(self, size=(150, 100), item_name=""):
"""Create a placeholder image with text"""
# Create a new image with a light gray background
img = Image.new('RGB', size, color=(200, 200, 200))
# Try to add text to the image
try:
from PIL import ImageDraw, ImageFont
draw = ImageDraw.Draw(img)
# Use default font
try:
font = ImageFont.truetype("arial.ttf", 12)
except:
font = ImageFont.load_default()
# Add text
text = item_name if len(item_name) < 15 else item_name[:12] + "..."
text_width, text_height = draw.textsize(text, font=font)
position = ((size[0] - text_width) // 2, (size[1] - text_height) // 2)
draw.text(position, text, fill=(100, 100, 100), font=font)
except:
# If drawing text fails, just return the gray image
pass
return img
def show_home(self):
self.clear_content()
# Header
header = ctk.CTkLabel(self.content_frame, text="Selamat Datang di RasaKita",
font=ctk.CTkFont(size=28, weight="bold"))
header.pack(pady=(20, 10))
# Subheader
subheader = ctk.CTkLabel(self.content_frame, text="Nikmati kelezatan masakan Indonesia")
subheader.pack(pady=(0, 20))
# Menampilkan beberapa menu favorit
fav_frame = ctk.CTkFrame(self.content_frame)
fav_frame.pack(fill="x", padx=20, pady=20)
fav_label = ctk.CTkLabel(fav_frame, text="Menu Favorit",
font=ctk.CTkFont(size=20, weight="bold"))
fav_label.pack(pady=10, anchor="w")
# Menampilkan 3 menu favorit dalam grid
favorites_frame = ctk.CTkFrame(fav_frame, fg_color="transparent")
favorites_frame.pack(fill="x", padx=10, pady=10)
# Configure grid columns
for i in range(3):
favorites_frame.columnconfigure(i, weight=1)
# Menampilkan 3 menu favorit pertama
for i, item in enumerate(self.menu_data[:3]):
self.create_menu_card(favorites_frame, item, i)
# Tombol untuk melihat semua menu
view_all_button = ctk.CTkButton(fav_frame, text="Lihat Semua Menu",
command=self.show_menu)
view_all_button.pack(pady=20)
# Bagian info tambahan
info_frame = ctk.CTkFrame(self.content_frame)
info_frame.pack(fill="x", padx=20, pady=20)
info_label = ctk.CTkLabel(info_frame, text="Kenapa Memilih Kami?",
font=ctk.CTkFont(size=20, weight="bold"))
info_label.pack(pady=10, anchor="w")
features = [
"✓ Makanan segar dan berkualitas",
"✓ Proses pemesanan yang mudah",
"✓ Pengiriman cepat",
"✓ Pembayaran aman"
]
for feature in features:
feature_label = ctk.CTkLabel(info_frame, text=feature, anchor="w")
feature_label.pack(pady=5, padx=20, anchor="w")
def show_menu(self):
self.clear_content()
# Header
header = ctk.CTkLabel(self.content_frame, text="Menu Makanan",
font=ctk.CTkFont(size=24, weight="bold"))
header.pack(pady=(20, 10))
# Filter kategori
filter_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent")
filter_frame.pack(fill="x", padx=20, pady=10)
filter_label = ctk.CTkLabel(filter_frame, text="Kategori:")
filter_label.pack(side="left", padx=(0, 10))
categories = ["Semua", "Makanan Utama", "Makanan Ringan", "Minuman"]
# Buat variabel untuk menyimpan referensi tombol kategori
self.category_buttons = {}
for category in categories:
cat_btn = ctk.CTkButton(filter_frame, text=category,
width=100, height=30,
command=lambda c=category: self.filter_menu(c))
cat_btn.pack(side="left", padx=5)
self.category_buttons[category] = cat_btn
# Search box
search_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent")
search_frame.pack(fill="x", padx=20, pady=10)
self.search_var = tk.StringVar()
search_entry = ctk.CTkEntry(search_frame, placeholder_text="Cari menu...",
textvariable=self.search_var, width=300)
search_entry.pack(side="left", padx=(0, 10))
search_button = ctk.CTkButton(search_frame, text="Cari",
command=self.search_menu)
search_button.pack(side="left")
# Menu container
menu_container = ctk.CTkFrame(self.content_frame)
menu_container.pack(fill="both", expand=True, padx=20, pady=20)
# Scrollable menu frame dengan tinggi yang cukup
self.menu_scroll = ctk.CTkScrollableFrame(menu_container, height=400)
self.menu_scroll.pack(fill="both", expand=True)
# Grid untuk menu
self.menu_grid = ctk.CTkFrame(self.menu_scroll, fg_color="transparent")
self.menu_grid.pack(fill="both", expand=True)
# Tampilkan semua menu
self.display_menu(self.menu_data)
def display_menu(self, menu_items):
# Hapus menu sebelumnya
for widget in self.menu_grid.winfo_children():
widget.destroy()
if not menu_items:
empty_label = ctk.CTkLabel(self.menu_grid,
text="Tidak ada menu yang sesuai dengan pencarian",
font=ctk.CTkFont(size=14))
empty_label.pack(pady=50)
return
# Buat baris sesuai kebutuhan
rows = (len(menu_items) + 2) // 3 # Memastikan ada cukup baris
# Konfigurasi grid
for i in range(3):
self.menu_grid.columnconfigure(i, weight=1, uniform="column")
for i in range(rows):
self.menu_grid.rowconfigure(i, weight=0)
# Menampilkan menu dalam grid 3 kolom
for i, item in enumerate(menu_items):
row = i // 3
col = i % 3
self.create_menu_card(self.menu_grid, item, col, row)
# Update grid layout
self.menu_grid.update()
def create_menu_card(self, parent, item, col, row=0):
# Create a card frame dengan ukuran yang lebih konsisten
card = ctk.CTkFrame(parent, width=440, height=560)
card.grid(row=row, column=col, padx=10, pady=10, sticky="nsew")
card.grid_propagate(False) # Pertahankan ukuran yang ditentukan
# Container untuk konten kartu
content_frame = ctk.CTkFrame(card, fg_color="transparent")
content_frame.pack(fill="both", expand=True, padx=10, pady=10)
# Load image dengan ukuran yang lebih kecil
ctk_img = self.load_image_from_url(item["image"], size=(160, 100))
# Set image
img_label = ctk.CTkLabel(content_frame, text="", image=ctk_img)
img_label.pack(pady=(5, 10))
# Product details
name_label = ctk.CTkLabel(content_frame, text=item["name"],
font=ctk.CTkFont(weight="bold"),
wraplength=180)
name_label.pack(pady=(0, 5))
desc_label = ctk.CTkLabel(content_frame, text=item["description"],
wraplength=180, height=40,
font=ctk.CTkFont(size=12))
desc_label.pack(pady=(0, 5))
price_label = ctk.CTkLabel(content_frame, text=f"Rp {item['price']:,}",
font=ctk.CTkFont(weight="bold"))
price_label.pack(pady=(0, 10))
add_button = ctk.CTkButton(content_frame, text="+ Keranjang",
command=lambda i=item: self.add_to_cart(i))
add_button.pack(pady=(0, 5))
def filter_menu(self, category):
# Highlight selected category button
for cat, btn in self.category_buttons.items():
if cat == category:
btn.configure(fg_color=("gray75", "gray25"))
else:
btn.configure(fg_color=("gray70", "gray30"))
if category == "Semua":
filtered_menu = self.menu_data
else:
filtered_menu = [item for item in self.menu_data if item["category"] == category]
self.display_menu(filtered_menu)
def search_menu(self):
search_term = self.search_var.get().lower()
if not search_term:
self.display_menu(self.menu_data)
return
filtered_menu = [
item for item in self.menu_data
if search_term in item["name"].lower() or search_term in item["description"].lower()
]
self.display_menu(filtered_menu)
def add_to_cart(self, item):
# Cek apakah item sudah ada di keranjang
for cart_item in self.cart:
if cart_item["id"] == item["id"]:
cart_item["quantity"] += 1
self.update_cart_count()
messagebox.showinfo("Keranjang", f"{item['name']} ditambahkan ke keranjang (x{cart_item['quantity']})")
return
# Jika belum ada, tambahkan ke keranjang
cart_item = item.copy()
cart_item["quantity"] = 1
self.cart.append(cart_item)
self.update_cart_count()
messagebox.showinfo("Keranjang", f"{item['name']} ditambahkan ke keranjang")
def update_cart_count(self):
total_items = sum(item["quantity"] for item in self.cart)
self.cart_count.configure(text=f"{total_items} item")
# Implementasi login
def show_login(self):
self.clear_content()
# Header
header = ctk.CTkLabel(self.content_frame, text="Login",
font=ctk.CTkFont(size=24, weight="bold"))
header.pack(pady=(20, 30))
# Form frame
login_frame = ctk.CTkFrame(self.content_frame, width=400)
login_frame.pack(pady=20, padx=50, fill="x", expand=False)
# Email field
email_frame = ctk.CTkFrame(login_frame, fg_color="transparent")
email_frame.pack(fill="x", padx=20, pady=(20, 10))
email_label = ctk.CTkLabel(email_frame, text="Email:", anchor="w")
email_label.pack(anchor="w")
self.login_email = ctk.CTkEntry(email_frame, placeholder_text="Masukkan email Anda")
self.login_email.pack(fill="x", pady=(5, 0))
# Password field
password_frame = ctk.CTkFrame(login_frame, fg_color="transparent")
password_frame.pack(fill="x", padx=20, pady=10)
password_label = ctk.CTkLabel(password_frame, text="Password:", anchor="w")
password_label.pack(anchor="w")
self.login_password = ctk.CTkEntry(password_frame, placeholder_text="Masukkan password Anda", show="●")
self.login_password.pack(fill="x", pady=(5, 0))
# Login button
login_button = ctk.CTkButton(login_frame, text="Login",
command=self.process_login,
font=ctk.CTkFont(weight="bold"))
login_button.pack(pady=20, padx=20, fill="x")
# Register link
register_frame = ctk.CTkFrame(login_frame, fg_color="transparent")
register_frame.pack(fill="x", padx=20, pady=(0, 20))
register_label = ctk.CTkLabel(register_frame, text="Belum punya akun?")
register_label.pack(side="left")
register_button = ctk.CTkButton(register_frame, text="Daftar di sini",
fg_color="transparent", hover=False,
command=self.show_register)
register_button.pack(side="left", padx=5)
def process_login(self):
"""Process login form"""
email = self.login_email.get().strip()
password = self.login_password.get()
# Basic validation
if not email or not password:
messagebox.showerror("Error", "Email dan password harus diisi!")
return
# Find user
user_found = None
for user in self.users:
if user["email"].lower() == email.lower():
user_found = user
break
if not user_found:
messagebox.showerror("Error", "Email tidak ditemukan!")
return
# Check password
if user_found["password"] != self.hash_password(password):
messagebox.showerror("Error", "Password salah!")
return
# Successful login
user_data = {
"id": user_found["id"],
"name": user_found["name"],
"email": user_found["email"],
"phone": user_found.get("phone", "")
}
self.current_user = user_data
self.save_session(user_data)
# Update UI
self.nav_profile.configure(text=f"Profil ({user_data['name']})")
messagebox.showinfo("Login Berhasil", f"Selamat datang kembali, {user_data['name']}!")
self.show_home()
# Implementasi register
def show_register(self):
self.clear_content()
# Header
header = ctk.CTkLabel(self.content_frame, text="Daftar Akun Baru",
font=ctk.CTkFont(size=24, weight="bold"))
header.pack(pady=(20, 30))
# Form frame
register_frame = ctk.CTkFrame(self.content_frame, width=400)
register_frame.pack(pady=20, padx=50, fill="both", expand=True)
# Form container with scrollbar
form_container = ctk.CTkScrollableFrame(register_frame)
form_container.pack(fill="both", expand=True, padx=20, pady=20)
# Nama field
name_frame = ctk.CTkFrame(form_container, fg_color="transparent")
name_frame.pack(fill="x", pady=10)
name_label = ctk.CTkLabel(name_frame, text="Nama Lengkap:", anchor="w")
name_label.pack(anchor="w")
self.register_name = ctk.CTkEntry(name_frame, placeholder_text="Masukkan nama lengkap Anda")
self.register_name.pack(fill="x", pady=(5, 0))
# Email field
email_frame = ctk.CTkFrame(form_container, fg_color="transparent")
email_frame.pack(fill="x", pady=10)
email_label = ctk.CTkLabel(email_frame, text="Email:", anchor="w")
email_label.pack(anchor="w")
self.register_email = ctk.CTkEntry(email_frame, placeholder_text="Masukkan email Anda")
self.register_email.pack(fill="x", pady=(5, 0))
# Phone field
phone_frame = ctk.CTkFrame(form_container, fg_color="transparent")
phone_frame.pack(fill="x", pady=10)
phone_label = ctk.CTkLabel(phone_frame, text="Nomor Telepon:", anchor="w")
phone_label.pack(anchor="w")
self.register_phone = ctk.CTkEntry(phone_frame, placeholder_text="Masukkan nomor telepon Anda")
self.register_phone.pack(fill="x", pady=(5, 0))
# Password field
password_frame = ctk.CTkFrame(form_container, fg_color="transparent")
password_frame.pack(fill="x", pady=10)
password_label = ctk.CTkLabel(password_frame, text="Password:", anchor="w")
password_label.pack(anchor="w")
self.register_password = ctk.CTkEntry(password_frame, placeholder_text="Minimal 6 karakter", show="●")
self.register_password.pack(fill="x", pady=(5, 0))
# Confirm Password field
confirm_frame = ctk.CTkFrame(form_container, fg_color="transparent")
confirm_frame.pack(fill="x", pady=10)
confirm_label = ctk.CTkLabel(confirm_frame, text="Konfirmasi Password:", anchor="w")
confirm_label.pack(anchor="w")
self.register_confirm = ctk.CTkEntry(confirm_frame, placeholder_text="Masukkan password kembali", show="●")
self.register_confirm.pack(fill="x", pady=(5, 0))
# Register button
register_button = ctk.CTkButton(form_container, text="Daftar",
command=self.process_register,
font=ctk.CTkFont(weight="bold"))
register_button.pack(pady=20, fill="x")
# Login link
login_frame = ctk.CTkFrame(form_container, fg_color="transparent")
login_frame.pack(fill="x", pady=(0, 10))
login_label = ctk.CTkLabel(login_frame, text="Sudah punya akun?")
login_label.pack(side="left")
login_button = ctk.CTkButton(login_frame, text="Login di sini",
fg_color="transparent", hover=False,
command=self.show_login)
login_button.pack(side="left", padx=5)
def process_register(self):
"""Process registration form"""
name = self.register_name.get().strip()
email = self.register_email.get().strip()
phone = self.register_phone.get().strip()
password = self.register_password.get()
confirm = self.register_confirm.get()
# Validation
if not name or not email or not phone or not password or not confirm:
messagebox.showerror("Error", "Semua field harus diisi!")
return
if len(name) < 3:
messagebox.showerror("Error", "Nama minimal 3 karakter!")
return
if "@" not in email or "." not in email:
messagebox.showerror("Error", "Format email tidak valid!")
return
if not phone.isdigit() or len(phone) < 10:
messagebox.showerror("Error", "Nomor telepon tidak valid!")
return
if len(password) < 6:
messagebox.showerror("Error", "Password minimal 6 karakter!")
return
if password != confirm:
messagebox.showerror("Error", "Password dan konfirmasi tidak cocok!")
return
# Check if email already exists
for user in self.users:
if user["email"].lower() == email.lower():
messagebox.showerror("Error", "Email sudah terdaftar!")
return
# Create new user
new_user = {
"id": str(int(datetime.now().timestamp())),
"name": name,
"email": email,
"phone": phone,
"password": self.hash_password(password),
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"orders": []
}
# Add user to users list
self.users.append(new_user)
self.save_users()
# Create session
user_data = {
"id": new_user["id"],
"name": new_user["name"],
"email": new_user["email"],
"phone": new_user["phone"]
}
self.current_user = user_data
self.save_session(user_data)
# Update UI
self.nav_profile.configure(text=f"Profil ({name})")
messagebox.showinfo("Registrasi Berhasil", f"Selamat datang, {name}! Akun Anda berhasil dibuat.")
self.show_home()
def process_payment(self, order):
"""Process payment and change order status"""
# Create a new toplevel window
payment_window = ctk.CTkToplevel(self)
payment_window.title("Proses Pembayaran")
payment_window.geometry("400x500")
payment_window.grab_set() # Make window modal
# Create scrollable frame for content
main_container = ctk.CTkScrollableFrame(payment_window)
main_container.pack(fill="both", expand=True, padx=10, pady=10)
# Header
header = ctk.CTkLabel(main_container, text="Proses Pembayaran",
font=ctk.CTkFont(size=20, weight="bold"))
header.pack(pady=(10, 10))
# Order info
order_info = ctk.CTkLabel(main_container, text=f"No. Pesanan: #{order['id']}")
order_info.pack(pady=(0, 20))
# Current status
current_status = order.get("status", "pending").upper()
status_colors = {
"PENDING": "#FFC107",
"PROCESSING": "#2196F3",
"COMPLETED": "#4CAF50",
"CANCELLED": "#F44336"
}
status_color = status_colors.get(current_status, "#9E9E9E")
status_frame = ctk.CTkFrame(main_container)
status_frame.pack(fill="x", padx=10, pady=10)
status_label = ctk.CTkLabel(status_frame, text="Status Saat Ini:",
font=ctk.CTkFont(weight="bold"))
status_label.pack(pady=5)
current_status_label = ctk.CTkLabel(status_frame,
text=current_status,
text_color=status_color,
font=ctk.CTkFont(weight="bold", size=16))
current_status_label.pack(pady=5)
# Total amount
total_frame = ctk.CTkFrame(main_container)
total_frame.pack(fill="x", padx=10, pady=10)
total_label = ctk.CTkLabel(total_frame, text="Total Pembayaran:",
font=ctk.CTkFont(weight="bold"))
total_label.pack(pady=5)
amount_label = ctk.CTkLabel(total_frame,
text=f"Rp {order.get('total', 0):,}",
font=ctk.CTkFont(weight="bold", size=16))
amount_label.pack(pady=5)
# Payment method
if "payment" in order:
payment_methods = {
"transfer": "Transfer Bank",
"cod": "COD (Bayar di Tempat)",
"ewallet": "E-Wallet"
}
payment_text = payment_methods.get(order["payment"], order["payment"])
method_frame = ctk.CTkFrame(main_container)
method_frame.pack(fill="x", padx=10, pady=10)
method_label = ctk.CTkLabel(method_frame, text="Metode Pembayaran:",
font=ctk.CTkFont(weight="bold"))
method_label.pack(pady=5)
payment_label = ctk.CTkLabel(method_frame,
text=payment_text,
font=ctk.CTkFont(size=16))
payment_label.pack(pady=5)
# Status selection
status_selection_frame = ctk.CTkFrame(main_container)
status_selection_frame.pack(fill="x", padx=10, pady=10)
selection_label = ctk.CTkLabel(status_selection_frame,
text="Pilih Status Pembayaran:",
font=ctk.CTkFont(weight="bold"))
selection_label.pack(pady=10)
# Status options with radio buttons
status_var = tk.StringVar(value=order.get("status", "pending"))
status_options = [
("Menunggu Pembayaran", "pending", "#FFC107"),
("Diproses", "processing", "#2196F3"),
("Selesai", "completed", "#4CAF50"),
("Dibatalkan", "cancelled", "#F44336")
]
for text, value, color in status_options:
option_frame = ctk.CTkFrame(status_selection_frame, fg_color="transparent")
option_frame.pack(fill="x", pady=5)
radio = ctk.CTkRadioButton(option_frame, text=text,
variable=status_var,
value=value)
radio.pack(side="left")
status_indicator = ctk.CTkLabel(option_frame, text="●", text_color=color)
status_indicator.pack(side="right")
# Instructions based on payment method
if order.get("payment") == "transfer":
instruction_frame = ctk.CTkFrame(main_container)
instruction_frame.pack(fill="x", padx=10, pady=10)
instruction_label = ctk.CTkLabel(instruction_frame,
text="Instruksi Transfer:",
font=ctk.CTkFont(weight="bold"))
instruction_label.pack(pady=(10, 5), anchor="w")
instructions = [
"1. Transfer ke rekening BCA: 1234567890",
"2. Atas nama: PT RasaKita Indonesia",
"3. Jumlah: Rp " + f"{order.get('total', 0):,}",
"4. Setelah transfer, pilih status 'Diproses'"
]
for inst in instructions:
inst_label = ctk.CTkLabel(instruction_frame, text=inst, anchor="w")
inst_label.pack(pady=2, anchor="w")
# Buttons - put these outside the scrollable frame at the bottom of the window
button_frame = ctk.CTkFrame(payment_window, fg_color="transparent")
button_frame.pack(fill="x", padx=20, pady=10)
cancel_btn = ctk.CTkButton(button_frame, text="Batal",
fg_color="gray70", hover_color="gray50",
command=payment_window.destroy)
cancel_btn.pack(side="left", padx=10, expand=True, fill="x")
save_btn = ctk.CTkButton(button_frame, text="Simpan",
command=lambda: self.update_payment_status(order['id'],
status_var.get(),
payment_window))
save_btn.pack(side="left", padx=10, expand=True, fill="x")
def update_payment_status(self, order_id, new_status, window=None):
"""Update payment status and refresh UI"""
# Change status in data
success = self.change_order_status(order_id, new_status)
if success:
status_texts = {
"pending": "Menunggu Pembayaran",
"processing": "Diproses",
"completed": "Selesai",
"cancelled": "Dibatalkan"
}
status_text = status_texts.get(new_status, new_status.capitalize())
messagebox.showinfo("Status Diperbarui", f"Status pesanan telah diubah menjadi {status_text}")
# Refresh profile page if we're on it
if self.content_frame.winfo_children() and "Profil" in self.content_frame.winfo_children()[0].cget("text"):
self.show_profile()
# Close payment window if provided
if window:
window.destroy()
else:
messagebox.showerror("Error", "Gagal mengubah status pesanan")
def change_order_status(self, order_id, new_status):
"""Change the status of an order and record history"""
if self.current_user:
# Find the order in all users
for i, user in enumerate(self.users):
if "orders" in user:
for j, order in enumerate(user["orders"]):
if order["id"] == order_id:
# Don't update if status is the same
if order.get("status") == new_status:
return True
# Update status
self.users[i]["orders"][j]["status"] = new_status
# Add to status history
if "status_history" not in self.users[i]["orders"][j]:
self.users[i]["orders"][j]["status_history"] = []
# Add new status change with timestamp
status_change = {
"status": new_status,
"changed_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"changed_by": self.current_user["name"]
}
self.users[i]["orders"][j]["status_history"].append(status_change)
self.save_users()
return True
return False
def show_profile(self):
self.clear_content()
# If not logged in, show login options
if not self.current_user:
# Header
header = ctk.CTkLabel(self.content_frame, text="Masuk atau Daftar",
font=ctk.CTkFont(size=24, weight="bold"))
header.pack(pady=(40, 20))
# Options frame
options_frame = ctk.CTkFrame(self.content_frame)
options_frame.pack(pady=30, padx=50)
# Login button
login_btn = ctk.CTkButton(options_frame, text="Login",
font=ctk.CTkFont(weight="bold", size=16),
height=45, width=200,
command=self.show_login)
login_btn.pack(pady=(20, 10), padx=30)
# Register button
register_btn = ctk.CTkButton(options_frame, text="Daftar Akun Baru",
font=ctk.CTkFont(weight="bold", size=16),
height=45, width=200,
command=self.show_register)
register_btn.pack(pady=(10, 20), padx=30)
return
# If logged in, show profile
header = ctk.CTkLabel(self.content_frame, text="Profil Saya",
font=ctk.CTkFont(size=24, weight="bold"))
header.pack(pady=(20, 30))
# Profile info
profile_frame = ctk.CTkFrame(self.content_frame)
profile_frame.pack(fill="x", padx=20, pady=10)
user_icon = ctk.CTkLabel(profile_frame, text="👤", font=ctk.CTkFont(size=40))
user_icon.pack(pady=20)
name_label = ctk.CTkLabel(profile_frame, text=self.current_user["name"],
font=ctk.CTkFont(size=18, weight="bold"))
name_label.pack(pady=5)
email_label = ctk.CTkLabel(profile_frame, text=self.current_user["email"])
email_label.pack(pady=2)
phone_label = ctk.CTkLabel(profile_frame, text=self.current_user["phone"])
phone_label.pack(pady=2)
# Logout button
logout_btn = ctk.CTkButton(profile_frame, text="Logout",
fg_color="#FF5252", hover_color="#FF0000",
command=self.logout)
logout_btn.pack(pady=20)
# Show order history
orders_label = ctk.CTkLabel(self.content_frame, text="Riwayat Pesanan",
font=ctk.CTkFont(size=18, weight="bold"))
orders_label.pack(pady=(20, 10), anchor="w", padx=20)
# Get user orders
user_orders = self.get_user_orders(self.current_user["id"])
if not user_orders:
orders_placeholder = ctk.CTkLabel(self.content_frame, text="Belum ada pesanan")
orders_placeholder.pack(pady=20)
else:
# Order history scrollable container
orders_container = ctk.CTkScrollableFrame(self.content_frame, height=300)
orders_container.pack(fill="both", expand=True, padx=20, pady=10)
# Display orders from newest to oldest
for order in reversed(user_orders):
self.create_order_card(orders_container, order)
def get_user_orders(self, user_id):
"""Get all orders for a specific user"""
for user in self.users:
if user["id"] == user_id:
if "orders" in user:
return user["orders"]
else:
return []
return []
def show_order_detail(self, order):
"""Show detailed view of an order"""
# Create a new toplevel window
detail_window = ctk.CTkToplevel(self)
detail_window.title(f"Detail Pesanan #{order['id']}")
detail_window.geometry("600x700")
detail_window.grab_set() # Make window modal
# Main container with scrollbar
main_container = ctk.CTkScrollableFrame(detail_window)
main_container.pack(fill="both", expand=True, padx=20, pady=20)
# Header
header = ctk.CTkLabel(main_container, text=f"Detail Pesanan #{order['id']}",
font=ctk.CTkFont(size=20, weight="bold"))
header.pack(pady=(0, 20))
# Order info
info_frame = ctk.CTkFrame(main_container)
info_frame.pack(fill="x", pady=10)
# Format date
try:
date_obj = datetime.strptime(order["date"], "%Y-%m-%d %H:%M:%S")
formatted_date = date_obj.strftime("%d %b %Y, %H:%M")
except:
formatted_date = order["date"]
# Status with color
status_text = order.get("status", "pending").upper()
status_colors = {
"PENDING": "#FFC107",
"PROCESSING": "#2196F3",
"COMPLETED": "#4CAF50",
"CANCELLED": "#F44336"
}
status_color = status_colors.get(status_text, "#9E9E9E")
# Add info rows
info_rows = [
("Tanggal", formatted_date),
("Status", status_text),
("Total", f"Rp {order.get('total', 0):,}")
]
for label, value in info_rows:
row = ctk.CTkFrame(info_frame, fg_color="transparent")
row.pack(fill="x", pady=5, padx=15)
label_widget = ctk.CTkLabel(row, text=label + ":",
font=ctk.CTkFont(weight="bold"),
width=100, anchor="w")
label_widget.pack(side="left")
value_color = status_color if label == "Status" else None
value_widget = ctk.CTkLabel(row, text=value, anchor="w", text_color=value_color)
value_widget.pack(side="left", fill="x", expand=True)
# Customer info
if "customer" in order:
customer = order["customer"]
customer_frame = ctk.CTkFrame(main_container)
customer_frame.pack(fill="x", pady=10)
customer_header = ctk.CTkLabel(customer_frame, text="Informasi Penerima",
font=ctk.CTkFont(size=16, weight="bold"))
customer_header.pack(anchor="w", padx=15, pady=(10, 5))
customer_rows = [
("Nama", customer.get("name", "-")),
("Alamat", customer.get("address", "-")),
("Telepon", customer.get("phone", "-"))
]
for label, value in customer_rows:
row = ctk.CTkFrame(customer_frame, fg_color="transparent")
row.pack(fill="x", pady=5, padx=15)
label_widget = ctk.CTkLabel(row, text=label + ":",
font=ctk.CTkFont(weight="bold"),
width=100, anchor="w")
label_widget.pack(side="left")
value_widget = ctk.CTkLabel(row, text=value, anchor="w", wraplength=400)
value_widget.pack(side="left", fill="x", expand=True)
# Payment info
if "payment" in order:
payment_methods = {
"transfer": "Transfer Bank",
"cod": "COD (Bayar di Tempat)",
"ewallet": "E-Wallet"
}
payment_text = payment_methods.get(order["payment"], order["payment"])
payment_frame = ctk.CTkFrame(main_container)
payment_frame.pack(fill="x", pady=10)
payment_header = ctk.CTkLabel(payment_frame, text="Informasi Pembayaran",
font=ctk.CTkFont(size=16, weight="bold"))
payment_header.pack(anchor="w", padx=15, pady=(10, 5))
payment_row = ctk.CTkFrame(payment_frame, fg_color="transparent")
payment_row.pack(fill="x", pady=5, padx=15)
method_label = ctk.CTkLabel(payment_row, text="Metode:",
font=ctk.CTkFont(weight="bold"),
width=100, anchor="w")
method_label.pack(side="left")
method_value = ctk.CTkLabel(payment_row, text=payment_text, anchor="w")
method_value.pack(side="left", fill="x", expand=True)
# Notes
if order.get("notes"):
notes_frame = ctk.CTkFrame(main_container)
notes_frame.pack(fill="x", pady=10)
notes_header = ctk.CTkLabel(notes_frame, text="Catatan",
font=ctk.CTkFont(size=16, weight="bold"))
notes_header.pack(anchor="w", padx=15, pady=(10, 5))
notes_text = ctk.CTkLabel(notes_frame, text=order["notes"],
wraplength=500, anchor="w", justify="left")
notes_text.pack(fill="x", padx=15, pady=(0, 10))
# Items
items_frame = ctk.CTkFrame(main_container)
items_frame.pack(fill="x", pady=10)
items_header = ctk.CTkLabel(items_frame, text="Item Pesanan",
font=ctk.CTkFont(size=16, weight="bold"))
items_header.pack(anchor="w", padx=15, pady=(10, 5))
# Table header
table_header = ctk.CTkFrame(items_frame, fg_color="transparent")
table_header.pack(fill="x", padx=15, pady=(5, 0))
name_header = ctk.CTkLabel(table_header, text="Item",
font=ctk.CTkFont(weight="bold"),
width=250)
name_header.pack(side="left")
qty_header = ctk.CTkLabel(table_header, text="Qty",
font=ctk.CTkFont(weight="bold"),
width=50)
qty_header.pack(side="left")
price_header = ctk.CTkLabel(table_header, text="Harga",
font=ctk.CTkFont(weight="bold"),
width=100)
price_header.pack(side="left")
subtotal_header = ctk.CTkLabel(table_header, text="Subtotal",
font=ctk.CTkFont(weight="bold"),
width=100)
subtotal_header.pack(side="left")
# Separator
separator = ctk.CTkFrame(items_frame, height=1, fg_color="gray75")
separator.pack(fill="x", padx=15, pady=5)
# Item rows
for item in order.get("items", []):
item_row = ctk.CTkFrame(items_frame, fg_color="transparent")
item_row.pack(fill="x", padx=15, pady=2)
subtotal = item["price"] * item["quantity"]
name_label = ctk.CTkLabel(item_row, text=item["name"],
width=250, anchor="w", wraplength=240)
name_label.pack(side="left")
qty_label = ctk.CTkLabel(item_row, text=str(item["quantity"]), width=50)
qty_label.pack(side="left")
price_label = ctk.CTkLabel(item_row, text=f"Rp {item['price']:,}", width=100)
price_label.pack(side="left")
subtotal_label = ctk.CTkLabel(item_row, text=f"Rp {subtotal:,}", width=100)
subtotal_label.pack(side="left")
# Another separator
separator2 = ctk.CTkFrame(items_frame, height=1, fg_color="gray75")
separator2.pack(fill="x", padx=15, pady=5)
# Total row
total_row = ctk.CTkFrame(items_frame, fg_color="transparent")
total_row.pack(fill="x", padx=15, pady=5)
total_label = ctk.CTkLabel(total_row, text="Total",
font=ctk.CTkFont(weight="bold", size=14),
width=250, anchor="e")
total_label.pack(side="left")
# Spacer for qty
spacer = ctk.CTkLabel(total_row, text="", width=50)
spacer.pack(side="left")
# Spacer for price
spacer2 = ctk.CTkLabel(total_row, text="", width=100)
spacer2.pack(side="left")
total_amount = ctk.CTkLabel(total_row, text=f"Rp {order.get('total', 0):,}",
font=ctk.CTkFont(weight="bold", size=14),
width=100)
total_amount.pack(side="left")
# Close button
close_button = ctk.CTkButton(detail_window, text="Tutup",
command=detail_window.destroy,
width=200)
close_button.pack(pady=20)
def create_order_card(self, parent, order):
"""Create a card for an order in the order history"""
# Main card container
order_card = ctk.CTkFrame(parent)
order_card.pack(fill="x", pady=10, padx=5)
# Order header
header_frame = ctk.CTkFrame(order_card, fg_color="transparent")
header_frame.pack(fill="x", padx=15, pady=(15, 5))
# Left side: Order ID and date
id_date_frame = ctk.CTkFrame(header_frame, fg_color="transparent")
id_date_frame.pack(side="left", anchor="w")
order_id = ctk.CTkLabel(id_date_frame,
text=f"Pesanan #{order['id']}",
font=ctk.CTkFont(weight="bold", size=14))
order_id.pack(anchor="w")
# Format date to more readable
try:
date_obj = datetime.strptime(order["date"], "%Y-%m-%d %H:%M:%S")
formatted_date = date_obj.strftime("%d %b %Y, %H:%M")
except:
formatted_date = order["date"]
order_date = ctk.CTkLabel(id_date_frame, text=formatted_date, font=ctk.CTkFont(size=12))
order_date.pack(anchor="w")
# Right side: Status and total
status_frame = ctk.CTkFrame(header_frame, fg_color="transparent")
status_frame.pack(side="right", anchor="e")
status_text = order.get("status", "pending").upper()
status_colors = {
"PENDING": "#FFC107", # Amber
"PROCESSING": "#2196F3", # Blue
"COMPLETED": "#4CAF50", # Green
"CANCELLED": "#F44336" # Red
}
status_color = status_colors.get(status_text, "#9E9E9E") # Default gray
status_label = ctk.CTkLabel(status_frame,
text=f"Status: {status_text}",
text_color=status_color,
font=ctk.CTkFont(weight="bold"))
status_label.pack(anchor="e")
total_label = ctk.CTkLabel(status_frame,
text=f"Total: Rp {order.get('total', 0):,}",
font=ctk.CTkFont(weight="bold"))
total_label.pack(anchor="e")
# Separator
separator = ctk.CTkFrame(order_card, height=1, fg_color="gray75")
separator.pack(fill="x", padx=15, pady=10)
# Items container
items_container = ctk.CTkFrame(order_card, fg_color="transparent")
items_container.pack(fill="x", padx=15, pady=(0, 5))
# Show shipping info
if "customer" in order:
customer = order["customer"]
shipping_info = f"Dikirim ke: {customer.get('name', '')}, {customer.get('address', '')}"
shipping_label = ctk.CTkLabel(items_container, text=shipping_info,
font=ctk.CTkFont(size=12),
wraplength=500)
shipping_label.pack(anchor="w", pady=(0, 10))
# Show items in columns
headers_frame = ctk.CTkFrame(items_container, fg_color="transparent")
headers_frame.pack(fill="x", pady=(0, 5))
name_header = ctk.CTkLabel(headers_frame, text="Item", font=ctk.CTkFont(weight="bold", size=12))
name_header.pack(side="left", padx=(0, 10), fill="x", expand=True)
qty_header = ctk.CTkLabel(headers_frame, text="Qty", font=ctk.CTkFont(weight="bold", size=12))
qty_header.pack(side="left", padx=10, fill="x", expand=False)
price_header = ctk.CTkLabel(headers_frame, text="Harga", font=ctk.CTkFont(weight="bold", size=12))
price_header.pack(side="left", padx=10, fill="x", expand=False)
# Items
items_frame = ctk.CTkFrame(items_container, fg_color="transparent")
items_frame.pack(fill="x")
# Limit to max 3 items to save space
max_display = 3
display_count = min(len(order.get("items", [])), max_display)
for i in range(display_count):
item = order["items"][i]
item_row = ctk.CTkFrame(items_frame, fg_color="transparent")
item_row.pack(fill="x", pady=2)
name_label = ctk.CTkLabel(item_row, text=item["name"],
font=ctk.CTkFont(size=12),
wraplength=300)
name_label.pack(side="left", padx=(0, 10), fill="x", expand=True)
qty_label = ctk.CTkLabel(item_row, text=str(item["quantity"]),
font=ctk.CTkFont(size=12))
qty_label.pack(side="left", padx=10, fill="x", expand=False)
price_label = ctk.CTkLabel(item_row, text=f"Rp {item['price']:,}",
font=ctk.CTkFont(size=12))
price_label.pack(side="left", padx=10, fill="x", expand=False)
# If there are more items than the displayed ones
if len(order.get("items", [])) > max_display:
more_items = len(order["items"]) - max_display
more_label = ctk.CTkLabel(items_frame,
text=f"... dan {more_items} item lainnya",
font=ctk.CTkFont(size=12, slant="italic"))
more_label.pack(fill="x", pady=(5, 0))
# Payment method
if "payment" in order:
payment_methods = {
"transfer": "Transfer Bank",
"cod": "COD (Bayar di Tempat)",
"ewallet": "E-Wallet"
}
payment_text = payment_methods.get(order["payment"], order["payment"])
payment_label = ctk.CTkLabel(items_container,
text=f"Metode Pembayaran: {payment_text}",
font=ctk.CTkFont(size=12))
payment_label.pack(anchor="w", pady=(10, 0))
# Notes
if order.get("notes"):
notes_label = ctk.CTkLabel(items_container,
text=f"Catatan: {order['notes']}",
font=ctk.CTkFont(size=12),
wraplength=500)
notes_label.pack(anchor="w", pady=(5, 0))
# Buttons
button_frame = ctk.CTkFrame(order_card, fg_color="transparent")
button_frame.pack(fill="x", padx=15, pady=(10, 15))
# Detail button
detail_btn = ctk.CTkButton(button_frame, text="Lihat Detail",
width=100,
command=lambda o=order: self.show_order_detail(o))
detail_btn.pack(side="right", padx=5)
bayar_btn = ctk.CTkButton(button_frame, text="Bayar Pesanan",
width=120,
command=lambda o=order: self.process_payment(o))
bayar_btn.pack(side="right", padx=5)
def logout(self):
"""Logout current user"""
if messagebox.askyesno("Konfirmasi", "Apakah Anda yakin ingin keluar?"):
self.clear_session()
self.current_user = None
self.nav_profile.configure(text="Login / Daftar")
messagebox.showinfo("Logout", "Anda telah berhasil keluar")
self.show_home()
if __name__ == "__main__":
app = FoodOrderApp()
app.mainloop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment