Jevinca Marvella f616da6fa0 Update main.py
2025-12-06 16:18:27 +07:00

1636 lines
59 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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("<Enter>", lambda e: login_btn.config(bg=COLORS['success']))
login_btn.bind("<Leave>", 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("<<TreeviewSelect>>", 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("<<TreeviewSelect>>", 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()