diff --git a/project/__pycache__/admin_menu.cpython-313.pyc b/project/__pycache__/admin_menu.cpython-313.pyc index ebe070f..7f3c35d 100644 Binary files a/project/__pycache__/admin_menu.cpython-313.pyc and b/project/__pycache__/admin_menu.cpython-313.pyc differ diff --git a/project/__pycache__/database.cpython-313.pyc b/project/__pycache__/database.cpython-313.pyc index a9fb776..3d5189d 100644 Binary files a/project/__pycache__/database.cpython-313.pyc and b/project/__pycache__/database.cpython-313.pyc differ diff --git a/project/__pycache__/kasir.cpython-313.pyc b/project/__pycache__/kasir.cpython-313.pyc index 9dddcae..d3ca0df 100644 Binary files a/project/__pycache__/kasir.cpython-313.pyc and b/project/__pycache__/kasir.cpython-313.pyc differ diff --git a/project/__pycache__/login.cpython-313.pyc b/project/__pycache__/login.cpython-313.pyc index 4338316..f1a27ea 100644 Binary files a/project/__pycache__/login.cpython-313.pyc and b/project/__pycache__/login.cpython-313.pyc differ diff --git a/project/__pycache__/pembeli_menu.cpython-313.pyc b/project/__pycache__/pembeli_menu.cpython-313.pyc index f03deae..ee4e315 100644 Binary files a/project/__pycache__/pembeli_menu.cpython-313.pyc and b/project/__pycache__/pembeli_menu.cpython-313.pyc differ diff --git a/project/__pycache__/pemilik.cpython-313.pyc b/project/__pycache__/pemilik.cpython-313.pyc index 36bdb78..8b813bd 100644 Binary files a/project/__pycache__/pemilik.cpython-313.pyc and b/project/__pycache__/pemilik.cpython-313.pyc differ diff --git a/project/__pycache__/waiter_dashboard.cpython-313.pyc b/project/__pycache__/waiter_dashboard.cpython-313.pyc index 259d2c6..6f6991b 100644 Binary files a/project/__pycache__/waiter_dashboard.cpython-313.pyc and b/project/__pycache__/waiter_dashboard.cpython-313.pyc differ diff --git a/project/admin_menu.py b/project/admin_menu.py index af6b83c..44beca4 100644 --- a/project/admin_menu.py +++ b/project/admin_menu.py @@ -1,40 +1,129 @@ import tkinter as tk +from tkinter import messagebox, filedialog from database import connect -from tkinter import messagebox +import os +import shutil -class AdminMenu: - def __init__(self, root): - self.root = root - self.frame = tk.Frame(root) - self.frame.pack() +# GANTI NAMA CLASS JADI AdminPage +class AdminPage(tk.Frame): + def __init__(self, parent, controller): + super().__init__(parent) + self.controller = controller + self.selected_image_path = None + self.data_menu = [] - tk.Label( - self.frame, - text="ADMIN - KELOLA MENU", - font=("Arial", 18, "bold") - ).pack(pady=10) + # --- Header --- + top = tk.Frame(self, bg="#333") + top.pack(fill="x") + tk.Label(top, text="ADMIN DASHBOARD", font=("Arial", 16, "bold"), fg="white", bg="#333").pack(side="left", padx=10, pady=10) + tk.Button(top, text="Logout", bg="#ff6b6b", command=lambda: controller.show_frame("LoginPage")).pack(side="right", padx=10) - self.nama = tk.Entry(self.frame) - self.harga = tk.Entry(self.frame) - self.gambar = tk.Entry(self.frame) + # --- Layout Kiri (Form) & Kanan (Tabel) --- + main_content = tk.Frame(self) + main_content.pack(fill="both", expand=True, padx=10, pady=10) - for label, entry in [ - ("Nama Menu", self.nama), - ("Harga", self.harga), - ("Path Gambar", self.gambar) - ]: - tk.Label(self.frame, text=label).pack() - entry.pack() + # --- KIRI --- + left_frame = tk.Frame(main_content) + left_frame.pack(side="left", fill="y", padx=10) - tk.Button(self.frame, text="Tambah Menu", command=self.tambah).pack(pady=5) + tk.Label(left_frame, text="Nama Menu:").pack(anchor="w") + self.entry_nama = tk.Entry(left_frame, width=30) + self.entry_nama.pack(pady=5) - def tambah(self): + tk.Label(left_frame, text="Harga:").pack(anchor="w") + self.entry_harga = tk.Entry(left_frame, width=30) + self.entry_harga.pack(pady=5) + + tk.Label(left_frame, text="Stok Awal:").pack(anchor="w") + self.entry_stok = tk.Entry(left_frame, width=30) + self.entry_stok.insert(0, "100") # Default stok + self.entry_stok.pack(pady=5) + + tk.Label(left_frame, text="Gambar:").pack(anchor="w") + self.btn_img = tk.Button(left_frame, text="Pilih Gambar", command=self.browse_image) + self.btn_img.pack(pady=5, anchor="w") + self.lbl_img_path = tk.Label(left_frame, text="Belum ada gambar", fg="gray", font=("Arial", 8)) + self.lbl_img_path.pack(anchor="w") + + btn_box = tk.Frame(left_frame, pady=20) + btn_box.pack() + tk.Button(btn_box, text="TAMBAH", bg="#4CAF50", fg="white", command=self.add_menu).grid(row=0, column=0, padx=5) + tk.Button(btn_box, text="HAPUS", bg="#f44336", fg="white", command=self.delete_menu).grid(row=0, column=1, padx=5) + + # --- KANAN --- + right_frame = tk.Frame(main_content) + right_frame.pack(side="right", fill="both", expand=True) + + tk.Label(right_frame, text="Daftar Menu:", font=("Arial", 10, "bold")).pack(anchor="w") + + self.list_menu = tk.Listbox(right_frame) + self.list_menu.pack(fill="both", expand=True) + + scrollbar = tk.Scrollbar(self.list_menu) + scrollbar.pack(side="right", fill="y") + self.list_menu.config(yscrollcommand=scrollbar.set) + scrollbar.config(command=self.list_menu.yview) + + def update_data(self): + self.list_menu.delete(0, tk.END) + self.data_menu = [] + db = connect() cur = db.cursor() - cur.execute( - "INSERT INTO menu(nama,harga,gambar) VALUES (?,?,?)", - (self.nama.get(), self.harga.get(), self.gambar.get()) - ) - db.commit() + cur.execute("SELECT id, nama, harga, stok FROM menu") + rows = cur.fetchall() db.close() - messagebox.showinfo("Sukses", "Menu berhasil ditambahkan") + + for row in rows: + self.data_menu.append(row) + self.list_menu.insert(tk.END, f"{row[1]} - Rp {int(row[2])} (Stok: {row[3]})") + + def browse_image(self): + filename = filedialog.askopenfilename(title="Pilih Gambar", filetypes=[("Images", "*.png;*.jpg;*.jpeg")]) + if filename: + self.selected_image_path = filename + self.lbl_img_path.config(text=os.path.basename(filename), fg="black") + + def add_menu(self): + nama = self.entry_nama.get() + harga = self.entry_harga.get() + stok = self.entry_stok.get() + + if not nama or not harga: + messagebox.showwarning("Warning", "Nama dan Harga wajib diisi!") + return + + final_image_path = "default.png" + if self.selected_image_path: + if not os.path.exists("img"): os.makedirs("img") + destinasi = os.path.join("img", os.path.basename(self.selected_image_path)) + try: + shutil.copy(self.selected_image_path, destinasi) + final_image_path = destinasi + except: + pass + + db = connect() + cur = db.cursor() + try: + cur.execute("INSERT INTO menu (nama, harga, stok, gambar) VALUES (?, ?, ?, ?)", + (nama, harga, stok, final_image_path)) + db.commit() + messagebox.showinfo("Sukses", "Menu berhasil ditambahkan!") + self.update_data() + except Exception as e: + messagebox.showerror("Error", str(e)) + finally: + db.close() + + def delete_menu(self): + idx = self.list_menu.curselection() + if not idx: return + + selected = self.data_menu[idx[0]] + if messagebox.askyesno("Hapus", f"Hapus {selected[1]}?"): + db = connect() + db.cursor().execute("DELETE FROM menu WHERE id=?", (selected[0],)) + db.commit() + db.close() + self.update_data() \ No newline at end of file diff --git a/project/aset/ayam_goreng.jpg b/project/aset/ayam_goreng.jpg new file mode 100644 index 0000000..9aaf03d Binary files /dev/null and b/project/aset/ayam_goreng.jpg differ diff --git a/project/aset/bakso.jpg b/project/aset/bakso.jpg new file mode 100644 index 0000000..bfedea6 Binary files /dev/null and b/project/aset/bakso.jpg differ diff --git a/project/aset/es_teh.jpg b/project/aset/es_teh.jpg new file mode 100644 index 0000000..d77de7a Binary files /dev/null and b/project/aset/es_teh.jpg differ diff --git a/project/aset/jus_jeruk.jpg b/project/aset/jus_jeruk.jpg new file mode 100644 index 0000000..2490317 Binary files /dev/null and b/project/aset/jus_jeruk.jpg differ diff --git a/project/aset/mie_ayam.jpg b/project/aset/mie_ayam.jpg new file mode 100644 index 0000000..04a7cb1 Binary files /dev/null and b/project/aset/mie_ayam.jpg differ diff --git a/project/cafe.db b/project/cafe.db index 924d768..00313c0 100644 Binary files a/project/cafe.db and b/project/cafe.db differ diff --git a/project/database.py b/project/database.py index 37243a2..fb9f26a 100644 --- a/project/database.py +++ b/project/database.py @@ -1,3 +1,4 @@ +# File: database.py import sqlite3 def connect(): @@ -7,7 +8,7 @@ def setup_database(): db = connect() cur = db.cursor() - # Users + # 1. Users cur.execute(""" CREATE TABLE IF NOT EXISTS users( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -17,56 +18,57 @@ def setup_database(): ) """) - # Menu + # 2. Menu cur.execute(""" CREATE TABLE IF NOT EXISTS menu( id INTEGER PRIMARY KEY AUTOINCREMENT, nama TEXT, + kategori TEXT, harga REAL, + stok INTEGER, gambar TEXT ) """) - # Orders + # 3. Transaksi cur.execute(""" - CREATE TABLE IF NOT EXISTS orders( + CREATE TABLE IF NOT EXISTS transaksi( id INTEGER PRIMARY KEY AUTOINCREMENT, - nama TEXT, - total REAL + nama_pelanggan TEXT, + meja_id INTEGER, + tanggal TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + total REAL, + metode_pembayaran TEXT, + status TEXT DEFAULT 'Pending' ) """) - # Pembayaran + # 4. Detail Transaksi cur.execute(""" - CREATE TABLE IF NOT EXISTS pembayaran( + CREATE TABLE IF NOT EXISTS detail_transaksi( id INTEGER PRIMARY KEY AUTOINCREMENT, - order_id INTEGER, - total REAL + transaksi_id INTEGER, + menu_id INTEGER, + jumlah INTEGER, + subtotal REAL ) """) - # User default + # Seeding User (Kalau kosong diisi default) cur.execute("SELECT COUNT(*) FROM users") if cur.fetchone()[0] == 0: users = [ ("admin","admin","admin"), ("kasir","kasir","kasir"), ("pembeli","pembeli","pembeli"), - ("pemilik","pemilik","pemilik") + ("pemilik","pemilik","pemilik"), + ("waiter","waiter","waiter") ] cur.executemany("INSERT INTO users(username,password,role) VALUES (?,?,?)", users) - db.commit() - - # Menu default - cur.execute("SELECT COUNT(*) FROM menu") - if cur.fetchone()[0] == 0: - menu = [ - ("Mie Ayam", 18000, "mie ayam.webp"), - ("Mie Kuah", 10000, "mie kuah.webp"), - ("Es Teh Manis", 5000, "es teh.webp"), - ("Jus Jeruk", 8000, "jus jeruk.webp") - ] - cur.executemany("INSERT INTO menu(nama,harga,gambar) VALUES (?,?,?)", menu) - db.commit() + db.commit() db.close() + print("Database Ready!") + +if __name__ == "__main__": + setup_database() \ No newline at end of file diff --git a/project/isi_menu.py b/project/isi_menu.py new file mode 100644 index 0000000..4285925 --- /dev/null +++ b/project/isi_menu.py @@ -0,0 +1,32 @@ +import sqlite3 + +def isi_data_awal(): + conn = sqlite3.connect("cafe.db") + cur = conn.cursor() + + # Hapus data lama biar gak dobel (opsional, biar bersih) + cur.execute("DELETE FROM menu") + + # Data Menu (Sesuaikan nama file gambar dengan yang ada di folder aset kamu) + menus = [ + # (Nama, Kategori, Harga, Stok, Nama File Gambar) + ("Ayam Goreng", "Makanan", 15000, 20, "ayam_goreng.jpg"), + ("Bakso Urat", "Makanan", 12000, 15, "bakso.jpg"), + ("Mie Ayam", "Makanan", 10000, 25, "mie_ayam.jpg"), + ("Es Teh Manis", "Minuman", 3000, 50, "es_teh.jpg"), + ("Jus Jeruk", "Minuman", 5000, 30, "jus_jeruk.jpg"), + ] + + print("Sedang mengisi data menu...") + + cur.executemany(""" + INSERT INTO menu (nama, kategori, harga, stok, gambar) + VALUES (?, ?, ?, ?, ?) + """, menus) + + conn.commit() + conn.close() + print("✅ Berhasil! Data menu sudah masuk database.") + +if __name__ == "__main__": + isi_data_awal() \ No newline at end of file diff --git a/project/kasir.py b/project/kasir.py index 1955564..44906f5 100644 --- a/project/kasir.py +++ b/project/kasir.py @@ -2,73 +2,60 @@ import tkinter as tk from tkinter import messagebox from database import connect -class KasirPage: +class KasirPage(tk.Frame): def __init__(self, parent, controller): - self.parent = parent + super().__init__(parent) self.controller = controller - self.frame = tk.Frame(parent) - self.frame.pack(fill="both", expand=True) - tk.Label(self.frame, text="KASIR PAGE", font=("Arial", 18, "bold")).pack(pady=10) + # Header + top = tk.Frame(self, bg="#ddd") + top.pack(fill="x") + tk.Button(top, text="Logout", command=lambda: controller.show_frame("LoginPage")).pack(side="right", padx=10, pady=5) + tk.Label(top, text="KASIR - PEMBAYARAN", font=("Arial", 16, "bold"), bg="#ddd").pack(side="left", padx=10) - # Tombol Logout - tk.Button(self.frame, text="Logout", bg="#f9e79f", command=self.logout).pack(pady=5) + # List Order + tk.Label(self, text="Tagihan Belum Lunas (Status: Served):", font=("Arial", 11)).pack(pady=10) + self.order_list = tk.Listbox(self, width=80, height=15, font=("Arial", 10)) + self.order_list.pack(pady=5) - # Listbox untuk menampilkan order - tk.Label(self.frame, text="Daftar Order:", font=("Arial", 12, "bold")).pack(pady=5) - self.listbox = tk.Listbox(self.frame, width=50, height=10) - self.listbox.pack(pady=5) + tk.Button(self, text="Refresh Data", command=self.update_data).pack(pady=5) + tk.Button(self, text="💰 PROSES BAYAR (LUNAS)", bg="#81C784", height=2, command=self.bayar).pack(fill="x", padx=50, pady=20) - # Tombol bayar - tk.Button(self.frame, text="Bayar", bg="#d1e7dd", command=self.bayar).pack(pady=5) - - self.load_orders() # Load order dari database saat awal - - def load_orders(self): - self.listbox.delete(0, tk.END) + def update_data(self): + self.order_list.delete(0, tk.END) + self.data_orders = [] + db = connect() cur = db.cursor() - cur.execute("SELECT id, nama, harga FROM orders") - self.data = cur.fetchall() + # Kasir melihat yang statusnya Served + cur.execute("SELECT id, nama_pelanggan, meja_id, total FROM transaksi WHERE status='Served'") + self.data_orders = cur.fetchall() db.close() - for order in self.data: - self.listbox.insert(tk.END, f"ID {order[0]}: {order[1]} - Rp {order[2]:,}") + for item in self.data_orders: + self.order_list.insert(tk.END, f"ID: {item[0]} | Meja {item[2]} | {item[1]} | Tagihan: Rp {int(item[3])}") def bayar(self): - idx = self.listbox.curselection() + idx = self.order_list.curselection() if not idx: - messagebox.showwarning("Pilih Order", "Pilih order yang ingin dibayar!") + messagebox.showwarning("Pilih", "Pilih tagihan yang mau dibayar!") return + + selected = self.data_orders[idx[0]] + transaksi_id = selected[0] + nama = selected[1] + total = selected[3] - order = self.data[idx[0]] - order_id, nama, total = order + confirm = messagebox.askyesno("Konfirmasi", f"Terima pembayaran Rp {int(total)} dari {nama}?") + if confirm: + db = connect() + cur = db.cursor() + + # Update status jadi 'Paid' dan metode pembayaran (misal Cash default) + cur.execute("UPDATE transaksi SET status='Paid', metode_pembayaran='Cash' WHERE id=?", (transaksi_id,)) + + db.commit() + db.close() - # Simpan pembayaran di database (opsional, bisa buat tabel pembayaran) - db = connect() - cur = db.cursor() - cur.execute("INSERT INTO pembayaran VALUES (NULL, ?, ?)", (order_id, total)) - cur.execute("DELETE FROM orders WHERE id=?", (order_id,)) - db.commit() - db.close() - - # Tampilkan struk - self.tampil_struk(order_id, nama, total) - - # Update listbox - self.load_orders() - - def tampil_struk(self, order_id, nama, total): - win = tk.Toplevel(self.frame) - win.title("Struk Pembayaran") - - tk.Label(win, text="STRUK PEMBAYARAN", font=("Arial",14,"bold")).pack(pady=5) - tk.Label(win, text=f"Order ID : {order_id}").pack() - tk.Label(win, text=f"Nama Menu: {nama}").pack() - tk.Label(win, text=f"Total : Rp {total:,}").pack() - tk.Label(win, text="Terima kasih 🙏").pack(pady=10) - tk.Button(win, text="Tutup", command=win.destroy).pack(pady=5) - - def logout(self): - self.frame.destroy() - self.controller.show_frame("Login") + messagebox.showinfo("Lunas", "Transaksi Selesai & Lunas!") + self.update_data() \ No newline at end of file diff --git a/project/login.py b/project/login.py index 86798c0..937b191 100644 --- a/project/login.py +++ b/project/login.py @@ -1,46 +1,56 @@ import tkinter as tk from tkinter import messagebox from database import connect -from admin_menu import AdminMenu -from pembeli_menu import PembeliMenu -from kasir import KasirPage -from pemilik import PemilikPage -class LoginPage: - def __init__(self, root): - self.root = root - self.frame = tk.Frame(root) - self.frame.pack(expand=True) +class LoginPage(tk.Frame): + def __init__(self, parent, controller): + super().__init__(parent) + self.controller = controller + + # Center Content + center_frame = tk.Frame(self) + center_frame.place(relx=0.5, rely=0.5, anchor="center") - tk.Label(self.frame, text="LOGIN CAFE", font=("Arial",20,"bold")).pack(pady=10) + tk.Label(center_frame, text="LOGIN CAFE", font=("Arial", 20, "bold")).pack(pady=20) - self.user = tk.Entry(self.frame) - self.passw = tk.Entry(self.frame, show="*") - self.user.pack() - self.passw.pack() + tk.Label(center_frame, text="Username").pack(anchor="w") + self.user = tk.Entry(center_frame, width=30) + self.user.pack(pady=5) - tk.Button(self.frame, text="Login", command=self.login).pack(pady=5) + tk.Label(center_frame, text="Password").pack(anchor="w") + self.passw = tk.Entry(center_frame, show="*", width=30) + self.passw.pack(pady=5) + + tk.Button(center_frame, text="Login", bg="#4CAF50", fg="white", width=20, command=self.login).pack(pady=20) def login(self): + username = self.user.get() + password = self.passw.get() + db = connect() cur = db.cursor() - cur.execute("SELECT role FROM users WHERE username=? AND password=?", - (self.user.get(), self.passw.get())) + cur.execute("SELECT role FROM users WHERE username=? AND password=?", (username, password)) res = cur.fetchone() db.close() if not res: - messagebox.showerror("Error","Login gagal") + messagebox.showerror("Error", "Username/Password salah!") return - self.frame.destroy() role = res[0] + + # Reset input + self.user.delete(0, tk.END) + self.passw.delete(0, tk.END) - if role=="admin": - AdminMenu(self.root) - elif role=="pembeli": - PembeliMenu(self.root) - elif role=="kasir": - KasirPage(self.root) - elif role=="pemilik": - PemilikPage(self.root) + # Arahkan sesuai Role (Nama Class harus sama dengan di main.py) + if role == "admin": + self.controller.show_frame("AdminPage") + elif role == "pembeli": + self.controller.show_frame("PembeliMenu") + elif role == "kasir": + self.controller.show_frame("KasirPage") + elif role == "pemilik": + self.controller.show_frame("PemilikPage") + elif role == "waiter": + self.controller.show_frame("WaiterPage") \ No newline at end of file diff --git a/project/main.py b/project/main.py index 3d5bcf4..232b2dd 100644 --- a/project/main.py +++ b/project/main.py @@ -1,60 +1,44 @@ import tkinter as tk -from database import setup_database, connect -from pembeli_menu import PembeliMenu +from database import setup_database + +# Pastikan nama file dan nama class sesuai +from login import LoginPage +from admin_menu import AdminPage # Class diganti jadi AdminPage di file admin_menu +from pembeli_menu import PembeliMenu # Class tetap PembeliMenu from kasir import KasirPage from pemilik import PemilikPage -from tkinter import messagebox +from waiter_dashboard import WaiterPage -# --- Login Screen --- -class LoginScreen: - def __init__(self, root): - self.root = root - self.frame = tk.Frame(root) - self.frame.pack(fill="both", expand=True) - - tk.Label(self.frame,text="LOGIN SISTEM CAFE", font=("Arial",18,"bold")).pack(pady=20) - tk.Label(self.frame,text="Username").pack() - self.username_entry = tk.Entry(self.frame) - self.username_entry.pack() - tk.Label(self.frame,text="Password").pack() - self.password_entry = tk.Entry(self.frame, show="*") - self.password_entry.pack(pady=5) - tk.Button(self.frame,text="Login", bg="#cfe2ff", command=self.login).pack(pady=10) - - def login(self): - username = self.username_entry.get() - password = self.password_entry.get() - - db = connect() - cur = db.cursor() - cur.execute("SELECT role FROM users WHERE username=? AND password=?", (username,password)) - result = cur.fetchone() - db.close() - - if result: - role = result[0] - self.frame.destroy() - if role=="pembeli": - PembeliMenu(self.root) - elif role=="kasir": - KasirPage(self.root) - elif role=="pemilik": - PemilikPage(self.root) - else: - messagebox.showerror("Error","Role tidak dikenali") - else: - messagebox.showerror("Error","Username / Password salah") - -# --- App --- -class App(tk.Tk): +class CafeApp(tk.Tk): def __init__(self): super().__init__() - self.title("Sistem Cafe") - self.geometry("700x600") + self.title("Sistem Manajemen Kafe") + self.geometry("1000x700") -# --- Run Program --- -if __name__=="__main__": - setup_database() - app = App() - LoginScreen(app) - app.mainloop() + setup_database() + + self.container = tk.Frame(self) + self.container.pack(side="top", fill="both", expand=True) + self.container.grid_rowconfigure(0, weight=1) + self.container.grid_columnconfigure(0, weight=1) + + self.frames = {} + + # Loop semua class halaman + for F in (LoginPage, AdminPage, PembeliMenu, KasirPage, PemilikPage, WaiterPage): + page_name = F.__name__ + frame = F(parent=self.container, controller=self) + self.frames[page_name] = frame + frame.grid(row=0, column=0, sticky="nsew") + + self.show_frame("LoginPage") + + def show_frame(self, page_name): + frame = self.frames[page_name] + if hasattr(frame, "update_data"): + frame.update_data() + frame.tkraise() + +if __name__ == "__main__": + app = CafeApp() + app.mainloop() \ No newline at end of file diff --git a/project/pembeli_menu.py b/project/pembeli_menu.py index c57f2bf..767a570 100644 --- a/project/pembeli_menu.py +++ b/project/pembeli_menu.py @@ -1,91 +1,177 @@ import tkinter as tk +from tkinter import messagebox, simpledialog from PIL import Image, ImageTk +import os from database import connect -from tkinter import messagebox -class PembeliMenu: - def __init__(self, parent): - self.parent = parent - self.frame = tk.Frame(parent) - self.frame.pack(fill="both", expand=True) +class PembeliMenu(tk.Frame): + def __init__(self, parent, controller): + super().__init__(parent) + self.controller = controller + self.keranjang = [] # List untuk simpan belanjaan sementara + self.image_refs = [] # Supaya gambar tidak hilang + # --- Layout Utama: Kiri (Menu) & Kanan (Keranjang) --- + + # 1. Frame Kiri (Daftar Menu) + self.left_frame = tk.Frame(self) + self.left_frame.pack(side="left", fill="both", expand=True) - self.cart = [] # Simpan tuple (nama, harga) - self.images = [] # Simpan reference gambar agar tidak dihapus GC + # Header Kiri + header = tk.Frame(self.left_frame, bg="#2c3e50", height=50) + header.pack(fill="x") + tk.Label(header, text="DAFTAR MENU", font=("Arial", 14, "bold"), fg="white", bg="#2c3e50").pack(pady=10) - # --- Judul Halaman --- - tk.Label(self.frame, text="MENU PEMBELI", font=("Arial", 18, "bold")).pack(pady=10) + # Canvas untuk Scroll Menu + self.canvas = tk.Canvas(self.left_frame) + self.scrollbar = tk.Scrollbar(self.left_frame, orient="vertical", command=self.canvas.yview) + self.scrollable_frame = tk.Frame(self.canvas) - # --- Tombol Logout --- - tk.Button(self.frame, text="Logout", bg="#f9e79f", command=self.logout).pack(pady=5) + self.scrollable_frame.bind("", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))) + self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw") + self.canvas.configure(yscrollcommand=self.scrollbar.set) - # --- Frame Menu --- - self.menu_frame = tk.Frame(self.frame) - self.menu_frame.pack(pady=10) + self.canvas.pack(side="left", fill="both", expand=True) + self.scrollbar.pack(side="right", fill="y") - self.load_menu() # Load menu dari database + # 2. Frame Kanan (Keranjang Belanja) + self.right_frame = tk.Frame(self, bg="#ecf0f1", width=300) + self.right_frame.pack(side="right", fill="y") + self.right_frame.pack_propagate(False) # Agar lebar tetap 300px - # --- Daftar Pesanan --- - tk.Label(self.frame, text="Daftar Pesanan:", font=("Arial", 12, "bold")).pack(pady=5) - self.listbox = tk.Listbox(self.frame, width=50, height=6) - self.listbox.pack() + tk.Label(self.right_frame, text="KERANJANG SAYA", font=("Arial", 12, "bold"), bg="#bdc3c7", pady=10).pack(fill="x") - # --- Total --- - self.total_lbl = tk.Label(self.frame, text="Total: Rp 0", font=("Arial", 12, "bold")) - self.total_lbl.pack(pady=5) + # Listbox Keranjang + self.cart_listbox = tk.Listbox(self.right_frame, font=("Arial", 10)) + self.cart_listbox.pack(fill="both", expand=True, padx=10, pady=10) - # --- Tombol Checkout --- - tk.Button(self.frame, text="Checkout", bg="#d1e7dd", command=self.checkout).pack(pady=5) + # Label Total Harga + self.total_label = tk.Label(self.right_frame, text="Total: Rp 0", font=("Arial", 14, "bold"), bg="#ecf0f1", fg="#e74c3c") + self.total_label.pack(pady=10) + + # Tombol Aksi + tk.Button(self.right_frame, text="Hapus Item Terpilih", bg="#e67e22", fg="white", command=self.hapus_item).pack(fill="x", padx=10, pady=5) + tk.Button(self.right_frame, text="CHECKOUT / BAYAR", bg="#27ae60", fg="white", font=("Arial", 10, "bold"), height=2, command=self.checkout).pack(fill="x", padx=10, pady=20) + tk.Button(self.right_frame, text="Kembali / Logout", command=lambda: controller.show_frame("LoginPage")).pack(pady=5) + + def update_data(self): + """Dipanggil saat halaman dibuka""" + self.load_menu() + self.keranjang = [] # Reset keranjang tiap login baru + self.update_keranjang_ui() def load_menu(self): + # Bersihkan area menu + for widget in self.scrollable_frame.winfo_children(): + widget.destroy() + self.image_refs.clear() + db = connect() cur = db.cursor() - cur.execute("SELECT nama, harga, gambar FROM menu") - data = cur.fetchall() + cur.execute("SELECT * FROM menu") + items = cur.fetchall() db.close() - for i, (nama, harga, gambar) in enumerate(data): - f = tk.Frame(self.menu_frame, bd=2, relief="ridge") - f.grid(row=i//3, column=i%3, padx=10, pady=10) + columns = 3 + for index, item in enumerate(items): + self.create_card(item, index, columns) - try: - img = Image.open(gambar).resize((120, 90)) - photo = ImageTk.PhotoImage(img) - self.images.append(photo) - tk.Label(f, image=photo).pack() - except FileNotFoundError: - tk.Label(f, text="No Image").pack() + def create_card(self, item, index, columns): + id_menu, nama, kategori, harga, stok, file_gambar = item + row = index // columns + col = index % columns - tk.Label(f, text=nama).pack() - tk.Label(f, text=f"Rp {harga:,}").pack() - tk.Button(f, text="Pesan", bg="#cfe2ff", - command=lambda n=nama, h=harga: self.add(n, h)).pack(pady=3) + card = tk.Frame(self.scrollable_frame, bd=2, relief="groove", bg="white") + card.grid(row=row, column=col, padx=5, pady=5, sticky="nsew") - def add(self, nama, harga): - self.cart.append((nama, harga)) - self.listbox.insert(tk.END, f"{nama} - Rp {harga:,}") - total = sum(h for _, h in self.cart) - self.total_lbl.config(text=f"Total: Rp {total:,}") + # Load Gambar + path = os.path.join("aset", file_gambar) + try: + img = Image.open(path).resize((120, 80), Image.Resampling.LANCZOS) + photo = ImageTk.PhotoImage(img) + self.image_refs.append(photo) + tk.Label(card, image=photo, bg="white").pack(pady=5) + except: + tk.Label(card, text="[No Image]", bg="#eee", height=4).pack(pady=5) + + tk.Label(card, text=nama, font=("Arial", 10, "bold"), bg="white").pack() + tk.Label(card, text=f"Rp {int(harga):,}", fg="green", bg="white").pack() + + # Tombol Tambah + state = "normal" if stok > 0 else "disabled" + text_btn = "Tambah" if stok > 0 else "Habis" + tk.Button(card, text=text_btn, bg="#3498db", fg="white", state=state, + command=lambda: self.tambah_ke_keranjang(item)).pack(pady=5, padx=5, fill="x") + + def tambah_ke_keranjang(self, item): + # item = (id, nama, kategori, harga, stok, gambar) + self.keranjang.append(item) + self.update_keranjang_ui() + + def update_keranjang_ui(self): + self.cart_listbox.delete(0, tk.END) + total = 0 + for item in self.keranjang: + nama = item[1] + harga = item[3] + self.cart_listbox.insert(tk.END, f"{nama} - Rp {int(harga):,}") + total += harga + + self.total_label.config(text=f"Total: Rp {int(total):,}") + + def hapus_item(self): + selected = self.cart_listbox.curselection() + if not selected: + return + index = selected[0] + del self.keranjang[index] + self.update_keranjang_ui() def checkout(self): - if not self.cart: - messagebox.showwarning("Pesan Kosong", "Belum ada pesanan!") + if not self.keranjang: + messagebox.showwarning("Kosong", "Keranjang masih kosong!") return + nama_pelanggan = simpledialog.askstring("Input", "Masukkan Nama Pelanggan:") + if not nama_pelanggan: return + + total_harga = sum(item[3] for item in self.keranjang) + + # Simpan ke Database db = connect() cur = db.cursor() - for nama, harga in self.cart: - cur.execute("INSERT INTO orders VALUES (NULL, ?, ?)", (nama, harga)) - db.commit() - db.close() - messagebox.showinfo("Sukses", "Pesanan dikirim ke kasir") - self.cart.clear() - self.listbox.delete(0, tk.END) - self.total_lbl.config(text="Total: Rp 0") + try: + # 1. Simpan Transaksi Utama + cur.execute(""" + INSERT INTO transaksi (nama_pelanggan, total, status) + VALUES (?, ?, 'Pending') + """, (nama_pelanggan, total_harga)) + transaksi_id = cur.lastrowid - def logout(self): - self.frame.destroy() - from main import LoginScreen - LoginScreen(self.parent) + # 2. Simpan Detail Transaksi (Looping item di keranjang) + for item in self.keranjang: + menu_id = item[0] + harga = item[3] + # Masukkan ke detail + cur.execute(""" + INSERT INTO detail_transaksi (transaksi_id, menu_id, jumlah, subtotal) + VALUES (?, ?, 1, ?) + """, (transaksi_id, menu_id, harga)) + + # Kurangi Stok Menu + cur.execute("UPDATE menu SET stok = stok - 1 WHERE id = ?", (menu_id,)) + db.commit() + messagebox.showinfo("Berhasil", "Pesanan berhasil dibuat! Silakan bayar di kasir.") + + # Reset + self.keranjang = [] + self.update_keranjang_ui() + self.load_menu() # Reload menu untuk update stok visual + + except Exception as e: + db.rollback() + messagebox.showerror("Error", f"Gagal Checkout: {e}") + finally: + db.close() \ No newline at end of file diff --git a/project/pemilik.py b/project/pemilik.py index 8a62bf5..5942a30 100644 --- a/project/pemilik.py +++ b/project/pemilik.py @@ -1,38 +1,43 @@ import tkinter as tk from database import connect -class PemilikPage: +class PemilikPage(tk.Frame): def __init__(self, parent, controller): - self.parent = parent + super().__init__(parent) self.controller = controller - self.frame = tk.Frame(parent) - self.frame.pack(fill="both", expand=True) + + # --- PERBAIKAN: Baris self.pack() sudah DIHAPUS --- + # Kita tidak boleh packing diri sendiri karena main.py sudah mengatur posisi kita pakai Grid. - tk.Label(self.frame, text="LAPORAN PENJUALAN", font=("Arial", 18, "bold")).pack(pady=10) + # Judul Halaman + tk.Label(self, text="LAPORAN PENJUALAN", font=("Arial", 18, "bold")).pack(pady=20) - # Label untuk total penjualan - self.total_lbl = tk.Label(self.frame, text="Total Penjualan: Rp 0", font=("Arial", 14, "bold")) - self.total_lbl.pack(pady=5) + # Label Total + self.total_lbl = tk.Label(self, text="Total Pendapatan: Rp 0", font=("Arial", 20, "bold"), fg="green") + self.total_lbl.pack(pady=20) + + tk.Label(self, text="(Menghitung semua transaksi berstatus 'Paid')", fg="gray").pack() - # Tombol Refresh - tk.Button(self.frame, text="Refresh", bg="#cfe2ff", command=self.load).pack(pady=5) + # Tombol-tombol + tk.Button(self, text="Refresh Laporan", bg="#cfe2ff", command=self.load).pack(pady=10) + tk.Button(self, text="Logout", bg="#f9e79f", command=lambda: controller.show_frame("LoginPage")).pack(pady=10) - # Tombol Logout - tk.Button(self.frame, text="Logout", bg="#f9e79f", command=self.logout).pack(pady=5) - - # Load data awal + def update_data(self): + """Dipanggil otomatis saat halaman dibuka""" self.load() def load(self): + """Ambil data total penjualan dari database""" db = connect() cur = db.cursor() - # Jumlahkan semua total pembayaran dari tabel pembayaran - cur.execute("SELECT SUM(total) FROM pembayaran") - total = cur.fetchone()[0] or 0 + + # Hitung sum total dari transaksi yang sudah Paid (Lunas) + cur.execute("SELECT SUM(total) FROM transaksi WHERE status='Paid'") + result = cur.fetchone()[0] + + # Kalau belum ada penjualan, set 0 + total = result if result else 0 db.close() - self.total_lbl.config(text=f"Total Penjualan: Rp {total:,}") - - def logout(self): - self.frame.destroy() - self.controller.show_frame("Login") + # Update tampilan + self.total_lbl.config(text=f"Total Pendapatan: Rp {int(total):,}") \ No newline at end of file diff --git a/project/waiter_dashboard.py b/project/waiter_dashboard.py index 4fb7fc0..bec7dd7 100644 --- a/project/waiter_dashboard.py +++ b/project/waiter_dashboard.py @@ -1,10 +1,60 @@ import tkinter as tk +from tkinter import messagebox +from database import connect -class WaiterDashboard(tk.Frame): +class WaiterPage(tk.Frame): def __init__(self, parent, controller): super().__init__(parent) + self.controller = controller + + # Header + top = tk.Frame(self, bg="#FF9800") + top.pack(fill="x") + tk.Label(top, text="WAITER DASHBOARD", font=("Arial", 16, "bold"), bg="#FF9800", fg="white").pack(side="left", padx=10, pady=10) + tk.Button(top, text="Logout", command=lambda: controller.show_frame("LoginPage")).pack(side="right", padx=10) - tk.Label(self, text="WAITER", font=("Arial", 20, "bold")).pack(pady=20) - tk.Label(self, text="(Input pesanan manual)").pack() + # List Pesanan + tk.Label(self, text="Pesanan Status: PENDING (Perlu Diantar)", font=("Arial", 12, "bold")).pack(pady=10) + + self.list_orders = tk.Listbox(self, width=80, height=15, font=("Arial", 10)) + self.list_orders.pack(pady=5) + + btn_frame = tk.Frame(self) + btn_frame.pack(pady=10) + + tk.Button(btn_frame, text="Refresh Data", command=self.update_data).pack(side="left", padx=5) + tk.Button(btn_frame, text="✅ Selesai Diantar (Served)", bg="#81C784", command=self.mark_served).pack(side="left", padx=5) - tk.Button(self, text="Logout", command=lambda: controller.show_frame("Login")).pack(pady=10) + def update_data(self): + self.list_orders.delete(0, tk.END) + self.data_orders = [] + + db = connect() + cur = db.cursor() + # Ambil Transaksi yang statusnya Pending + cur.execute("SELECT id, nama_pelanggan, meja_id, total FROM transaksi WHERE status='Pending'") + self.data_orders = cur.fetchall() + db.close() + + for item in self.data_orders: + # item: (id, nama, meja, total) + self.list_orders.insert(tk.END, f"ID Transaksi: {item[0]} | Meja: {item[2]} | A.N: {item[1]} | Total: Rp {int(item[3])}") + + def mark_served(self): + idx = self.list_orders.curselection() + if not idx: + messagebox.showwarning("Pilih", "Pilih pesanan dulu!") + return + + selected = self.data_orders[idx[0]] + transaksi_id = selected[0] + + db = connect() + cur = db.cursor() + # Update Status Transaksi jadi Served + cur.execute("UPDATE transaksi SET status='Served' WHERE id=?", (transaksi_id,)) + db.commit() + db.close() + + messagebox.showinfo("Sukses", "Pesanan Meja selesai dilayani!") + self.update_data() \ No newline at end of file