Fitur Fav & History

This commit is contained in:
Jevinca Marvella 2025-12-10 11:46:41 +07:00
parent 251a9e37ae
commit 7229cef4ce
4 changed files with 391 additions and 3 deletions

View File

@ -1,2 +1,2 @@
id,transaksi_id,menu_id,qty,harga_satuan,subtotal_item id,transaksi_id,menu_id,qty,harga_satuan,subtotal_item
1,1,1,1,20000.0,20000.0

1 id transaksi_id menu_id qty harga_satuan subtotal_item
2

1
favorite.csv Normal file
View File

@ -0,0 +1 @@
user_id,menu_id,order_count,last_ordered
1 user_id menu_id order_count last_ordered

389
main.py
View File

@ -17,6 +17,7 @@ MENU_CSV = "menu.csv"
PROMO_CSV = "promo.csv" PROMO_CSV = "promo.csv"
TRANSAKSI_CSV = "transaksi.csv" TRANSAKSI_CSV = "transaksi.csv"
DETAIL_TRANSAKSI_CSV = "detail_transaksi.csv" DETAIL_TRANSAKSI_CSV = "detail_transaksi.csv"
FAVORITE_CSV = "favorite.csv"
IMG_PREVIEW_SIZE = (120, 80) IMG_PREVIEW_SIZE = (120, 80)
@ -61,6 +62,7 @@ def init_db_csv():
ensure_file(PROMO_CSV, ["code", "type", "value", "min_total"]) ensure_file(PROMO_CSV, ["code", "type", "value", "min_total"])
ensure_file(TRANSAKSI_CSV, ["id", "user_id", "nomor_meja", "total", "status", "promo_code", "subtotal", "item_discount", "promo_discount", "tanggal"]) ensure_file(TRANSAKSI_CSV, ["id", "user_id", "nomor_meja", "total", "status", "promo_code", "subtotal", "item_discount", "promo_discount", "tanggal"])
ensure_file(DETAIL_TRANSAKSI_CSV, ["id", "transaksi_id", "menu_id", "qty", "harga_satuan", "subtotal_item"]) ensure_file(DETAIL_TRANSAKSI_CSV, ["id", "transaksi_id", "menu_id", "qty", "harga_satuan", "subtotal_item"])
ensure_file(FAVORITE_CSV, ["user_id", "menu_id", "order_count", "last_ordered"])
seed_defaults() seed_defaults()
@ -423,6 +425,9 @@ def transaksi_add(user_id, nomor_meja, cart_items, promo_code=None):
success, msg = menu_decrease_stock(item['menu_id'], qty) success, msg = menu_decrease_stock(item['menu_id'], qty)
if not success: if not success:
return False, f"Gagal mengurangi stok menu ID {item['menu_id']}: {msg}" return False, f"Gagal mengurangi stok menu ID {item['menu_id']}: {msg}"
# Update favorite count
favorite_update(user_id, item['menu_id'])
write_all(DETAIL_TRANSAKSI_CSV, ["id", "transaksi_id", "menu_id", "qty", "harga_satuan", "subtotal_item"], detail_rows) write_all(DETAIL_TRANSAKSI_CSV, ["id", "transaksi_id", "menu_id", "qty", "harga_satuan", "subtotal_item"], detail_rows)
@ -700,6 +705,7 @@ class App:
self.tab_promo = ttk.Frame(main) self.tab_promo = ttk.Frame(main)
self.tab_order = ttk.Frame(main) self.tab_order = ttk.Frame(main)
self.tab_waiter = ttk.Frame(main) self.tab_waiter = ttk.Frame(main)
self.tab_favorite = ttk.Frame(main)
# Tab untuk semua role # Tab untuk semua role
main.add(self.tab_menu_view, text="Menu - View") main.add(self.tab_menu_view, text="Menu - View")
@ -708,6 +714,9 @@ class App:
if self.session['role'] in ['pembeli', 'admin', 'user']: if self.session['role'] in ['pembeli', 'admin', 'user']:
main.add(self.tab_order, text="Order Menu") main.add(self.tab_order, text="Order Menu")
if self.session['role'] in ['pembeli', 'admin', 'user']:
main.add(self.tab_favorite, text="Favorit Saya")
# Tab khusus waiter # Tab khusus waiter
if self.session['role'] in ['waiter', 'admin']: if self.session['role'] in ['waiter', 'admin']:
main.add(self.tab_waiter, text="Waiter - Pesanan") main.add(self.tab_waiter, text="Waiter - Pesanan")
@ -723,6 +732,8 @@ class App:
if self.session['role'] in ['pembeli', 'admin', 'user']: if self.session['role'] in ['pembeli', 'admin', 'user']:
self.build_order_tab(self.tab_order) self.build_order_tab(self.tab_order)
if self.session['role'] in ['pembeli', 'admin', 'user']:
self.build_favorite_tab(self.tab_favorite)
if self.session['role'] in ['waiter', 'admin']: if self.session['role'] in ['waiter', 'admin']:
self.build_waiter_tab(self.tab_waiter) self.build_waiter_tab(self.tab_waiter)
@ -1305,7 +1316,7 @@ class App:
if col >= 2: if col >= 2:
col = 0 col = 0
row += 1 row += 1
def reset_order_search(self): def reset_order_search(self):
self.order_search_var.set("") self.order_search_var.set("")
self.reload_order_menu_cards() self.reload_order_menu_cards()
@ -1350,6 +1361,286 @@ class App:
self.reload_order_menu_cards() self.reload_order_menu_cards()
self.update_cart_display() self.update_cart_display()
return return
# Wilayah dikuasai Favorite
def build_favorite_tab(self, parent):
"""Tab untuk melihat menu favorit dan history pesanan"""
for w in parent.winfo_children():
w.destroy()
# Header
header = ttk.Frame(parent)
header.pack(fill='x', padx=10, pady=8)
ttk.Label(header, text="🌟 Menu Favorit & History", font=("Arial", 14, "bold")).pack(side='left')
ttk.Button(header, text="🔄 Refresh", command=self.reload_favorite_tab).pack(side='right', padx=6)
# Split 2 panel: kiri = favorit, kanan = history
left = ttk.LabelFrame(parent, text="⭐ Menu Favorit Saya (Top 5)", padding=10)
left.pack(side='left', fill='both', expand=True, padx=10, pady=6)
right = ttk.LabelFrame(parent, text="📜 History Pesanan Terakhir", padding=10)
right.pack(side='right', fill='both', expand=True, padx=10, pady=6)
# === PANEL KIRI: Menu Favorit ===
# Treeview favorit
fav_cols = ("Rank", "Menu", "Kategori", "Harga", "Dipesan", "Terakhir")
self.favorite_tree = ttk.Treeview(left, columns=fav_cols, show='headings', height=12)
self.favorite_tree.heading("Rank", text="#")
self.favorite_tree.heading("Menu", text="Menu")
self.favorite_tree.heading("Kategori", text="Kategori")
self.favorite_tree.heading("Harga", text="Harga")
self.favorite_tree.heading("Dipesan", text="Dipesan")
self.favorite_tree.heading("Terakhir", text="Terakhir")
self.favorite_tree.column("Rank", width=40)
self.favorite_tree.column("Menu", width=150)
self.favorite_tree.column("Kategori", width=90)
self.favorite_tree.column("Harga", width=80)
self.favorite_tree.column("Dipesan", width=70)
self.favorite_tree.column("Terakhir", width=140)
self.favorite_tree.pack(fill='both', expand=True, pady=6)
# Tombol quick order
fav_btn_frame = ttk.Frame(left)
fav_btn_frame.pack(pady=6)
ttk.Label(fav_btn_frame, text="Quick Order:").pack(side='left', padx=6)
ttk.Button(fav_btn_frame, text="🛒 Pesan Lagi", command=self.quick_order_favorite).pack(side='left', padx=3)
# === PANEL KANAN: History Transaksi ===
# Treeview history
hist_cols = ("ID", "Tanggal", "Meja", "Total", "Status")
self.history_tree = ttk.Treeview(right, columns=hist_cols, show='headings', height=12)
self.history_tree.heading("ID", text="ID")
self.history_tree.heading("Tanggal", text="Tanggal")
self.history_tree.heading("Meja", text="Meja")
self.history_tree.heading("Total", text="Total")
self.history_tree.heading("Status", text="Status")
self.history_tree.column("ID", width=40)
self.history_tree.column("Tanggal", width=140)
self.history_tree.column("Meja", width=60)
self.history_tree.column("Total", width=100)
self.history_tree.column("Status", width=90)
self.history_tree.pack(fill='both', expand=True, pady=6)
# Bind event untuk lihat detail
self.history_tree.bind("<<TreeviewSelect>>", self.on_history_select)
# Detail history
detail_frame = ttk.Frame(right)
detail_frame.pack(fill='x', pady=6)
self.history_detail_text = tk.Text(detail_frame, height=8, font=("Courier New", 8), wrap='word')
hist_scroll = ttk.Scrollbar(detail_frame, orient='vertical', command=self.history_detail_text.yview)
self.history_detail_text.configure(yscrollcommand=hist_scroll.set)
self.history_detail_text.pack(side='left', fill='both', expand=True)
hist_scroll.pack(side='right', fill='y')
# Tombol history action
hist_btn_frame = ttk.Frame(right)
hist_btn_frame.pack(pady=6)
ttk.Button(hist_btn_frame, text="🔁 Pesan Ulang", command=self.reorder_from_history).pack(side='left', padx=3)
# Load data
self.reload_favorite_tab()
def reload_favorite_tab(self):
"""Load data favorit dan history"""
# Clear trees
for r in self.favorite_tree.get_children():
self.favorite_tree.delete(r)
for r in self.history_tree.get_children():
self.history_tree.delete(r)
# Load favorit
favorites = favorite_list(self.session['id'], limit=5)
rank = 1
for fav in favorites:
menu_id, count, last_ordered = fav
menu_data = menu_get(menu_id)
if not menu_data:
continue
_, nama, kategori, harga, stok, foto, tersedia, item_disc = menu_data
self.favorite_tree.insert("", tk.END, values=(
rank,
nama,
kategori,
f"Rp {harga:,.0f}",
f"{count}x",
last_ordered
))
rank += 1
# Load history transaksi
history = transaksi_list(user_id=self.session['id'])
for h in history:
tid, uid, meja, total, status, promo_code, tanggal = h
self.history_tree.insert("", tk.END, values=(
tid,
tanggal,
meja,
f"Rp {total:,.0f}",
status
))
def on_history_select(self, event):
"""Tampilkan detail history saat dipilih"""
sel = self.history_tree.selection()
if not sel:
return
item = self.history_tree.item(sel)['values']
transaksi_id = item[0]
# Get detail transaksi
transaksi_data = transaksi_get(transaksi_id)
if not transaksi_data:
return
tid, uid, meja, total, status, promo_code, subtotal, item_disc, promo_disc, tanggal = transaksi_data
detail_items = detail_transaksi_list(transaksi_id)
# Format detail
detail_text = f"TRANSAKSI #{tid} - {status.upper()}\n"
detail_text += f"{'='*40}\n"
detail_text += f"Tanggal: {tanggal}\n"
detail_text += f"Meja: {meja}\n\n"
detail_text += f"Item Pesanan:\n"
detail_text += f"{'-'*40}\n"
for detail in detail_items:
did, mid, qty, harga, subtotal_item = detail
menu_data = menu_get(mid)
if menu_data:
_, nama, kategori, _, _, _, _, _ = menu_data
detail_text += f"{nama}\n"
detail_text += f" {qty} x Rp {harga:,.0f} = Rp {subtotal_item:,.0f}\n"
detail_text += f"{'-'*40}\n"
detail_text += f"Subtotal: Rp {subtotal:,.0f}\n"
detail_text += f"Diskon: Rp {item_disc + promo_disc:,.0f}\n"
detail_text += f"TOTAL: Rp {total:,.0f}\n"
self.history_detail_text.delete('1.0', tk.END)
self.history_detail_text.insert('1.0', detail_text)
def quick_order_favorite(self):
"""Pesan ulang dari menu favorit yang dipilih"""
sel = self.favorite_tree.selection()
if not sel:
messagebox.showwarning("Pilih Menu", "Pilih menu favorit terlebih dahulu")
return
item = self.favorite_tree.item(sel)['values']
menu_name = item[1]
# Cari menu_id dari nama
all_menus = menu_list()
menu_id = None
for m in all_menus:
if m[1] == menu_name:
menu_id = m[0]
break
if not menu_id:
messagebox.showerror("Error", "Menu tidak ditemukan")
return
# Tambah ke cart (qty 1)
menu_data = menu_get(menu_id)
if not menu_data:
return
_, nama, kategori, harga, stok, foto, tersedia, item_disc = menu_data
if stok < 1:
messagebox.showwarning("Stok Habis", f"Stok {nama} habis")
return
# Cek apakah sudah ada di cart
found = False
for cart_item in self.cart_items:
if cart_item['menu_id'] == menu_id:
if cart_item['qty'] < stok:
cart_item['qty'] += 1
found = True
else:
messagebox.showwarning("Stok Habis", f"Stok {nama} hanya {stok}")
return
break
if not found:
self.cart_items.append({'menu_id': menu_id, 'qty': 1})
messagebox.showinfo("Ditambahkan", f"{nama} ditambahkan ke keranjang!\n\nSilakan ke tab 'Order Menu' untuk checkout.")
def reorder_from_history(self):
"""Pesan ulang semua item dari history yang dipilih"""
sel = self.history_tree.selection()
if not sel:
messagebox.showwarning("Pilih History", "Pilih history pesanan terlebih dahulu")
return
item = self.history_tree.item(sel)['values']
transaksi_id = item[0]
# Get detail items
detail_items = detail_transaksi_list(transaksi_id)
if not detail_items:
messagebox.showerror("Error", "Tidak ada detail pesanan")
return
# Tambah semua item ke cart
added_count = 0
for detail in detail_items:
did, mid, qty, harga, subtotal_item = detail
# Cek stok
menu_data = menu_get(mid)
if not menu_data:
continue
_, nama, kategori, harga_now, stok, foto, tersedia, item_disc = menu_data
if stok < qty:
messagebox.showwarning("Stok Kurang", f"Stok {nama} hanya {stok}, pesanan asli {qty}")
qty = stok
if qty <= 0:
continue
# Tambah ke cart
found = False
for cart_item in self.cart_items:
if cart_item['menu_id'] == mid:
new_qty = cart_item['qty'] + qty
if new_qty <= stok:
cart_item['qty'] = new_qty
found = True
added_count += 1
break
if not found:
self.cart_items.append({'menu_id': mid, 'qty': qty})
added_count += 1
if added_count > 0:
messagebox.showinfo("Berhasil", f"{added_count} item ditambahkan ke keranjang!\n\nSilakan ke tab 'Order Menu' untuk checkout.")
else:
messagebox.showwarning("Gagal", "Tidak ada item yang bisa ditambahkan (stok habis)")
def update_cart_display(self): def update_cart_display(self):
"""Update tampilan keranjang dan hitung total""" """Update tampilan keranjang dan hitung total"""
@ -1706,6 +1997,102 @@ class App:
else: else:
messagebox.showerror("❌ Gagal", "Gagal mengubah status pesanan") messagebox.showerror("❌ Gagal", "Gagal mengubah status pesanan")
# Wilayah dikuasai Favorite
def favorite_update(user_id, menu_id):
"""Update atau tambah favorite count untuk user tertentu"""
from datetime import datetime
rows = read_all(FAVORITE_CSV)
found = False
for r in rows:
if r.get("user_id") == str(user_id) and r.get("menu_id") == str(menu_id):
# Update count
try:
count = int(r.get("order_count") or 0)
except:
count = 0
r["order_count"] = str(count + 1)
r["last_ordered"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
found = True
break
if not found:
# Tambah baru
rows.append({
"user_id": str(user_id),
"menu_id": str(menu_id),
"order_count": "1",
"last_ordered": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})
write_all(FAVORITE_CSV, ["user_id", "menu_id", "order_count", "last_ordered"], rows)
def favorite_list(user_id, limit=5):
"""Ambil menu favorit user, sorted by order_count descending"""
rows = read_all(FAVORITE_CSV)
out = []
for r in rows:
if r.get("user_id") == str(user_id):
try:
mid = int(r.get("menu_id") or 0)
except:
mid = r.get("menu_id")
try:
count = int(r.get("order_count") or 0)
except:
count = 0
last_ordered = r.get("last_ordered")
out.append((mid, count, last_ordered))
# Sort by count descending
out.sort(key=lambda x: x[1], reverse=True)
# Limit results
if limit:
out = out[:limit]
return out
def favorite_all(limit=10):
"""Ambil menu paling populer dari semua user"""
rows = read_all(FAVORITE_CSV)
menu_counts = {}
for r in rows:
menu_id = r.get("menu_id")
try:
count = int(r.get("order_count") or 0)
except:
count = 0
if menu_id in menu_counts:
menu_counts[menu_id] += count
else:
menu_counts[menu_id] = count
# Convert to list dan sort
out = []
for menu_id, total_count in menu_counts.items():
try:
mid = int(menu_id)
except:
mid = menu_id
out.append((mid, total_count))
out.sort(key=lambda x: x[1], reverse=True)
if limit:
out = out[:limit]
return out

View File

@ -1,2 +1,2 @@
id,user_id,nomor_meja,total,status,promo_code,subtotal,item_discount,promo_discount,tanggal id,user_id,nomor_meja,total,status,promo_code,subtotal,item_discount,promo_discount,tanggal
1,4,5,20000.0,dibayar,,20000.0,0.0,0.0,2025-12-10 11:26:40

1 id user_id nomor_meja total status promo_code subtotal item_discount promo_discount tanggal
2