"""akun default buat login : - admin / admin123 (role admin) - kasir / kasir123 (role kasir) - waiter / waiter123 (role waiter) - user / user123 (role pembeli) - owner / owner123 (role pemilik) """ import os import csv import tkinter as tk 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 = { 'primary': '#2C3E50', # Dark Blue 'secondary': '#E67E22', # Orange 'success': '#27AE60', # Green 'danger': '#E74C3C', # Red 'warning': '#F39C12', # Yellow 'info': '#3498DB', # Light Blue 'light': '#ECF0F1', # Light Gray 'dark': '#34495E', # Dark Gray 'bg_gradient_start': '#667eea', # Purple 'bg_gradient_end': '#764ba2', # Dark Purple 'cafe_brown': '#8B4513', # Saddle Brown 'cafe_cream': '#F5DEB3', # Wheat 'cafe_green': '#228B22', # Forest Green } def ensure_file(path, fieldnames): if not os.path.exists(path): with open(path, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() def read_all(path): if not os.path.exists(path): return [] with open(path, newline="", encoding="utf-8") as f: reader = csv.DictReader(f) return list(reader) def write_all(path, fieldnames, rows): with open(path, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) def next_int_id(rows, id_field="id"): max_id = 0 for r in rows: try: v = int(r.get(id_field, 0) or 0) if v > max_id: max_id = v except: continue return str(max_id + 1) 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"]) seed_defaults() # buat masukin data/sample ke database csv def seed_defaults(): users = read_all(USERS_CSV) if not users: defaults = [ ('admin','admin123','admin'), ('kasir','kasir123','kasir'), ('waiter','waiter123','waiter'), ('user','user123','pembeli'), ('owner','owner123','pemilik'), ] rows = [] for i,(u,p,r) in enumerate(defaults, start=1): rows.append({"id": str(i), "username": u, "password": p, "role": r}) write_all(USERS_CSV, ["id","username","password","role"], rows) menu_rows = read_all(MENU_CSV) if not menu_rows: sample = [ ('Americano','Minuman',20000,10,None,1,0), ('Latte','Minuman',25000,5,None,1,10), ('Banana Cake','Dessert',30000,2,None,1,0), ('Nasi Goreng','Makanan',35000,0,None,0,0), ] rows = [] for i,(name,kategori,harga,stok,foto,tersedia,disc) in enumerate(sample, start=1): rows.append({ "id": str(i), "nama": name, "kategori": kategori, "harga": str(harga), "stok": str(stok), "foto": foto or "", "tersedia": str(tersedia), "item_discount_pct": str(disc) }) write_all(MENU_CSV, ["id","nama","kategori","harga","stok","foto","tersedia","item_discount_pct"], rows) promo_rows = read_all(PROMO_CSV) if not promo_rows: promos = [ ('PARDEDE','percent',10,0), ('BOTAK','fixed',5000,20000), ] rows = [] for code,ptype,val,min_total in promos: rows.append({ "code": code, "type": ptype, "value": str(val), "min_total": str(min_total) }) write_all(PROMO_CSV, ["code","type","value","min_total"], rows) def authenticate(username, password): rows = read_all(USERS_CSV) for r in rows: if r.get("username") == username and r.get("password") == password: return {'id': int(r.get("id")), 'username': r.get("username"), 'role': r.get("role")} return None # Wilayah dikuasai Menu def menu_add(nama, kategori, harga, stok, foto, item_discount_pct=0): rows = read_all(MENU_CSV) new_id = next_int_id(rows, "id") tersedia = "1" if int(stok) > 0 else "0" rows.append({ "id": new_id, "nama": nama, "kategori": kategori, "harga": str(float(harga)), "stok": str(int(stok)), "foto": foto or "", "tersedia": tersedia, "item_discount_pct": str(float(item_discount_pct)) }) write_all(MENU_CSV, ["id","nama","kategori","harga","stok","foto","tersedia","item_discount_pct"], rows) def menu_update(menu_id, nama, kategori, harga, stok, foto, item_discount_pct=0): rows = read_all(MENU_CSV) found = False for r in rows: if r.get("id") == str(menu_id): r["nama"] = nama r["kategori"] = kategori r["harga"] = str(float(harga)) r["stok"] = str(int(stok)) r["foto"] = foto or "" r["tersedia"] = "1" if int(stok) > 0 else "0" r["item_discount_pct"] = str(float(item_discount_pct)) found = True break if found: write_all(MENU_CSV, ["id","nama","kategori","harga","stok","foto","tersedia","item_discount_pct"], rows) else: raise ValueError("Menu id tidak ditemukan") def menu_delete(menu_id): rows = read_all(MENU_CSV) newrows = [r for r in rows if r.get("id") != str(menu_id)] write_all(MENU_CSV, ["id","nama","kategori","harga","stok","foto","tersedia","item_discount_pct"], newrows) def menu_list(kategori=None, available_only=False, search_text=None): rows = read_all(MENU_CSV) out = [] for r in rows: if kategori and r.get("kategori") != kategori: continue if available_only and r.get("tersedia") != "1": continue if search_text: s = search_text.lower() if s not in (r.get("nama","").lower() or "") and s not in (r.get("kategori","").lower() or ""): continue try: mid = int(r.get("id") or 0) except: mid = r.get("id") try: harga = float(r.get("harga") or 0.0) except: harga = 0.0 try: stok = int(float(r.get("stok") or 0)) except: stok = 0 foto = r.get("foto") or None tersedia = 1 if r.get("tersedia") == "1" else 0 try: item_disc = float(r.get("item_discount_pct") or 0.0) except: item_disc = 0.0 out.append((mid, r.get("nama"), r.get("kategori"), harga, stok, foto, tersedia, item_disc)) out.sort(key=lambda x: int(x[0])) return out def menu_get(menu_id): rows = read_all(MENU_CSV) for r in rows: if r.get("id") == str(menu_id): try: mid = int(r.get("id") or 0) except: mid = r.get("id") try: harga = float(r.get("harga") or 0.0) except: harga = 0.0 try: stok = int(float(r.get("stok") or 0)) except: stok = 0 foto = r.get("foto") or None tersedia = 1 if r.get("tersedia") == "1" else 0 try: item_disc = float(r.get("item_discount_pct") or 0.0) except: item_disc = 0.0 return (mid, r.get("nama"), r.get("kategori"), harga, stok, foto, tersedia, item_disc) return None def menu_decrease_stock(menu_id, qty): rows = read_all(MENU_CSV) found = False for r in rows: if r.get("id") == str(menu_id): found = True try: stok = int(float(r.get("stok") or 0)) except: stok = 0 if stok < qty: return False, "Stok tidak cukup" newstok = stok - qty r["stok"] = str(newstok) r["tersedia"] = "1" if newstok > 0 else "0" break if not found: return False, "Menu tidak ditemukan" write_all(MENU_CSV, ["id","nama","kategori","harga","stok","foto","tersedia","item_discount_pct"], rows) return True, newstok # Reza balap liar # wilayah dikuasai promo def promo_add(code, ptype, value, min_total=0): rows = read_all(PROMO_CSV) for r in rows: if r.get("code") == code: raise ValueError("Kode promo sudah ada") rows.append({ "code": code, "type": ptype, "value": str(float(value)), "min_total": str(float(min_total)) }) write_all(PROMO_CSV, ["code","type","value","min_total"], rows) def promo_update(code, ptype, value, min_total=0): rows = read_all(PROMO_CSV) found = False for r in rows: if r.get("code") == code: r["type"] = ptype r["value"] = str(float(value)) r["min_total"] = str(float(min_total)) found = True break if not found: raise ValueError("Promo tidak ditemukan") write_all(PROMO_CSV, ["code","type","value","min_total"], rows) def promo_delete(code): rows = read_all(PROMO_CSV) newrows = [r for r in rows if r.get("code") != code] write_all(PROMO_CSV, ["code","type","value","min_total"], newrows) def promo_list(): rows = read_all(PROMO_CSV) out = [] for r in rows: try: val = float(r.get("value") or 0.0) except: val = 0.0 try: mt = float(r.get("min_total") or 0.0) except: mt = 0.0 out.append((r.get("code"), r.get("type"), val, mt)) out.sort(key=lambda x: x[0] or "") return out def promo_get(code): rows = read_all(PROMO_CSV) for r in rows: if r.get("code") == code: try: val = float(r.get("value") or 0.0) except: val = 0.0 try: mt = float(r.get("min_total") or 0.0) except: mt = 0.0 return (r.get("code"), r.get("type"), val, mt) return None # 19 juta lapangan badmin # Buat logika diskon + promok def apply_discounts_and_promo(cart_items, promo_code=None): subtotal = 0.0 item_discount_total = 0.0 menu_rows = read_all(MENU_CSV) menu_dict = {r["id"]: r for r in menu_rows} for it in cart_items: mid = str(it.get('menu_id')) r = menu_dict.get(mid) if not r: continue try: price = float(r.get("harga") or 0.0) except: price = 0.0 try: item_disc_pct = float(r.get("item_discount_pct") or 0.0) except: item_disc_pct = 0.0 qty = int(it.get('qty', 1)) line = price * qty subtotal += line if item_disc_pct and item_disc_pct > 0: item_discount_total += (price * qty) * (item_disc_pct / 100.0) promo_discount = 0.0 promo_applied = None if promo_code: p = promo_get(promo_code) if p: _, ptype, val, min_total = p if subtotal >= (min_total or 0.0): if ptype == 'percent': promo_discount = (subtotal - item_discount_total) * (val / 100.0) else: promo_discount = val promo_applied = promo_code total = subtotal - item_discount_total - promo_discount if total < 0: total = 0.0 return { 'subtotal': round(subtotal, 2), 'item_discount': round(item_discount_total, 2), 'promo_code': promo_applied, 'promo_discount': round(promo_discount, 2), '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 class App: def __init__(self, root): self.root = root 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') # Button styles style.configure('Primary.TButton', background=COLORS['secondary'], foreground='white', font=('Arial', 10, 'bold')) style.map('Primary.TButton', background=[('active', COLORS['warning'])]) style.configure('Success.TButton', background=COLORS['success'], foreground='white', font=('Arial', 10, 'bold')) style.configure('Danger.TButton', background=COLORS['danger'], foreground='white', font=('Arial', 10, 'bold')) # Treeview style style.configure('Treeview', background=COLORS['light'], foreground=COLORS['dark'], font=('Arial', 9)) style.configure('Treeview.Heading', background=COLORS['primary'], foreground='white', font=('Arial', 10, 'bold')) style.map('Treeview', background=[('selected', COLORS['info'])]) def setup_ui(self): self.root.geometry("1000x650") self.root.resizable(False, False) self.login_frame() 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['cafe_brown']) main_frame.pack(fill='both', 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 header_frame = tk.Frame(center, bg=COLORS['cafe_green'], height=120) header_frame.pack(fill='x') tk.Label(header_frame, text="☕", font=("Arial", 48), bg=COLORS['cafe_green'], fg='white').pack(pady=5) tk.Label(header_frame, text="CAFE TOTORO MANIA", font=("Arial", 24, "bold"), bg=COLORS['cafe_green'], fg='white').pack() tk.Label(header_frame, text="~ Your Cozy Coffee Corner ~", font=("Arial", 11, "italic"), bg=COLORS['cafe_green'], fg=COLORS['cafe_cream']).pack(pady=2) # Form Section form_frame = tk.Frame(center, bg='white', padx=40, pady=30) form_frame.pack(fill='both', expand=True) tk.Label(form_frame, text="Selamat Datang! 👋", font=("Arial", 18, "bold"), bg='white', fg=COLORS['cafe_brown']).pack(pady=(0,10)) tk.Label(form_frame, text="Silakan login untuk melanjutkan", font=("Arial", 10), bg='white', fg=COLORS['dark']).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)) self.username_var = tk.StringVar() username_entry = tk.Entry(form_frame, textvariable=self.username_var, font=("Arial", 12), relief='solid', bd=2) username_entry.pack(fill='x', ipady=8) # Password tk.Label(form_frame, text="🔒 Password", font=("Arial", 11, "bold"), bg='white', fg=COLORS['dark']).pack(anchor='w', pady=(15,5)) self.password_var = tk.StringVar() password_entry = tk.Entry(form_frame, textvariable=self.password_var, show="●", font=("Arial", 12), relief='solid', bd=2) password_entry.pack(fill='x', ipady=8) # Login Button login_btn = tk.Button(form_frame, text="🔐 LOGIN", command=self.handle_login, font=("Arial", 13, "bold"), bg=COLORS['cafe_green'], fg='white', relief='flat', cursor='hand2', bd=0) login_btn.pack(fill='x', pady=(25,10), ipady=10) # Hover effect login_btn.bind("", lambda e: login_btn.config(bg=COLORS['success'])) login_btn.bind("", lambda e: login_btn.config(bg=COLORS['cafe_green'])) # Info text info_frame = tk.Frame(center, bg=COLORS['light'], height=60) info_frame.pack(fill='x', side='bottom') tk.Label(info_frame, text="💡 Tip: Gunakan user/user123 untuk pembeli", font=("Arial", 9), bg=COLORS['light'], fg=COLORS['dark']).pack(pady=15) def handle_login(self): u = self.username_var.get().strip() p = self.password_var.get().strip() if not u or not p: messagebox.showwarning("Input", "Masukkan username & password") return user = authenticate(u,p) if not user: messagebox.showerror("Gagal", "Username atau password salah") return self.session = user messagebox.showinfo("Sukses", f"Login berhasil sebagai {user['role']}") self.dashboard_frame() 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() top = ttk.Frame(self.root) top.pack(fill='x') 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=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) def build_menu_view_tab(self, parent): for w in parent.winfo_children(): w.destroy() left = ttk.Frame(parent, width=600) right = ttk.Frame(parent, width=380) left.pack(side='left', fill='both', expand=True, padx=6, pady=6) right.pack(side='right', fill='y', padx=6, pady=6) filter_frame = ttk.Frame(left) filter_frame.pack(fill='x', pady=6) ttk.Label(filter_frame, text="Cari / Nama atau Kategori:").pack(side='left', padx=3) self.view_search_var = tk.StringVar() ttk.Entry(filter_frame, textvariable=self.view_search_var, width=30).pack(side='left', padx=3) ttk.Button(filter_frame, text="Cari", command=self.reload_view_table).pack(side='left', padx=3) ttk.Button(filter_frame, text="Reset", command=self.reset_view_filters).pack(side='left', padx=3) ttk.Button(filter_frame, text="Hanya Tersedia", command=lambda: self.reload_view_table(available_only=True)).pack(side='left', padx=6) cols = ("ID","Nama","Kategori","Harga","Stok","Tersedia","ItemDisc%") self.view_tree = ttk.Treeview(left, columns=cols, show='headings', height=18) for c in cols: self.view_tree.heading(c, text=c) self.view_tree.column(c, width=90 if c!="Nama" else 200) self.view_tree.pack(fill='both', expand=True) self.view_tree.bind("<>", self.on_view_select) ttk.Label(right, text="Preview Item", font=("Arial", 12, "bold")).pack(pady=6) self.preview_label = ttk.Label(right, text="Pilih menu di kiri") self.preview_label.pack() self.preview_img_label = ttk.Label(right) self.preview_img_label.pack(pady=6) self.preview_detail = tk.Text(right, width=45, height=12) self.preview_detail.pack() self.reload_view_table() def reload_view_table(self, available_only=False): s = self.view_search_var.get().strip() if hasattr(self, 'view_search_var') else "" results = menu_list(search_text=s or None, available_only=available_only) for r in self.view_tree.get_children(): self.view_tree.delete(r) for row in results: mid,nama,kategori,harga,stok,foto,tersedia,item_disc = row self.view_tree.insert("", tk.END, values=(mid,nama,kategori,harga,stok, "Yes" if tersedia else "No", item_disc)) def reset_view_filters(self): self.view_search_var.set("") self.reload_view_table() def on_view_select(self, event): sel = self.view_tree.selection() if not sel: return item = self.view_tree.item(sel)['values'] menu_id = item[0] data = menu_get(menu_id) if not data: return mid,nama,kategori,harga,stok,foto,tersedia,item_disc = data self.preview_detail.delete('1.0', tk.END) txt = f"ID: {mid}\nNama: {nama}\nKategori: {kategori}\nHarga: {harga}\nStok: {stok}\nTersedia: {'Yes' if tersedia else 'No'}\nItem Discount: {item_disc}%\nFoto path: {foto}\n" self.preview_detail.insert(tk.END, txt) if foto and os.path.exists(foto): try: img = Image.open(foto) img.thumbnail(IMG_PREVIEW_SIZE) tkimg = ImageTk.PhotoImage(img) self.img_cache['preview'] = tkimg self.preview_img_label.config(image=tkimg) except Exception as e: self.preview_img_label.config(image='') else: self.preview_img_label.config(image='') def build_menu_manage_tab(self, parent): for w in parent.winfo_children(): w.destroy() topfrm = ttk.Frame(parent) topfrm.pack(fill='x', padx=6, pady=6) ttk.Label(topfrm, text="Kelola Menu", font=("Arial", 14, "bold")).pack(side='left') ttk.Button(topfrm, text="Tambah Menu", command=self.open_add_menu_window).pack(side='right', padx=6) cols = ("ID","Nama","Kategori","Harga","Stok","Tersedia","ItemDisc%") self.manage_tree = ttk.Treeview(parent, columns=cols, show='headings', height=18) for c in cols: self.manage_tree.heading(c, text=c) self.manage_tree.column(c, width=100 if c!="Nama" else 220) self.manage_tree.pack(fill='both', padx=6, pady=6) btnfrm = ttk.Frame(parent) btnfrm.pack(pady=6) ttk.Button(btnfrm, text="Edit Terpilih", command=self.open_edit_menu_window).pack(side='left', padx=6) ttk.Button(btnfrm, text="Hapus Terpilih", command=self.delete_selected_menu).pack(side='left', padx=6) ttk.Button(btnfrm, text="Reload", command=self.reload_manage_table).pack(side='left', padx=6) self.reload_manage_table() def reload_manage_table(self): for r in self.manage_tree.get_children(): self.manage_tree.delete(r) rows = menu_list() for row in rows: mid,nama,kategori,harga,stok,foto,tersedia,item_disc = row self.manage_tree.insert("", tk.END, values=(mid,nama,kategori,harga,stok,"Yes" if tersedia else "No", item_disc)) def open_add_menu_window(self): w = tk.Toplevel(self.root) w.title("Tambah Menu") frm = ttk.Frame(w,padding=10) frm.pack() labels = ["Nama","Kategori","Harga","Stok","Foto path","Item Discount (%)"] vars = {} for i,lab in enumerate(labels): ttk.Label(frm, text=lab).grid(row=i, column=0, sticky='e', pady=4) vars[lab] = tk.StringVar() ttk.Entry(frm, textvariable=vars[lab], width=40).grid(row=i, column=1, pady=4) ttk.Button(frm, text="Pilih Foto", command=lambda: self.select_file(vars["Foto path"])).grid(row=4, column=2, padx=6) def save(): try: nama = vars["Nama"].get().strip() kategori = vars["Kategori"].get().strip() harga = float(vars["Harga"].get()) stok = int(vars["Stok"].get()) foto = vars["Foto path"].get().strip() or None item_disc = float(vars["Item Discount (%)"].get() or 0) except Exception as e: messagebox.showerror("Input error", "Periksa kembali input (Harga/Stok harus angka)") return menu_add(nama,kategori,harga,stok,foto,item_disc) messagebox.showinfo("Sukses","Menu ditambahkan") w.destroy() self.reload_manage_table() self.reload_view_table() ttk.Button(frm, text="Simpan", command=save).grid(row=len(labels), column=1, pady=8) def open_edit_menu_window(self): sel = self.manage_tree.selection() if not sel: messagebox.showwarning("Pilih", "Pilih menu terlebih dahulu") return item = self.manage_tree.item(sel)['values'] menu_id = item[0] data = menu_get(menu_id) if not data: messagebox.showerror("Error", "Data menu tidak ditemukan") return mid,nama,kategori,harga,stok,foto,tersedia,item_disc = data w = tk.Toplevel(self.root) w.title("Edit Menu") frm = ttk.Frame(w,padding=10) frm.pack() labels = ["Nama","Kategori","Harga","Stok","Foto path","Item Discount (%)"] vars = {} defaults = [nama,kategori,str(harga),str(stok),foto or "",str(item_disc or 0)] for i,lab in enumerate(labels): ttk.Label(frm, text=lab).grid(row=i, column=0, sticky='e', pady=4) vars[lab] = tk.StringVar(value=defaults[i]) ttk.Entry(frm, textvariable=vars[lab], width=40).grid(row=i, column=1, pady=4) ttk.Button(frm, text="Pilih Foto", command=lambda: self.select_file(vars["Foto path"])).grid(row=4, column=2, padx=6) def save(): try: nama = vars["Nama"].get().strip() kategori = vars["Kategori"].get().strip() harga = float(vars["Harga"].get()) stok = int(vars["Stok"].get()) foto = vars["Foto path"].get().strip() or None item_disc = float(vars["Item Discount (%)"].get() or 0) except: messagebox.showerror("Input error", "Periksa input") return menu_update(menu_id, nama, kategori, harga, stok, foto, item_disc) messagebox.showinfo("Sukses","Menu diperbarui") w.destroy() self.reload_manage_table() self.reload_view_table() ttk.Button(frm, text="Update", command=save).grid(row=len(labels), column=1, pady=8) def delete_selected_menu(self): sel = self.manage_tree.selection() if not sel: messagebox.showwarning("Pilih", "Pilih menu untuk dihapus") return item = self.manage_tree.item(sel)['values'] menu_id = item[0] if messagebox.askyesno("Konfirmasi", "Hapus menu terpilih?"): menu_delete(menu_id) messagebox.showinfo("Dihapus", "Menu berhasil dihapus") self.reload_manage_table() self.reload_view_table() def select_file(self, var): p = filedialog.askopenfilename(title="Pilih file gambar", filetypes=[("Image files","*.png;*.jpg;*.jpeg;*.gif;*.bmp"),("All files","*.*")]) if p: var.set(p) def build_promo_tab(self, parent): for w in parent.winfo_children(): w.destroy() top = ttk.Frame(parent) top.pack(fill='x', pady=6) ttk.Label(top, text="Promo Codes", font=("Arial", 14, "bold")).pack(side='left', padx=6) ttk.Button(top, text="Tambah Promo", command=self.open_add_promo).pack(side='right', padx=6) cols = ("Code","Type","Value","MinTotal") self.promo_tree = ttk.Treeview(parent, columns=cols, show='headings', height=12) for c in cols: self.promo_tree.heading(c, text=c) self.promo_tree.column(c, width=120) self.promo_tree.pack(fill='x', padx=6, pady=6) btnfrm = ttk.Frame(parent) btnfrm.pack(pady=6) ttk.Button(btnfrm, text="Edit Promo", command=self.open_edit_promo).pack(side='left', padx=6) ttk.Button(btnfrm, text="Hapus Promo", command=self.delete_selected_promo).pack(side='left', padx=6) ttk.Button(btnfrm, text="Reload", command=self.reload_promo_table).pack(side='left', padx=6) self.reload_promo_table() def reload_promo_table(self): for r in self.promo_tree.get_children(): self.promo_tree.delete(r) for p in promo_list(): self.promo_tree.insert("", tk.END, values=p) def open_add_promo(self): w = tk.Toplevel(self.root) w.title("Tambah Promo") w.geometry("350x230") w.transient(self.root) w.grab_set() frm = ttk.Frame(w, padding=15) frm.pack(fill="both", expand=True) vars = { 'code': tk.StringVar(), 'type': tk.StringVar(value='percent'), 'value': tk.StringVar(), 'min_total': tk.StringVar(value='0') } ttk.Label(frm, text="Code:").grid(row=0, column=0, sticky='e', pady=5) ttk.Entry(frm, textvariable=vars['code'], width=20).grid(row=0, column=1) ttk.Label(frm, text="Type (percent/fixed):").grid(row=1, column=0, sticky='e', pady=5) ttk.Entry(frm, textvariable=vars['type'], width=20).grid(row=1, column=1) ttk.Label(frm, text="Value:").grid(row=2, column=0, sticky='e', pady=5) ttk.Entry(frm, textvariable=vars['value'], width=20).grid(row=2, column=1) ttk.Label(frm, text="Min Total:").grid(row=3, column=0, sticky='e', pady=5) ttk.Entry(frm, textvariable=vars['min_total'], width=20).grid(row=3, column=1) def save(): try: code = vars['code'].get().strip().upper() ptype = vars['type'].get().strip() val = float(vars['value'].get()) mt = float(vars['min_total'].get() or 0) if ptype not in ('percent', 'fixed'): raise ValueError("type harus 'percent' atau 'fixed'") except Exception as e: messagebox.showerror("Error", f"Input salah: {e}") return try: promo_add(code, ptype, val, mt) messagebox.showinfo("Sukses", "Promo ditambahkan") w.destroy() self.reload_promo_table() except Exception as e: messagebox.showerror("Error", f"Kode promo sudah ada atau error: {e}") ttk.Button(frm, text="Simpan", command=save).grid(row=4, column=1, pady=12) def open_edit_promo(self): sel = self.promo_tree.selection() if not sel: messagebox.showwarning("Pilih", "Pilih promo untuk diedit") return code = self.promo_tree.item(sel)['values'][0] row = promo_get(code) if not row: messagebox.showerror("Error", "Promo tidak ditemukan") return code, ptype, val, min_total = row w = tk.Toplevel(self.root) w.title("Edit Promo") w.geometry("350x230") w.transient(self.root) w.grab_set() frm = ttk.Frame(w, padding=15) frm.pack(fill="both", expand=True) vars = { 'type': tk.StringVar(value=ptype), 'value': tk.StringVar(value=str(val)), 'min_total': tk.StringVar(value=str(min_total)) } ttk.Label(frm, text=f"Code: {code}", font=("Arial", 10, "bold")).grid(row=0, column=0, columnspan=2, pady=5) ttk.Label(frm, text="Type (percent/fixed):").grid(row=1, column=0, sticky='e', pady=5) ttk.Entry(frm, textvariable=vars['type'], width=20).grid(row=1, column=1) ttk.Label(frm, text="Value:").grid(row=2, column=0, sticky='e', pady=5) ttk.Entry(frm, textvariable=vars['value'], width=20).grid(row=2, column=1) ttk.Label(frm, text="Min Total:").grid(row=3, column=0, sticky='e', pady=5) ttk.Entry(frm, textvariable=vars['min_total'], width=20).grid(row=3, column=1) def save(): try: ptype = vars['type'].get().strip() val = float(vars['value'].get()) mt = float(vars['min_total'].get() or 0) if ptype not in ('percent', 'fixed'): raise ValueError("type harus 'percent' atau 'fixed'") except Exception as e: messagebox.showerror("Error", f"Input salah: {e}") return promo_update(code, ptype, val, mt) messagebox.showinfo("Sukses", "Promo diperbarui") w.destroy() self.reload_promo_table() ttk.Button(frm, text="Update", command=save).grid(row=4, column=1, pady=12) def delete_selected_promo(self): sel = self.promo_tree.selection() if not sel: messagebox.showwarning("Pilih", "Pilih promo") return code = self.promo_tree.item(sel)['values'][0] if messagebox.askyesno("Konfirmasi", f"Hapus promo {code}?"): promo_delete(code) messagebox.showinfo("Dihapus","Promo terhapus") self.reload_promo_table() # ========== TAB: ORDER SYSTEM (PEMBELI) ========== def build_order_tab(self, parent): """Tab pemesanan untuk pembeli""" 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: Daftar Menu left = ttk.Frame(container, relief='solid', borderwidth=1) left.pack(side='left', fill='both', expand=True, padx=(0, 5)) ttk.Label(left, text="🍽️ Menu Tersedia", font=("Arial", 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) # Tombol tambah ke keranjang add_frame = ttk.Frame(left) add_frame.pack(fill='x', padx=10, pady=8) ttk.Label(add_frame, text="Jumlah:", font=("Arial", 10)).pack(side='left', padx=5) self.order_qty_var = tk.StringVar(value="1") ttk.Entry(add_frame, textvariable=self.order_qty_var, width=8).pack(side='left', padx=5) ttk.Button(add_frame, text="➕ Tambah ke Keranjang", command=self.add_to_cart).pack(side='left', padx=10) # RIGHT: Keranjang right = ttk.Frame(container, relief='solid', borderwidth=1) right.pack(side='right', fill='both', padx=(5, 0)) right.config(width=350) ttk.Label(right, text="🛒 Keranjang Belanja", font=("Arial", 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 = 120 if c == "Menu" else 70 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).pack(side='left', padx=3) ttk.Button(cart_btn_frm, text="📝 Update Qty", command=self.update_cart_qty).pack(side='left', padx=3) # Total self.cart_total_var = tk.StringVar(value="Total: Rp 0") ttk.Label(right, textvariable=self.cart_total_var, font=("Arial", 12, "bold")).pack(pady=5) # Form nomor meja meja_frm = ttk.Frame(right) meja_frm.pack(fill='x', padx=10, pady=8) ttk.Label(meja_frm, text="Nomor Meja:", font=("Arial", 10)).pack(side='left', padx=5) self.nomor_meja_var = tk.StringVar() ttk.Entry(meja_frm, textvariable=self.nomor_meja_var, width=10).pack(side='left', padx=5) # Tombol pesan ttk.Button(right, text="📋 PESAN SEKARANG", command=self.submit_order, style="Accent.TButton").pack(pady=10, padx=10, fill='x') self.reload_order_menu() def reload_order_menu(self): """Reload daftar menu untuk order""" for item in self.order_menu_tree.get_children(): self.order_menu_tree.delete(item) search = self.order_search_var.get().strip() if hasattr(self, 'order_search_var') else "" 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): self.order_search_var.set("") self.reload_order_menu() 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_str = item_vals[3].replace("Rp ", "").replace(",", "") harga = float(harga_str) stok = int(item_vals[4]) if qty > stok: messagebox.showerror("Stok Habis", f"Stok hanya tersedia {stok}") return # Cek apakah sudah ada di cart found = False for cart_item in self.cart: if cart_item['menu_id'] == menu_id: cart_item['qty'] += qty cart_item['subtotal'] = cart_item['harga'] * cart_item['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""" 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'] 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 input qty baru new_qty = tk.simpledialog.askinteger("Update Jumlah", f"Jumlah baru untuk {item['nama']}:", initialvalue=item['qty'], minvalue=1) if new_qty: item['qty'] = new_qty item['subtotal'] = item['harga'] * new_qty self.update_cart_display() def submit_order(self): """Submit pesanan""" if not self.cart: 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 try: trans_id = create_transaksi(self.session['id'], nomor_meja, self.cart) # Tambah ke favorites 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 {sum(i['subtotal'] for i in self.cart):,.0f}\n\n" f"Status: Menunggu\n" f"Pesanan Anda akan segera diproses oleh waiter!") # Reset 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: {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="📋 Dashboard Waiter - Kelola Pesanan", font=("Arial", 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 Saya", font=("Arial", 16, "bold")).pack(pady=10) ttk.Label(parent, text="Menu yang paling sering Anda pesan", font=("Arial", 10)).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) favs = get_user_favorites(self.session['id'], limit=10) if not favs: self.fav_tree.insert("", tk.END, values=( "Belum ada menu favorit", "-", "-", "0" )) return for fav in favs: menu = fav['menu'] mid, nama, kategori, harga, stok, foto, tersedia, disc = menu self.fav_tree.insert("", tk.END, values=( nama, kategori, f"Rp {harga:,.0f}", f"{fav['count']}x" ), tags=(str(mid),)) 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 item_vals = self.fav_tree.item(sel)['values'] if item_vals[0] == "Belum ada menu favorit": return # 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}") # ========== UPDATE DASHBOARD FRAME ========== # GANTI fungsi dashboard_frame yang lama dengan ini: def dashboard_frame(self): for w in self.root.winfo_children(): w.destroy() # Header top = ttk.Frame(self.root) top.pack(fill='x') ttk.Label(top, text=f"👤 User: {self.session['username']} | Role: {self.session['role'].upper()}", font=("Arial", 12, "bold")).pack(side='left', padx=15, pady=8) ttk.Button(top, text="🚪 Logout", command=self.logout).pack(side='right', padx=15) # Notebook tabs 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) 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) # Done if __name__ == "__main__": init_db_csv() root = tk.Tk() app = App(root) root.mainloop()