diff --git a/detail_transaksi.csv b/detail_transaksi.csv index 0ca8be3..da34181 100644 --- a/detail_transaksi.csv +++ b/detail_transaksi.csv @@ -1,2 +1,2 @@ id,transaksi_id,menu_id,qty,harga_satuan,subtotal_item -1,1,1,1,20000.0,20000.0 + diff --git a/favorite.csv b/favorite.csv new file mode 100644 index 0000000..3af07ad --- /dev/null +++ b/favorite.csv @@ -0,0 +1 @@ +user_id,menu_id,order_count,last_ordered \ No newline at end of file diff --git a/main.py b/main.py index 67192c4..83a3350 100644 --- a/main.py +++ b/main.py @@ -17,6 +17,7 @@ MENU_CSV = "menu.csv" PROMO_CSV = "promo.csv" TRANSAKSI_CSV = "transaksi.csv" DETAIL_TRANSAKSI_CSV = "detail_transaksi.csv" +FAVORITE_CSV = "favorite.csv" IMG_PREVIEW_SIZE = (120, 80) @@ -61,6 +62,7 @@ def init_db_csv(): 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(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() @@ -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) if not success: 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) @@ -700,6 +705,7 @@ class App: self.tab_promo = ttk.Frame(main) self.tab_order = ttk.Frame(main) self.tab_waiter = ttk.Frame(main) + self.tab_favorite = ttk.Frame(main) # Tab untuk semua role main.add(self.tab_menu_view, text="Menu - View") @@ -708,6 +714,9 @@ class App: if self.session['role'] in ['pembeli', 'admin', 'user']: 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 if self.session['role'] in ['waiter', 'admin']: main.add(self.tab_waiter, text="Waiter - Pesanan") @@ -723,6 +732,8 @@ class App: if self.session['role'] in ['pembeli', 'admin', 'user']: 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']: self.build_waiter_tab(self.tab_waiter) @@ -1305,7 +1316,7 @@ class App: if col >= 2: col = 0 row += 1 - + def reset_order_search(self): self.order_search_var.set("") self.reload_order_menu_cards() @@ -1350,6 +1361,286 @@ class App: self.reload_order_menu_cards() self.update_cart_display() 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("<>", 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): """Update tampilan keranjang dan hitung total""" @@ -1706,6 +1997,102 @@ class App: else: 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 + diff --git a/transaksi.csv b/transaksi.csv index 2fa0884..3798792 100644 --- a/transaksi.csv +++ b/transaksi.csv @@ -1,2 +1,2 @@ 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 +