From 1f904a2ba8d84fffc85606fa58a98c511936e2bb Mon Sep 17 00:00:00 2001 From: Jevinca Marvella Date: Mon, 8 Dec 2025 12:05:30 +0700 Subject: [PATCH] Update File csv dan Struktur database --- detail_transaksi.csv | 2 +- favorites.csv | 1 - main.py | 1067 ++---------------------------------------- transaksi.csv | 2 +- 4 files changed, 47 insertions(+), 1025 deletions(-) delete mode 100644 favorites.csv diff --git a/detail_transaksi.csv b/detail_transaksi.csv index 2d5d78e..0b1d68b 100644 --- a/detail_transaksi.csv +++ b/detail_transaksi.csv @@ -1 +1 @@ -id,transaksi_id,menu_id,nama_menu,jumlah,harga_satuan,subtotal \ No newline at end of file +id,transaksi_id,menu_id,qty,harga_satuan,subtotal_item \ No newline at end of file diff --git a/favorites.csv b/favorites.csv deleted file mode 100644 index e7e46c7..0000000 --- a/favorites.csv +++ /dev/null @@ -1 +0,0 @@ -user_id,menu_id,count \ No newline at end of file diff --git a/main.py b/main.py index ea2690c..9c97e1c 100644 --- a/main.py +++ b/main.py @@ -9,35 +9,16 @@ import os import csv import tkinter as tk -from tkinter import ttk, messagebox, filedialog, simpledialog +from tkinter import ttk, messagebox, filedialog from PIL import Image, ImageTk -from datetime import datetime USERS_CSV = "users.csv" MENU_CSV = "menu.csv" PROMO_CSV = "promo.csv" TRANSAKSI_CSV = "transaksi.csv" DETAIL_TRANSAKSI_CSV = "detail_transaksi.csv" -FAVORITES_CSV = "favorites.csv" IMG_PREVIEW_SIZE = (120, 80) -COLORS = { - # Totoro Forest Theme - 'primary': '#5D8A66', # Forest Green (warna daun) - 'secondary': '#8B7355', # Wood Brown (warna pohon) - 'success': '#7BA05B', # Soft Green - 'danger': '#C1666B', # Soft Red - 'warning': '#E8B86D', # Warm Yellow - 'info': '#6B9AC4', # Sky Blue - 'light': '#F5F3E7', # Cream (kertas antik) - 'dark': '#3E4E3A', # Dark Forest - 'totoro_gray': '#9CA3AF', # Abu-abu Totoro - 'totoro_belly': '#E8DCC4', # Perut Totoro (cream) - 'leaf_green': '#A8D5A3', # Hijau muda daun - 'tree_brown': '#6B4423', # Cokelat tua kayu - 'soft_cream': '#FAF8F1', # Background lembut -} - def ensure_file(path, fieldnames): if not os.path.exists(path): @@ -78,9 +59,9 @@ def init_db_csv(): ensure_file(USERS_CSV, ["id", "username", "password", "role"]) ensure_file(MENU_CSV, ["id", "nama", "kategori", "harga", "stok", "foto", "tersedia", "item_discount_pct"]) ensure_file(PROMO_CSV, ["code", "type", "value", "min_total"]) - ensure_file(TRANSAKSI_CSV, ["id", "user_id", "tanggal", "nomor_meja", "total", "status", "metode_pembayaran"]) - ensure_file(DETAIL_TRANSAKSI_CSV, ["id", "transaksi_id", "menu_id", "nama_menu", "jumlah", "harga_satuan", "subtotal"]) - ensure_file(FAVORITES_CSV, ["user_id", "menu_id", "count"]) + ensure_file(TRANSAKSI_CSV, ["id", "user_id", "nomor_meja", "total", "status", "promo_code", "subtotal", "item_discount", "promo_discount", "tanggal"]) + ensure_file(DETAIL_TRANSAKSI_CSV, ["id", "transaksi_id", "menu_id", "qty", "harga_satuan", "subtotal_item"]) + seed_defaults() @@ -386,6 +367,17 @@ def promo_get(code): + + + + + + + + + + + # 19 juta lapangan badmin # Buat logika diskon + promok @@ -441,225 +433,32 @@ def apply_discounts_and_promo(cart_items, promo_code=None): 'total': round(total, 2) } -# TRANSAKSI FUNCTIONS (2nd Person : Jevvvv) -from datetime import datetime - -def create_transaksi(user_id, nomor_meja, cart_items): - """Membuat transaksi baru dengan status 'Menunggu'""" - rows = read_all(TRANSAKSI_CSV) - trans_id = next_int_id(rows, "id") - - # Hitung total - total = 0.0 - for item in cart_items: - total += item['subtotal'] - - trans = { - "id": trans_id, - "user_id": str(user_id), - "tanggal": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - "nomor_meja": str(nomor_meja), - "total": str(total), - "status": "Menunggu", # Status: Menunggu, Diproses, Selesai - "metode_pembayaran": "" - } - rows.append(trans) - write_all(TRANSAKSI_CSV, ["id", "user_id", "tanggal", "nomor_meja", "total", "status", "metode_pembayaran"], rows) - - # Simpan detail transaksi - detail_rows = read_all(DETAIL_TRANSAKSI_CSV) - for item in cart_items: - detail_id = next_int_id(detail_rows, "id") - detail = { - "id": detail_id, - "transaksi_id": trans_id, - "menu_id": str(item['menu_id']), - "nama_menu": item['nama'], - "jumlah": str(item['qty']), - "harga_satuan": str(item['harga']), - "subtotal": str(item['subtotal']) - } - detail_rows.append(detail) - write_all(DETAIL_TRANSAKSI_CSV, ["id", "transaksi_id", "menu_id", "nama_menu", "jumlah", "harga_satuan", "subtotal"], detail_rows) - - # Kurangi stok - for item in cart_items: - menu_decrease_stock(item['menu_id'], item['qty']) - - return trans_id -def get_all_transaksi(status=None): - """Ambil semua transaksi, bisa filter by status""" - rows = read_all(TRANSAKSI_CSV) - result = [] - for r in rows: - if status and r.get("status") != status: - continue - result.append({ - 'id': r.get('id'), - 'user_id': r.get('user_id'), - 'tanggal': r.get('tanggal'), - 'nomor_meja': r.get('nomor_meja'), - 'total': float(r.get('total', 0)), - 'status': r.get('status'), - 'metode_pembayaran': r.get('metode_pembayaran', '') - }) - return result -def get_transaksi_detail(transaksi_id): - """Ambil detail item dari transaksi""" - rows = read_all(DETAIL_TRANSAKSI_CSV) - result = [] - for r in rows: - if r.get("transaksi_id") == str(transaksi_id): - result.append({ - 'id': r.get('id'), - 'menu_id': r.get('menu_id'), - 'nama_menu': r.get('nama_menu'), - 'jumlah': int(r.get('jumlah', 0)), - 'harga_satuan': float(r.get('harga_satuan', 0)), - 'subtotal': float(r.get('subtotal', 0)) - }) - return result -def update_transaksi_status(transaksi_id, status): - """Update status transaksi (Menunggu -> Diproses -> Selesai)""" - rows = read_all(TRANSAKSI_CSV) - for r in rows: - if r.get("id") == str(transaksi_id): - r["status"] = status - break - write_all(TRANSAKSI_CSV, ["id", "user_id", "tanggal", "nomor_meja", "total", "status", "metode_pembayaran"], rows) -# FAVORITES FUNCTIONS (2nd Person: JEVVVV) -def add_to_favorites(user_id, menu_id): - """Tambah atau update counter menu favorit user""" - rows = read_all(FAVORITES_CSV) - found = False - for r in rows: - if r.get("user_id") == str(user_id) and r.get("menu_id") == str(menu_id): - r["count"] = str(int(r.get("count", 0)) + 1) - found = True - break - - if not found: - rows.append({ - "user_id": str(user_id), - "menu_id": str(menu_id), - "count": "1" - }) - - write_all(FAVORITES_CSV, ["user_id", "menu_id", "count"], rows) -def get_user_favorites(user_id, limit=5): - """Ambil menu favorit user (paling sering dipesan)""" - rows = read_all(FAVORITES_CSV) - user_favs = [] - for r in rows: - if r.get("user_id") == str(user_id): - user_favs.append({ - 'menu_id': int(r.get("menu_id")), - 'count': int(r.get("count", 0)) - }) - - # Sort by count descending - user_favs.sort(key=lambda x: x['count'], reverse=True) - - # Ambil detail menu - result = [] - for fav in user_favs[:limit]: - menu = menu_get(fav['menu_id']) - if menu: - result.append({ - 'menu': menu, - 'count': fav['count'] - }) - - return result # Wilayah dikuasai UI -def add_forest_decoration(parent_frame): - deco = tk.Label(parent_frame, text="πŸŒΏπŸƒ", - font=("Arial", 12), - bg=parent_frame['bg'] if 'bg' in parent_frame.keys() else COLORS['soft_cream'], - fg=COLORS['leaf_green']) - return deco + + class App: def __init__(self, root): self.root = root - self.root.title("🌳 Cafe Totoro - Forest Coffee & Treats 🐾") - self.root.configure(bg=COLORS['tree_brown']) + self.root.title("Cafe Totoro Mania") self.session = None self.img_cache = {} - self.cart = [] - self.setup_styles() self.setup_ui() - def setup_styles(self): - style = ttk.Style() - style.theme_use('clam') - - # Totoro Theme Colors - style.configure('TFrame', background=COLORS['soft_cream']) - style.configure('TLabel', background=COLORS['soft_cream'], - foreground=COLORS['dark'], font=('Georgia', 10)) - style.configure('TNotebook', background=COLORS['light']) - style.configure('TNotebook.Tab', padding=[20, 10], - font=('Georgia', 10, 'bold')) - - # Button styles - Totoro Theme - style.configure('Primary.TButton', - background=COLORS['primary'], - foreground='white', - font=('Georgia', 10, 'bold'), - borderwidth=0, - focuscolor='none') - style.map('Primary.TButton', - background=[('active', COLORS['leaf_green']), - ('pressed', COLORS['success'])]) - - style.configure('Success.TButton', - background=COLORS['success'], - foreground='white', - font=('Georgia', 10, 'bold')) - style.map('Success.TButton', - background=[('active', COLORS['leaf_green'])]) - - style.configure('Danger.TButton', - background=COLORS['danger'], - foreground='white', - font=('Georgia', 10, 'bold')) - - style.configure('Accent.TButton', - background=COLORS['warning'], - foreground=COLORS['dark'], - font=('Georgia', 11, 'bold'), - padding=10) - - # Treeview style - Forest Theme - style.configure('Treeview', - background=COLORS['soft_cream'], - foreground=COLORS['dark'], - fieldbackground=COLORS['soft_cream'], - font=('Georgia', 9), - rowheight=25) - style.configure('Treeview.Heading', - background=COLORS['primary'], - foreground='white', - font=('Georgia', 10, 'bold'), - relief='flat') - style.map('Treeview', - background=[('selected', COLORS['leaf_green'])], - foreground=[('selected', 'white')]) def setup_ui(self): self.root.geometry("1000x650") self.root.resizable(False, False) @@ -668,83 +467,20 @@ class App: def login_frame(self): for w in self.root.winfo_children(): w.destroy() - - # Main container dengan background cafe - main_frame = tk.Frame(self.root, bg=COLORS['tree_brown']) - main_frame.pack(fill='both', expand=True) + frame = ttk.Frame(self.root, padding=20) + frame.pack(expand=True) - # Center frame - center = tk.Frame(main_frame, bg='white', relief='raised', bd=3) - center.place(relx=0.5, rely=0.5, anchor='center', width=500, height=550) - - - # Logo/Header Section - Totoro Theme - header_frame = tk.Frame(center, bg=COLORS['primary'], height=140) - header_frame.pack(fill='x') - # Totoro ASCII Art / Emoji - tk.Label(header_frame, text="🌳", font=("Arial", 28), - bg=COLORS['primary'], fg=COLORS['leaf_green']).pack(side='left', padx=10, pady=10) - - tk.Label(header_frame, text="γ…€πŸΎ", font=("Arial", 40), - bg=COLORS['primary'], fg=COLORS['totoro_gray']).pack(pady=5) - - tk.Label(header_frame, text="🌿", font=("Arial", 28), - bg=COLORS['primary'], fg=COLORS['leaf_green']).pack(side='right', padx=10, pady=10) - - tk.Label(header_frame, text="CAFE TOTORO", - font=("Georgia", 28, "bold"), bg=COLORS['primary'], - fg=COLORS['soft_cream']).pack() - - tk.Label(header_frame, text="✦ Welcome to the Forest Cafe ✦", - font=("Georgia", 11, "italic"), bg=COLORS['primary'], - fg=COLORS['totoro_belly']).pack(pady=5) - - # Form Section - form_frame = tk.Frame(center, bg=COLORS['soft_cream'], padx=40, pady=30) - form_frame.pack(fill='both', expand=True) - - tk.Label(form_frame, text="Mari masuk ke hutan ajaib kami", - font=("Georgia", 10, "italic"), bg=COLORS['soft_cream'], - fg=COLORS['totoro_gray']).pack(pady=(0,25)) - - # Username - tk.Label(form_frame, text="πŸ‘€ Username", font=("Arial", 11, "bold"), - bg='white', fg=COLORS['dark']).pack(anchor='w', pady=(10,5)) + ttk.Label(frame, text="Login kak", font=("Arial", 24)).grid(row=0, column=0, columnspan=2, pady=10) + ttk.Label(frame, text="Username:").grid(row=1, column=0, sticky='e', pady=5) self.username_var = tk.StringVar() - username_entry = tk.Entry(form_frame, textvariable=self.username_var, - font=("Arial", 12), relief='solid', bd=2, - bg='white', fg=COLORS['dark']) - username_entry.pack(fill='x', ipady=8) - - # Password - tk.Label(form_frame, text="πŸ”’ Password", font=("Georgia", 11, "bold"), - bg=COLORS['soft_cream'], fg=COLORS['dark']).pack(anchor='w', pady=(15,5)) + ttk.Entry(frame, textvariable=self.username_var, width=30).grid(row=1, column=1, pady=5) + ttk.Label(frame, text="Password:").grid(row=2, column=0, sticky='e', pady=5) self.password_var = tk.StringVar() - password_entry = tk.Entry(form_frame, textvariable=self.password_var, - show="●", font=("Arial", 12), relief='solid', bd=2, - bg='white', fg=COLORS['dark']) - password_entry.pack(fill='x', ipady=8) + ttk.Entry(frame, textvariable=self.password_var, show="*", width=30).grid(row=2, column=1, pady=5) - # Login Button - login_btn = tk.Button(form_frame, text="🌿 MASUK KE HUTAN", command=self.handle_login, - font=("Georgia", 13, "bold"), bg=COLORS['primary'], - fg='white', relief='flat', cursor='hand2', bd=0, - activebackground=COLORS['success']) - login_btn.pack(fill='x', pady=(25,10), ipady=10) - - # Hover effect - login_btn.bind("", lambda e: login_btn.config(bg=COLORS['leaf_green'])) - login_btn.bind("", lambda e: login_btn.config(bg=COLORS['primary'])) - - # Info text - info_frame = tk.Frame(center, bg=COLORS['totoro_belly'], height=60) - info_frame.pack(fill='x', side='bottom') - - tk.Label(info_frame, text="πŸƒ Tip: Gunakan user/user123 untuk role pembeli", - font=("Georgia", 9), bg=COLORS['totoro_belly'], - fg=COLORS['dark']).pack(pady=15) + ttk.Button(frame, text="Login", command=self.handle_login).grid(row=3, column=0, columnspan=2, pady=12) def handle_login(self): u = self.username_var.get().strip() @@ -763,85 +499,33 @@ class App: def logout(self): self.session = None self.img_cache.clear() - self.cart = [] self.login_frame() def dashboard_frame(self): for w in self.root.winfo_children(): w.destroy() - - # Header - top = tk.Frame(self.root, bg=COLORS['primary'], height=50) + top = ttk.Frame(self.root) top.pack(fill='x') - - welcome_text = f"🐾 Halo, {self.session['username']} β€’ {self.session['role'].upper()}" - tk.Label(top, text=welcome_text, - font=("Georgia", 12, "bold"), - bg=COLORS['primary'], - fg=COLORS['soft_cream']).pack(side='left', padx=20, pady=12) - - logout_btn = tk.Button(top, text="πŸŒ™ Logout", command=self.logout, - font=("Georgia", 10, "bold"), - bg=COLORS['secondary'], - fg='white', - relief='flat', - cursor='hand2', - padx=15, pady=5) - logout_btn.pack(side='right', padx=20, pady=8) - logout_btn.bind("", lambda e: logout_btn.config(bg=COLORS['danger'])) - logout_btn.bind("", lambda e: logout_btn.config(bg=COLORS['secondary'])) - - # Notebook tabs + ttk.Label(top, text=f"User: {self.session['username']} | Role: {self.session['role']}", + font=("Arial", 12)).pack(side='left', padx=10, pady=6) + ttk.Button(top, text="Logout", command=self.logout).pack(side='right', padx=10) main = ttk.Notebook(self.root) - main.pack(fill='both', expand=True, padx=10, pady=10) - - role = self.session['role'] - - # Setup tabs berdasarkan role - if role == 'pembeli': - self.tab_order = ttk.Frame(main) - self.tab_favorites = ttk.Frame(main) - main.add(self.tab_order, text="πŸ›’ Pesan Menu") - main.add(self.tab_favorites, text="⭐ Menu Favorit") - self.build_order_tab(self.tab_order) - self.build_favorites_tab(self.tab_favorites) - - elif role == 'waiter': - self.tab_waiter = ttk.Frame(main) - main.add(self.tab_waiter, text="πŸ“‹ Dashboard Waiter") - self.build_waiter_tab(self.tab_waiter) - - elif role == 'admin': - self.tab_menu_view = ttk.Frame(main) - self.tab_menu_manage = ttk.Frame(main) - self.tab_promo = ttk.Frame(main) - self.tab_waiter = ttk.Frame(main) - main.add(self.tab_menu_view, text="πŸ“– Menu - View") - main.add(self.tab_menu_manage, text="βš™οΈ Menu - Manage") - main.add(self.tab_promo, text="🎁 Promo - Manage") - main.add(self.tab_waiter, text="πŸ“‹ Dashboard Waiter") - self.build_menu_view_tab(self.tab_menu_view) + main.pack(fill='both', expand=True, padx=10, pady=8) + self.tab_menu_manage = ttk.Frame(main) + self.tab_menu_view = ttk.Frame(main) + self.tab_promo = ttk.Frame(main) + + main.add(self.tab_menu_view, text="Menu - View") + if self.session['role'] == 'admin': + main.add(self.tab_menu_manage, text="Menu - Manage") + main.add(self.tab_promo, text="Promo - Manage") + else: + pass + + self.build_menu_view_tab(self.tab_menu_view) + if self.session['role'] == 'admin': self.build_menu_manage_tab(self.tab_menu_manage) self.build_promo_tab(self.tab_promo) - self.build_waiter_tab(self.tab_waiter) - - else: # kasir, owner - self.tab_menu_view = ttk.Frame(main) - main.add(self.tab_menu_view, text="πŸ“– Menu - View") - self.build_menu_view_tab(self.tab_menu_view) - - def build_order_tab(self, parent): - """Tab pemesanan untuk pembeli""" - print("πŸ”§ build_order_tab called") - - # Initialize cart dan qty variable PERTAMA KALI - if not hasattr(self, 'cart'): - self.cart = [] - print(" βœ“ Cart initialized") - - if not hasattr(self, 'order_qty_var'): - self.order_qty_var = tk.StringVar(value="1") - print(" βœ“ Quantity variable initialized") def build_menu_view_tab(self, parent): for w in parent.winfo_children(): @@ -1184,671 +868,10 @@ class App: self.reload_promo_table() - # ========== TAB: ORDER SYSTEM (PEMBELI) ========== - # ========== TAB: ORDER SYSTEM (PEMBELI) ========== - def build_order_tab(self, parent): - """Tab pemesanan untuk pembeli""" - # Initialize cart kalau belum ada - if not hasattr(self, 'cart'): - self.cart = [] - print("πŸ”§ Cart initialized") - - # Clear parent - for w in parent.winfo_children(): - w.destroy() - - # Container utama - container = ttk.Frame(parent) - container.pack(fill='both', expand=True, padx=10, pady=10) - - # ========== LEFT SIDE: DAFTAR MENU ========== - left = ttk.Frame(container, relief='solid', borderwidth=1) - left.pack(side='left', fill='both', expand=True, padx=(0, 5)) - - # Header - ttk.Label(left, text="🌿 Menu Hutan Totoro", - font=("Georgia", 14, "bold")).pack(pady=8) - - # Filter pencarian - filter_frm = ttk.Frame(left) - filter_frm.pack(fill='x', padx=10, pady=5) - - ttk.Label(filter_frm, text="Cari Menu:").pack(side='left', padx=5) - self.order_search_var = tk.StringVar() - ttk.Entry(filter_frm, textvariable=self.order_search_var, - width=20).pack(side='left', padx=5) - ttk.Button(filter_frm, text="πŸ” Cari", - command=self.reload_order_menu).pack(side='left', padx=3) - ttk.Button(filter_frm, text="πŸ”„ Reset", - command=self.reset_order_search).pack(side='left', padx=3) - - # Treeview menu - cols = ("ID", "Nama", "Kategori", "Harga", "Stok") - self.order_menu_tree = ttk.Treeview(left, columns=cols, - show='headings', height=15) - for c in cols: - self.order_menu_tree.heading(c, text=c) - w = 50 if c == "ID" else (180 if c == "Nama" else 100) - self.order_menu_tree.column(c, width=w) - self.order_menu_tree.pack(fill='both', expand=True, padx=10, pady=5) - - # ========== SECTION: TAMBAH KE KERANJANG ========== - add_frame = tk.Frame(left, bg=COLORS['soft_cream']) - add_frame.pack(fill='x', padx=10, pady=8) - - tk.Label(add_frame, text="Jumlah:", font=("Georgia", 10, "bold"), - bg=COLORS['soft_cream'], fg=COLORS['dark']).pack(side='left', padx=5) - - # Initialize qty variable - if not hasattr(self, 'order_qty_var'): - self.order_qty_var = tk.StringVar(value="1") - - # Frame untuk tombol +/- - qty_frame = tk.Frame(add_frame, bg=COLORS['soft_cream']) - qty_frame.pack(side='left', padx=5) - - # Tombol MINUS - btn_minus = tk.Button(qty_frame, text="βž–", width=3, - command=self.decrease_qty, - bg=COLORS['danger'], fg='white', - font=('Arial', 10, 'bold'), - relief='flat', cursor='hand2', - activebackground='#C1666B') - btn_minus.pack(side='left', padx=2) - - # Entry QUANTITY - qty_entry = tk.Entry(qty_frame, textvariable=self.order_qty_var, - width=6, justify='center', - font=('Georgia', 11, 'bold'), - bg='white', fg=COLORS['dark']) - qty_entry.pack(side='left', padx=3) - - # Tombol PLUS - btn_plus = tk.Button(qty_frame, text="βž•", width=3, - command=self.increase_qty, - bg=COLORS['success'], fg='white', - font=('Arial', 10, 'bold'), - relief='flat', cursor='hand2', - activebackground='#7BA05B') - btn_plus.pack(side='left', padx=2) - - # Tombol TAMBAH KE KERANJANG - btn_add = tk.Button(add_frame, text="πŸ›’ Tambah ke Keranjang", - command=self.add_to_cart, - bg=COLORS['primary'], fg='white', - font=('Georgia', 10, 'bold'), - relief='flat', cursor='hand2', - padx=15, pady=5, - activebackground=COLORS['leaf_green']) - btn_add.pack(side='left', padx=10) - - # ========== RIGHT SIDE: KERANJANG ========== - right = ttk.Frame(container, relief='solid', borderwidth=1) - right.pack(side='right', fill='both', padx=(5, 0)) - right.config(width=380) - - # Header keranjang - ttk.Label(right, text="🧺 Keranjang Pesanan", - font=("Georgia", 14, "bold")).pack(pady=8) - - # Treeview keranjang - cart_cols = ("Menu", "Harga", "Qty", "Subtotal") - self.cart_tree = ttk.Treeview(right, columns=cart_cols, - show='headings', height=12) - for c in cart_cols: - self.cart_tree.heading(c, text=c) - w = 130 if c == "Menu" else 75 - self.cart_tree.column(c, width=w) - self.cart_tree.pack(fill='both', padx=10, pady=5) - - # Tombol update & hapus - cart_btn_frm = ttk.Frame(right) - cart_btn_frm.pack(fill='x', padx=10, pady=5) - - ttk.Button(cart_btn_frm, text="πŸ—‘οΈ Hapus Item", - command=self.remove_from_cart, - style='Danger.TButton').pack(side='left', padx=3) - ttk.Button(cart_btn_frm, text="πŸ“ Update Qty", - command=self.update_cart_qty, - style='Primary.TButton').pack(side='left', padx=3) - - # Label total - self.cart_total_var = tk.StringVar(value="Total: Rp 0") - tk.Label(right, textvariable=self.cart_total_var, - font=("Georgia", 13, "bold"), - bg=COLORS['soft_cream'], fg=COLORS['primary'], - pady=8).pack(pady=5, fill='x') - - # Form nomor meja - meja_frm = tk.Frame(right, bg=COLORS['soft_cream'], pady=10) - meja_frm.pack(fill='x', padx=10, pady=8) - - tk.Label(meja_frm, text="πŸ“ Nomor Meja:", - font=("Georgia", 10, "bold"), - bg=COLORS['soft_cream'], fg=COLORS['dark']).pack(side='left', padx=5) - - self.nomor_meja_var = tk.StringVar() - tk.Entry(meja_frm, textvariable=self.nomor_meja_var, - width=10, font=('Arial', 11), justify='center').pack(side='left', padx=5) - - # Tombol PESAN SEKARANG - btn_order = tk.Button(right, text="πŸ“‹ PESAN SEKARANG", - command=self.submit_order, - bg=COLORS['warning'], fg=COLORS['dark'], - font=('Georgia', 12, 'bold'), - relief='flat', cursor='hand2', - pady=12, - activebackground='#E8B86D') - btn_order.pack(pady=10, padx=10, fill='x') - - # Debug info - print("πŸ”§ Order tab initialized successfully") - - # Load menu - self.reload_order_menu() - def reload_order_menu(self): - """Reload daftar menu untuk order""" - if not hasattr(self, 'order_menu_tree'): - return - - for item in self.order_menu_tree.get_children(): - self.order_menu_tree.delete(item) - - search = "" - if hasattr(self, 'order_search_var'): - search = self.order_search_var.get().strip() - - menus = menu_list(available_only=True, search_text=search or None) - - for m in menus: - mid, nama, kategori, harga, stok, foto, tersedia, disc = m - self.order_menu_tree.insert("", tk.END, values=( - mid, nama, kategori, f"Rp {harga:,.0f}", stok - )) - def reset_order_search(self): - """Reset pencarian menu""" - if hasattr(self, 'order_search_var'): - self.order_search_var.set("") - self.reload_order_menu() - def increase_qty(self): - """Tingkatkan jumlah item""" - try: - current = int(self.order_qty_var.get()) - self.order_qty_var.set(str(current + 1)) - except: - self.order_qty_var.set("1") - def decrease_qty(self): - """Kurangi jumlah item""" - try: - current = int(self.order_qty_var.get()) - if current > 1: - self.order_qty_var.set(str(current - 1)) - except: - self.order_qty_var.set("1") - - def add_to_cart(self): - """Tambah item ke keranjang""" - sel = self.order_menu_tree.selection() - if not sel: - messagebox.showwarning("Pilih Menu", "Pilih menu terlebih dahulu!") - return - - try: - qty = int(self.order_qty_var.get()) - if qty <= 0: - raise ValueError() - except: - messagebox.showerror("Error", "Jumlah harus angka positif!") - return - - item_vals = self.order_menu_tree.item(sel)['values'] - menu_id = item_vals[0] - nama = item_vals[1] - harga_display = str(item_vals[3]) - - # Parse harga - hapus semua kecuali angka - harga_clean = "" - for char in harga_display: - if char.isdigit(): - harga_clean += char - - try: - harga = float(harga_clean) - except: - messagebox.showerror("Error", f"Format harga salah: {harga_display}") - return - - stok = int(item_vals[4]) - - if qty > stok: - messagebox.showerror("Stok Habis", f"Stok hanya tersedia {stok}") - return - - if not hasattr(self, 'cart'): - self.cart = [] - - # Cek apakah sudah ada di cart - found = False - for cart_item in self.cart: - if cart_item['menu_id'] == menu_id: - new_qty = cart_item['qty'] + qty - if new_qty > stok: - messagebox.showerror("Stok Habis", - f"Total pesanan ({new_qty}) melebihi stok ({stok})") - return - cart_item['qty'] = new_qty - cart_item['subtotal'] = cart_item['harga'] * new_qty - found = True - break - - if not found: - self.cart.append({ - 'menu_id': menu_id, - 'nama': nama, - 'harga': harga, - 'qty': qty, - 'subtotal': harga * qty - }) - - self.update_cart_display() - messagebox.showinfo("Berhasil", f"βœ… {nama} x{qty} ditambahkan ke keranjang!") - - def update_cart_display(self): - """Update tampilan keranjang""" - if not hasattr(self, 'cart_tree'): - return - - for item in self.cart_tree.get_children(): - self.cart_tree.delete(item) - - total = 0.0 - for item in self.cart: - self.cart_tree.insert("", tk.END, values=( - item['nama'], - f"Rp {item['harga']:,.0f}", - item['qty'], - f"Rp {item['subtotal']:,.0f}" - )) - total += item['subtotal'] - - if hasattr(self, 'cart_total_var'): - self.cart_total_var.set(f"Total: Rp {total:,.0f}") - - def remove_from_cart(self): - """Hapus item dari keranjang""" - sel = self.cart_tree.selection() - if not sel: - messagebox.showwarning("Pilih Item", "Pilih item yang akan dihapus!") - return - - idx = self.cart_tree.index(sel) - del self.cart[idx] - self.update_cart_display() - messagebox.showinfo("Dihapus", "Item berhasil dihapus dari keranjang") - - def update_cart_qty(self): - """Update jumlah item di keranjang""" - sel = self.cart_tree.selection() - if not sel: - messagebox.showwarning("Pilih Item", "Pilih item yang akan diupdate!") - return - - idx = self.cart_tree.index(sel) - item = self.cart[idx] - - dialog = tk.Toplevel(self.root) - dialog.title("Update Jumlah") - dialog.geometry("320x180") - dialog.transient(self.root) - dialog.grab_set() - dialog.configure(bg=COLORS['soft_cream']) - - tk.Label(dialog, text=f"🍽️ {item['nama']}", - font=("Georgia", 12, "bold"), - bg=COLORS['soft_cream'], fg=COLORS['dark']).pack(pady=15) - - qty_frame = tk.Frame(dialog, bg=COLORS['soft_cream']) - qty_frame.pack(pady=15) - - qty_var = tk.StringVar(value=str(item['qty'])) - - def decrease(): - try: - val = int(qty_var.get()) - if val > 1: - qty_var.set(str(val - 1)) - except: - pass - - def increase(): - try: - val = int(qty_var.get()) - qty_var.set(str(val + 1)) - except: - pass - - tk.Button(qty_frame, text="βž–", width=3, command=decrease, - bg=COLORS['danger'], fg='white', - font=('Arial', 11, 'bold'), relief='flat').pack(side='left', padx=5) - - tk.Entry(qty_frame, textvariable=qty_var, width=8, - justify='center', font=('Georgia', 12, 'bold')).pack(side='left', padx=5) - - tk.Button(qty_frame, text="βž•", width=3, command=increase, - bg=COLORS['success'], fg='white', - font=('Arial', 11, 'bold'), relief='flat').pack(side='left', padx=5) - - def save(): - try: - new_qty = int(qty_var.get()) - if new_qty > 0: - item['qty'] = new_qty - item['subtotal'] = item['harga'] * new_qty - self.update_cart_display() - dialog.destroy() - messagebox.showinfo("Berhasil", f"βœ… {item['nama']} diupdate!") - else: - messagebox.showerror("Error", "Jumlah harus lebih dari 0!") - except: - messagebox.showerror("Error", "Input salah!") - - tk.Button(dialog, text="βœ… Simpan", command=save, - bg=COLORS['primary'], fg='white', - font=('Georgia', 11, 'bold'), - relief='flat', padx=30, pady=8).pack(pady=15) - - def submit_order(self): - """Submit pesanan""" - if not self.cart or len(self.cart) == 0: - messagebox.showwarning("Keranjang Kosong", - "Tambahkan menu ke keranjang terlebih dahulu!") - return - - nomor_meja = self.nomor_meja_var.get().strip() - if not nomor_meja: - messagebox.showwarning("Nomor Meja", "Masukkan nomor meja!") - return - - total = sum(i['subtotal'] for i in self.cart) - - try: - trans_id = create_transaksi(self.session['id'], nomor_meja, self.cart) - - for item in self.cart: - add_to_favorites(self.session['id'], item['menu_id']) - - messagebox.showinfo("Berhasil", - f"βœ… Pesanan berhasil dibuat!\n\n" - f"πŸ†” ID Transaksi: {trans_id}\n" - f"πŸ“ Nomor Meja: {nomor_meja}\n" - f"πŸ’° Total: Rp {total:,.0f}\n\n" - f"⏳ Status: Menunggu") - - self.cart = [] - self.nomor_meja_var.set("") - self.order_qty_var.set("1") - self.update_cart_display() - self.reload_order_menu() - - except Exception as e: - messagebox.showerror("Error", f"Gagal membuat pesanan:\n{str(e)}") - - # ========== TAB: WAITER DASHBOARD ========== - def build_waiter_tab(self, parent): - """Dashboard untuk waiter mengelola pesanan""" - for w in parent.winfo_children(): - w.destroy() - - ttk.Label(parent, text="🐾 Totoro's Kitchen - Kelola Pesanan", - font=("Georgia", 16, "bold")).pack(pady=10) - - # Filter status - filter_frm = ttk.Frame(parent) - filter_frm.pack(fill='x', padx=10, pady=5) - ttk.Label(filter_frm, text="Filter Status:").pack(side='left', padx=5) - ttk.Button(filter_frm, text="πŸ• Menunggu", - command=lambda: self.reload_waiter_orders("Menunggu")).pack(side='left', padx=3) - ttk.Button(filter_frm, text="⏳ Diproses", - command=lambda: self.reload_waiter_orders("Diproses")).pack(side='left', padx=3) - ttk.Button(filter_frm, text="βœ… Selesai", - command=lambda: self.reload_waiter_orders("Selesai")).pack(side='left', padx=3) - ttk.Button(filter_frm, text="πŸ“‹ Semua", - command=lambda: self.reload_waiter_orders(None)).pack(side='left', padx=3) - ttk.Button(filter_frm, text="πŸ”„ Refresh", - command=lambda: self.reload_waiter_orders()).pack(side='right', padx=10) - - # Treeview pesanan - cols = ("ID", "Tanggal", "Meja", "Total", "Status") - self.waiter_tree = ttk.Treeview(parent, columns=cols, show='headings', height=10) - for c in cols: - self.waiter_tree.heading(c, text=c) - w = 50 if c == "ID" else (150 if c == "Tanggal" else 80) - self.waiter_tree.column(c, width=w) - self.waiter_tree.pack(fill='both', expand=True, padx=10, pady=10) - self.waiter_tree.bind("<>", self.on_waiter_select) - - # Detail pesanan - detail_frm = ttk.LabelFrame(parent, text="πŸ“ Detail Pesanan", padding=10) - detail_frm.pack(fill='both', padx=10, pady=5) - - self.waiter_detail_text = tk.Text(detail_frm, height=8, width=80, state='disabled') - self.waiter_detail_text.pack(side='left', fill='both', expand=True) - - # Tombol aksi - btn_frm = ttk.Frame(parent) - btn_frm.pack(fill='x', padx=10, pady=10) - ttk.Button(btn_frm, text="⏳ Proses Pesanan", - command=lambda: self.change_order_status("Diproses")).pack(side='left', padx=5) - ttk.Button(btn_frm, text="βœ… Selesai Dilayani", - command=lambda: self.change_order_status("Selesai")).pack(side='left', padx=5) - - self.reload_waiter_orders() - - def reload_waiter_orders(self, status=None): - """Reload daftar pesanan untuk waiter""" - for item in self.waiter_tree.get_children(): - self.waiter_tree.delete(item) - - orders = get_all_transaksi(status=status) - - for order in orders: - status_icon = {"Menunggu": "πŸ•", "Diproses": "⏳", "Selesai": "βœ…"}.get(order['status'], "") - self.waiter_tree.insert("", tk.END, values=( - order['id'], - order['tanggal'], - order['nomor_meja'], - f"Rp {order['total']:,.0f}", - f"{status_icon} {order['status']}" - )) - - def on_waiter_select(self, event): - """Ketika waiter pilih pesanan, tampilkan detail""" - sel = self.waiter_tree.selection() - if not sel: - return - - trans_id = self.waiter_tree.item(sel)['values'][0] - details = get_transaksi_detail(trans_id) - - self.waiter_detail_text.config(state='normal') - self.waiter_detail_text.delete('1.0', tk.END) - - text = f"ID Transaksi: {trans_id}\n" - text += f"{'='*50}\n" - text += f"{'Menu':<25} {'Qty':<5} {'Harga':<12} {'Subtotal':<12}\n" - text += f"{'-'*50}\n" - - for d in details: - text += f"{d['nama_menu']:<25} {d['jumlah']:<5} Rp {d['harga_satuan']:>8,.0f} Rp {d['subtotal']:>8,.0f}\n" - - self.waiter_detail_text.insert('1.0', text) - self.waiter_detail_text.config(state='disabled') - - def change_order_status(self, new_status): - """Ubah status pesanan""" - sel = self.waiter_tree.selection() - if not sel: - messagebox.showwarning("Pilih Pesanan", "Pilih pesanan terlebih dahulu!") - return - - trans_id = self.waiter_tree.item(sel)['values'][0] - current_status = self.waiter_tree.item(sel)['values'][4].split()[-1] - - # Validasi flow status - if current_status == "Selesai": - messagebox.showinfo("Info", "Pesanan sudah selesai dilayani!") - return - - # ========== LANJUTAN DARI ARTIFACTS SEBELUMNYA ========== -# Paste kode ini setelah fungsi change_order_status - - if new_status == "Selesai" and current_status == "Menunggu": - if not messagebox.askyesno("Konfirmasi", "Pesanan belum diproses. Langsung selesai?"): - return - - update_transaksi_status(trans_id, new_status) - messagebox.showinfo("Berhasil", f"Status pesanan diubah menjadi: {new_status}") - self.reload_waiter_orders() - - # ========== TAB: FAVORITES (PEMBELI) ========== - def build_favorites_tab(self, parent): - """Tab menu favorit untuk pembeli""" - for w in parent.winfo_children(): - w.destroy() - - ttk.Label(parent, text="⭐ Menu Favorit Totoro", - font=("Georgia", 16, "bold")).pack(pady=10) - - ttk.Label(parent, text="πŸƒ Menu yang paling sering Anda pesan dari hutan kami", - font=("Georgia", 10, "italic")).pack(pady=5) - - # Treeview favorites - cols = ("Nama Menu", "Kategori", "Harga", "Sering Dipesan") - self.fav_tree = ttk.Treeview(parent, columns=cols, show='headings', height=12) - for c in cols: - self.fav_tree.heading(c, text=c) - w = 200 if c == "Nama Menu" else 120 - self.fav_tree.column(c, width=w) - self.fav_tree.pack(fill='both', expand=True, padx=10, pady=10) - - # Tombol pesan cepat - btn_frm = ttk.Frame(parent) - btn_frm.pack(pady=10) - ttk.Label(btn_frm, text="Pesan Cepat - Jumlah:").pack(side='left', padx=5) - self.fav_qty_var = tk.StringVar(value="1") - ttk.Entry(btn_frm, textvariable=self.fav_qty_var, width=8).pack(side='left', padx=5) - ttk.Button(btn_frm, text="πŸš€ Pesan Langsung", - command=self.quick_order_from_fav).pack(side='left', padx=10) - ttk.Button(btn_frm, text="πŸ”„ Refresh", - command=self.reload_favorites).pack(side='left', padx=5) - - self.reload_favorites() - - def reload_favorites(self): - """Reload menu favorit user""" - for item in self.fav_tree.get_children(): - self.fav_tree.delete(item) - - try: - favs = get_user_favorites(self.session['id'], limit=10) - - if not favs: - # Tampilkan pesan kosong - item_id = self.fav_tree.insert("", tk.END, values=( - "πŸƒ Belum ada menu favorit. Pesan sekarang!", "-", "-", "0" - )) - self.fav_tree.item(item_id, tags=('empty',)) - return - - for fav in favs: - menu = fav['menu'] - mid, nama, kategori, harga, stok, foto, tersedia, disc = menu - self.fav_tree.insert("", tk.END, values=( - f"⭐ {nama}", - kategori, - f"Rp {harga:,.0f}", - f"πŸ”₯ {fav['count']}x" - ), tags=(str(mid),)) - except Exception as e: - print(f"Error reload favorites: {e}") - self.fav_tree.insert("", tk.END, values=( - "❌ Error memuat favorit", "-", "-", "0" - )) - - def quick_order_from_fav(self): - """Pesan langsung dari menu favorit""" - sel = self.fav_tree.selection() - if not sel: - messagebox.showwarning("Pilih Menu", "Pilih menu favorit terlebih dahulu!") - return - - # Cek jika kosong - tags = self.fav_tree.item(sel)['tags'] - if 'empty' in tags: - messagebox.showinfo("Info", "Belum ada menu favorit. Silakan pesan menu terlebih dahulu!") - return - - item_vals = self.fav_tree.item(sel)['values'] - - # Ambil menu_id dari tags - menu_id = int(self.fav_tree.item(sel)['tags'][0]) - menu = menu_get(menu_id) - - if not menu: - messagebox.showerror("Error", "Menu tidak ditemukan!") - return - - mid, nama, kategori, harga, stok, foto, tersedia, disc = menu - - if not tersedia or stok == 0: - messagebox.showwarning("Stok Habis", f"{nama} sedang tidak tersedia!") - return - - try: - qty = int(self.fav_qty_var.get()) - if qty <= 0: - raise ValueError() - except: - messagebox.showerror("Error", "Jumlah harus angka positif!") - return - - if qty > stok: - messagebox.showerror("Stok Tidak Cukup", f"Stok hanya tersedia {stok}") - return - - # Tanya nomor meja - nomor_meja = tk.simpledialog.askstring("Nomor Meja", "Masukkan nomor meja Anda:") - if not nomor_meja: - return - - # Buat transaksi langsung - cart_item = [{ - 'menu_id': mid, - 'nama': nama, - 'harga': harga, - 'qty': qty, - 'subtotal': harga * qty - }] - - try: - trans_id = create_transaksi(self.session['id'], nomor_meja, cart_item) - add_to_favorites(self.session['id'], mid) - - messagebox.showinfo("Berhasil", - f"βœ… Pesanan Cepat Berhasil!\n\n" - f"{nama} x{qty}\n" - f"Total: Rp {harga * qty:,.0f}\n" - f"Meja: {nomor_meja}\n\n" - f"ID Transaksi: {trans_id}") - - self.reload_favorites() - - except Exception as e: - messagebox.showerror("Error", f"Gagal membuat pesanan: {e}") # Done @@ -1858,4 +881,4 @@ if __name__ == "__main__": init_db_csv() root = tk.Tk() app = App(root) - root.mainloop() + root.mainloop() \ No newline at end of file diff --git a/transaksi.csv b/transaksi.csv index 8d64c03..6ae0c5a 100644 --- a/transaksi.csv +++ b/transaksi.csv @@ -1 +1 @@ -id,user_id,tanggal,nomor_meja,total,status,metode_pembayaran \ No newline at end of file +id,user_id,nomor_meja,total,status,promo_code,subtotal,item_discount,promo_discount,tanggal \ No newline at end of file