Created
May 29, 2025 16:32
-
-
Save Xnuvers007/f69e3df6480c4dd5d17d85f432109b3b to your computer and use it in GitHub Desktop.
food ordering
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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