Compare commits

...
46 Commits
Author SHA1 Message Date
5803025001 f349274710 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-16 15:31:17 +07:00
ofeleoprecio 4c8221971a fixing things 2025-12-16 15:31:14 +07:00
ofeleoprecio 5bd728b1bd fixing things 2025-12-16 15:24:08 +07:00
ofeleoprecio 9053434a83 add new 2025-12-16 15:14:12 +07:00
5803025001 e8edfe939f Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-16 10:53:17 +07:00
5803025001 17bdf516b3 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-16 10:53:14 +07:00
ofeleoprecio 94e45a7aad Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-16 10:29:03 +07:00
5803025001 54c14826c4 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-16 10:28:56 +07:00
5803025001 52233b09be Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 09:16:19 +07:00
5803025001 3ab63e6327 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 09:16:17 +07:00
5803025001 e8bc7113d1 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 09:04:23 +07:00
5803025001 509715f629 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 09:04:20 +07:00
5803025001 c4b6e1fba9 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 08:55:06 +07:00
5803025001 e5c6da3417 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 08:55:02 +07:00
5803025001 92abbb95fa Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 04:23:49 +07:00
5803025001 6e6218f2fa Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 04:23:45 +07:00
5803025001 3dc145beae Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 03:07:38 +07:00
5803025001 78c730f245 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 03:07:32 +07:00
5803025001 984b1ecba5 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 02:56:50 +07:00
5803025001 eaf12c10a4 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 02:56:48 +07:00
5803025001 29f0700324 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 02:36:24 +07:00
5803025001 2a9b7a4edc Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 02:36:20 +07:00
5803025001 787bf561e4 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 02:28:36 +07:00
5803025001 b1a5224a67 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 02:28:33 +07:00
5803025001 a710f36393 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:38:33 +07:00
5803025001 67c6169a7e Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:38:30 +07:00
5803025001 20386a867a Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:29:49 +07:00
5803025001 f62c4904cd Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:29:46 +07:00
5803025001 6359376da0 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:21:00 +07:00
5803025001 5f55320865 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:20:56 +07:00
5803025001 02aa1da64f Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:16:34 +07:00
5803025001 db80a043d7 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:16:31 +07:00
5803025001 69e133c6e3 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:12:43 +07:00
5803025001 40a0ed8d5b Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:12:40 +07:00
5803025001 deafb9c440 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:10:04 +07:00
5803025001 2482a8d090 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:09:58 +07:00
5803025001 9ae17ff2b7 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:04:22 +07:00
5803025001 10b5cc4a02 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:04:19 +07:00
5803025001 130a6e0a4f Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:03:02 +07:00
5803025001 91cfa7c8c8 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 01:03:00 +07:00
5803025001 85e501b109 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 00:56:30 +07:00
5803025001 a3ae8295b9 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 00:56:26 +07:00
5803025001 785c873eda Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 00:46:49 +07:00
5803025001 eb162b1888 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 00:46:46 +07:00
5803025001 2bc30165e2 Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 00:45:52 +07:00
5803025001 2d2669361d Merge branch 'main' of https://git-eng.ukwms.ac.id/5803025001/Python-Menu 2025-12-15 00:45:45 +07:00
27 changed files with 1167 additions and 492 deletions
+1
View File
@@ -0,0 +1 @@
*.pyc
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+190 -95
View File
@@ -1,129 +1,224 @@
import tkinter as tk import tkinter as tk
from tkinter import messagebox, filedialog from tkinter import messagebox
from database import connect from database import connect
import os
import shutil
# GANTI NAMA CLASS JADI AdminPage class AdminMenu:
class AdminPage(tk.Frame): def __init__(self, root):
def __init__(self, parent, controller): self.root = root
super().__init__(parent) self.frame = tk.Frame(root)
self.controller = controller self.frame.pack(fill="both", expand=True)
self.selected_image_path = None
self.data_menu = []
# --- Header --- # =====================
top = tk.Frame(self, bg="#333") # JUDUL
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.Label(
tk.Button(top, text="Logout", bg="#ff6b6b", command=lambda: controller.show_frame("LoginPage")).pack(side="right", padx=10) self.frame,
text="ADMIN - KELOLA MENU",
font=("Arial", 18, "bold")
).pack(pady=15)
# --- Layout Kiri (Form) & Kanan (Tabel) --- # =====================
main_content = tk.Frame(self) # FORM INPUT
main_content.pack(fill="both", expand=True, padx=10, pady=10) # =====================
form = tk.Frame(self.frame)
form.pack(pady=10)
# --- KIRI --- tk.Label(form, text="Nama Menu", width=15, anchor="w")\
left_frame = tk.Frame(main_content) .grid(row=0, column=0, pady=5)
left_frame.pack(side="left", fill="y", padx=10) self.nama = tk.Entry(form, width=30)
self.nama.grid(row=0, column=1)
tk.Label(left_frame, text="Nama Menu:").pack(anchor="w") tk.Label(form, text="Kategori", width=15, anchor="w")\
self.entry_nama = tk.Entry(left_frame, width=30) .grid(row=1, column=0, pady=5)
self.entry_nama.pack(pady=5) self.kategori = tk.StringVar()
tk.OptionMenu(
form,
self.kategori,
"Makanan",
"Minuman",
"Dessert"
).grid(row=1, column=1, sticky="w")
self.kategori.set("Makanan")
tk.Label(left_frame, text="Harga:").pack(anchor="w") tk.Label(form, text="Harga", width=15, anchor="w")\
self.entry_harga = tk.Entry(left_frame, width=30) .grid(row=2, column=0, pady=5)
self.entry_harga.pack(pady=5) self.harga = tk.Entry(form, width=30)
self.harga.grid(row=2, column=1)
tk.Label(left_frame, text="Stok Awal:").pack(anchor="w") tk.Label(form, text="Path Gambar", width=15, anchor="w")\
self.entry_stok = tk.Entry(left_frame, width=30) .grid(row=3, column=0, pady=5)
self.entry_stok.insert(0, "100") # Default stok self.gambar = tk.Entry(form, width=30)
self.entry_stok.pack(pady=5) self.gambar.grid(row=3, column=1)
tk.Label(left_frame, text="Gambar:").pack(anchor="w") # =====================
self.btn_img = tk.Button(left_frame, text="Pilih Gambar", command=self.browse_image) # BUTTON CRUD
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)) btn_frame = tk.Frame(self.frame)
self.lbl_img_path.pack(anchor="w") btn_frame.pack(pady=10)
btn_box = tk.Frame(left_frame, pady=20) tk.Button(btn_frame, text="Tambah", width=10,
btn_box.pack() bg="#d1e7dd", command=self.tambah).grid(row=0, column=0, padx=5)
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 --- tk.Button(btn_frame, text="Update", width=10,
right_frame = tk.Frame(main_content) bg="#cfe2ff", command=self.update).grid(row=0, column=1, padx=5)
right_frame.pack(side="right", fill="both", expand=True)
tk.Label(right_frame, text="Daftar Menu:", font=("Arial", 10, "bold")).pack(anchor="w") tk.Button(btn_frame, text="Hapus", width=10,
bg="#f8d7da", command=self.hapus).grid(row=0, column=2, padx=5)
self.list_menu = tk.Listbox(right_frame) tk.Button(btn_frame, text="Refresh", width=10,
self.list_menu.pack(fill="both", expand=True) command=self.load_menu).grid(row=0, column=3, padx=5)
scrollbar = tk.Scrollbar(self.list_menu) tk.Button(btn_frame, text="Logout", width=10,
scrollbar.pack(side="right", fill="y") command=self.logout).grid(row=0, column=4, padx=5)
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) # FILTER
self.data_menu = [] # =====================
filter_frame = tk.Frame(self.frame)
filter_frame.pack(pady=5)
tk.Label(filter_frame, text="Filter Kategori:").pack(side=tk.LEFT)
self.filter_kategori = tk.StringVar()
tk.OptionMenu(
filter_frame,
self.filter_kategori,
"Semua",
"Makanan",
"Minuman",
"Dessert",
command=self.load_menu
).pack(side=tk.LEFT)
self.filter_kategori.set("Semua")
# =====================
# LIST MENU
# =====================
self.listbox = tk.Listbox(self.frame, width=80, height=10)
self.listbox.pack(pady=10)
self.listbox.bind("<<ListboxSelect>>", self.pilih_menu)
self.menu_data = []
self.load_menu()
# =====================
# LOAD MENU + FILTER
# =====================
def load_menu(self, *_):
self.listbox.delete(0, tk.END)
db = connect() db = connect()
cur = db.cursor() cur = db.cursor()
cur.execute("SELECT id, nama, harga, stok FROM menu")
rows = cur.fetchall() if self.filter_kategori.get() == "Semua":
cur.execute("SELECT * FROM menu")
else:
cur.execute(
"SELECT * FROM menu WHERE kategori=?",
(self.filter_kategori.get(),)
)
self.menu_data = cur.fetchall()
db.close() db.close()
for row in rows: for m in self.menu_data:
self.data_menu.append(row) self.listbox.insert(
self.list_menu.insert(tk.END, f"{row[1]} - Rp {int(row[2])} (Stok: {row[3]})") tk.END,
f"{m[0]:<3} | {m[1]:<20} | {m[2]:<10} | Rp {m[3]:,.0f}"
)
def browse_image(self): # =====================
filename = filedialog.askopenfilename(title="Pilih Gambar", filetypes=[("Images", "*.png;*.jpg;*.jpeg")]) # PILIH MENU
if filename: # =====================
self.selected_image_path = filename def pilih_menu(self, event):
self.lbl_img_path.config(text=os.path.basename(filename), fg="black") idx = self.listbox.curselection()
if not idx:
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 return
final_image_path = "default.png" menu = self.menu_data[idx[0]]
if self.selected_image_path: self.clear_form()
if not os.path.exists("img"): os.makedirs("img")
destinasi = os.path.join("img", os.path.basename(self.selected_image_path)) self.nama.insert(0, menu[1])
try: self.kategori.set(menu[2])
shutil.copy(self.selected_image_path, destinasi) self.harga.insert(0, menu[3])
final_image_path = destinasi self.gambar.insert(0, menu[4])
except:
pass # =====================
# CRUD
# =====================
def tambah(self):
if not self.nama.get() or not self.harga.get():
messagebox.showwarning("Error", "Data belum lengkap")
return
db = connect() db = connect()
cur = db.cursor() cur = db.cursor()
try: cur.execute(
cur.execute("INSERT INTO menu (nama, harga, stok, gambar) VALUES (?, ?, ?, ?)", "INSERT INTO menu(nama,kategori,harga,gambar) VALUES (?,?,?,?)",
(nama, harga, stok, final_image_path)) (
self.nama.get(),
self.kategori.get(),
float(self.harga.get()),
self.gambar.get()
)
)
db.commit() db.commit()
messagebox.showinfo("Sukses", "Menu berhasil ditambahkan!")
self.update_data()
except Exception as e:
messagebox.showerror("Error", str(e))
finally:
db.close() db.close()
def delete_menu(self): messagebox.showinfo("Sukses", "Menu berhasil ditambahkan")
idx = self.list_menu.curselection() self.load_menu()
if not idx: return self.clear_form()
def update(self):
idx = self.listbox.curselection()
if not idx:
messagebox.showwarning("Pilih", "Pilih menu dulu")
return
menu_id = self.menu_data[idx[0]][0]
selected = self.data_menu[idx[0]]
if messagebox.askyesno("Hapus", f"Hapus {selected[1]}?"):
db = connect() db = connect()
db.cursor().execute("DELETE FROM menu WHERE id=?", (selected[0],)) cur = db.cursor()
cur.execute(
"UPDATE menu SET nama=?, kategori=?, harga=?, gambar=? WHERE id=?",
(
self.nama.get(),
self.kategori.get(),
float(self.harga.get()),
self.gambar.get(),
menu_id
)
)
db.commit() db.commit()
db.close() db.close()
self.update_data()
messagebox.showinfo("Sukses", "Menu berhasil diupdate")
self.load_menu()
def hapus(self):
idx = self.listbox.curselection()
if not idx:
messagebox.showwarning("Pilih", "Pilih menu dulu")
return
menu_id = self.menu_data[idx[0]][0]
if messagebox.askyesno("Konfirmasi", "Yakin hapus menu ini?"):
db = connect()
cur = db.cursor()
cur.execute("DELETE FROM menu WHERE id=?", (menu_id,))
db.commit()
db.close()
messagebox.showinfo("Sukses", "Menu berhasil dihapus")
self.load_menu()
self.clear_form()
def clear_form(self):
self.nama.delete(0, tk.END)
self.harga.delete(0, tk.END)
self.gambar.delete(0, tk.END)
self.kategori.set("Makanan")
def logout(self):
self.frame.destroy()
from main import LoginScreen
LoginScreen(self.root)
Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
View File
Binary file not shown.
+51 -20
View File
@@ -1,4 +1,3 @@
# File: database.py
import sqlite3 import sqlite3
def connect(): def connect():
@@ -8,7 +7,9 @@ def setup_database():
db = connect() db = connect()
cur = db.cursor() cur = db.cursor()
# 1. Users # =====================
# TABEL USERS
# =====================
cur.execute(""" cur.execute("""
CREATE TABLE IF NOT EXISTS users( CREATE TABLE IF NOT EXISTS users(
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -18,57 +19,87 @@ def setup_database():
) )
""") """)
# 2. Menu # =====================
# TABEL MENU (ADA KATEGORI)
# =====================
cur.execute(""" cur.execute("""
CREATE TABLE IF NOT EXISTS menu( CREATE TABLE IF NOT EXISTS menu(
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
nama TEXT, nama TEXT,
kategori TEXT, kategori TEXT,
harga REAL, harga REAL,
stok INTEGER,
gambar TEXT gambar TEXT
) )
""") """)
# 3. Transaksi # Jaga-jaga DB lama (tidak error)
try:
cur.execute("ALTER TABLE menu ADD COLUMN kategori TEXT")
except sqlite3.OperationalError:
pass
# =====================
# TABEL TRANSAKSI
# =====================
cur.execute(""" cur.execute("""
CREATE TABLE IF NOT EXISTS transaksi( CREATE TABLE IF NOT EXISTS transaksi(
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
nama_pelanggan TEXT, tanggal TEXT,
meja_id INTEGER,
tanggal TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
total REAL, total REAL,
metode_pembayaran TEXT, meja_id INTEGER,
status TEXT DEFAULT 'Pending' status TEXT,
metode TEXT
) )
""") """)
# 4. Detail Transaksi # =====================
# DETAIL TRANSAKSI
# =====================
cur.execute(""" cur.execute("""
CREATE TABLE IF NOT EXISTS detail_transaksi( CREATE TABLE IF NOT EXISTS detail_transaksi(
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
transaksi_id INTEGER, transaksi_id INTEGER,
menu_id INTEGER, menu_nama TEXT,
harga REAL,
jumlah INTEGER, jumlah INTEGER,
subtotal REAL subtotal REAL
) )
""") """)
# Seeding User (Kalau kosong diisi default) # =====================
# INSERT USER DEFAULT
# =====================
cur.execute("SELECT COUNT(*) FROM users") cur.execute("SELECT COUNT(*) FROM users")
if cur.fetchone()[0] == 0: if cur.fetchone()[0] == 0:
users = [ users = [
("admin", "admin", "admin"), ("admin", "admin", "admin"),
("kasir", "kasir", "kasir"), ("kasir", "kasir", "kasir"),
("pembeli", "pembeli", "pembeli"), ("pembeli", "pembeli", "pembeli"),
("pemilik","pemilik","pemilik"), ("waiter", "waiter", "waiter"),
("waiter","waiter","waiter") ("pemilik", "pemilik", "pemilik")
] ]
cur.executemany("INSERT INTO users(username,password,role) VALUES (?,?,?)", users) cur.executemany(
"INSERT INTO users(username,password,role) VALUES (?,?,?)",
users
)
# =====================
# INSERT MENU DEFAULT
# =====================
cur.execute("SELECT COUNT(*) FROM menu")
if cur.fetchone()[0] == 0:
menus = [
('Ayam Goreng', 'Makanan', 20000, 'aset/ayam_goreng.jpg'),
('Bakso', 'Makanan', 15000, 'aset/bakso.jpg'),
('Mie Ayam', 'Makanan', 12000, 'aset/mie_ayam.jpg'),
('Es Teh', 'Minuman', 5000, 'aset/es_teh.jpg'),
('Es Teler', 'Dessert', 15000, 'aset/es_teler.jpg'),
('Jus Jeruk', 'Minuman', 8000, 'aset/jus_jeruk.jpg')
]
cur.executemany(
"INSERT INTO menu (nama, kategori, harga, gambar) VALUES (?,?,?,?)",
menus
)
db.commit() db.commit()
db.close() db.close()
print("Database Ready!")
if __name__ == "__main__":
setup_database()
-32
View File
@@ -1,32 +0,0 @@
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()
+322 -34
View File
@@ -1,61 +1,349 @@
import tkinter as tk import tkinter as tk
from tkinter import messagebox from tkinter import messagebox
from database import connect from database import connect
from datetime import datetime
import qrcode
from PIL import ImageTk
class KasirPage(tk.Frame): class KasirPage:
def __init__(self, parent, controller): def __init__(self, parent, controller=None):
super().__init__(parent) self.parent = parent
self.controller = controller self.frame = tk.Frame(parent)
self.frame.pack(fill="both", expand=True)
# Header # =====================
top = tk.Frame(self, bg="#ddd") # JUDUL
top.pack(fill="x") # =====================
tk.Button(top, text="Logout", command=lambda: controller.show_frame("LoginPage")).pack(side="right", padx=10, pady=5) tk.Label(
tk.Label(top, text="KASIR - PEMBAYARAN", font=("Arial", 16, "bold"), bg="#ddd").pack(side="left", padx=10) self.frame,
text="KASIR - PEMBAYARAN",
font=("Arial", 18, "bold")
).pack(pady=10)
# List Order tk.Button(
tk.Label(self, text="Tagihan Belum Lunas (Status: Served):", font=("Arial", 11)).pack(pady=10) self.frame,
self.order_list = tk.Listbox(self, width=80, height=15, font=("Arial", 10)) text="Logout",
self.order_list.pack(pady=5) bg="#f9e79f",
command=self.logout
).pack(pady=5)
tk.Button(self, text="Refresh Data", command=self.update_data).pack(pady=5) tk.Button(
tk.Button(self, text="💰 PROSES BAYAR (LUNAS)", bg="#81C784", height=2, command=self.bayar).pack(fill="x", padx=50, pady=20) self.frame,
text="Refresh",
command=self.load_tagihan
).pack(pady=3)
def update_data(self): # =====================
self.order_list.delete(0, tk.END) # LIST TAGIHAN
self.data_orders = [] # =====================
tk.Label(
self.frame,
text="Daftar Tagihan (Status: Disajikan)",
font=("Arial", 12)
).pack(pady=5)
self.listbox = tk.Listbox(self.frame, width=65, height=10)
self.listbox.pack(pady=5)
# =====================
# METODE PEMBAYARAN
# =====================
metode_frame = tk.Frame(self.frame)
metode_frame.pack(pady=5)
tk.Label(metode_frame, text="Metode Pembayaran:").pack(side=tk.LEFT)
self.metode = tk.StringVar(value="Cash")
tk.OptionMenu(
metode_frame,
self.metode,
"Cash", "QRIS", "E-Wallet"
).pack(side=tk.LEFT, padx=5)
# =====================
# INPUT CASH
# =====================
cash_frame = tk.Frame(self.frame)
cash_frame.pack(pady=5)
tk.Label(cash_frame, text="Uang Diterima (Cash):").pack(side=tk.LEFT)
self.uang_entry = tk.Entry(cash_frame, width=15)
self.uang_entry.pack(side=tk.LEFT, padx=5)
# =====================
# HITUNG KEMBALIAN
# =====================
def hitung_kembalian(event=None):
try:
# 1. Ambil transaksi yang sedang dipilih (Total tagihan)
idx = self.listbox.curselection()
if idx:
teks_item = self.listbox.get(idx).split(" ")
total = int(teks_item[len(teks_item)-1].replace(",",""))
uang_diterima = int(self.uang_entry.get())
# 3. Hitung dan update label
kembalian = uang_diterima - total
self.kembalian_label.config(text=f"Rp {kembalian:,.0f}")
except ValueError:
# Jika input bukan angka atau kosong, atur kembalian jadi 0
self.kembalian_label.config(text="Rp 0")
except IndexError:
# Jika transaksi tidak dipilih
self.kembalian_label.config(text="Rp 0")
self.uang_entry.bind("<KeyRelease>", hitung_kembalian)
# =====================
# UANG KEMBALIAN (BARU)
# =====================
kembalian_frame = tk.Frame(self.frame)
kembalian_frame.pack(pady=5)
tk.Label(kembalian_frame, text="Uang Kembalian:").pack(side=tk.LEFT)
# ⭐️ Label untuk menampilkan kembalian
self.kembalian_label = tk.Label(kembalian_frame, text="Rp 0")
self.kembalian_label.pack(side=tk.LEFT, padx=5)
# =====================
# BUTTON BAYAR
# =====================
tk.Button(
self.frame,
text="Proses Pembayaran",
bg="#d1e7dd",
width=25,
command=self.bayar
).pack(pady=10)
self.transaksi_data = []
self.load_tagihan()
# =====================
# LOAD TAGIHAN
# =====================
def load_tagihan(self):
self.listbox.delete(0, tk.END)
self.transaksi_data = []
db = connect() db = connect()
cur = db.cursor() cur = db.cursor()
# Kasir melihat yang statusnya Served cur.execute("""
cur.execute("SELECT id, nama_pelanggan, meja_id, total FROM transaksi WHERE status='Served'") SELECT id, meja_id, total
self.data_orders = cur.fetchall() FROM transaksi
WHERE status='Disajikan'
""")
rows = cur.fetchall()
db.close() db.close()
for item in self.data_orders: for row in rows:
self.order_list.insert(tk.END, f"ID: {item[0]} | Meja {item[2]} | {item[1]} | Tagihan: Rp {int(item[3])}") t_id, meja, total = row
self.listbox.insert(
tk.END,
f"Meja {meja} | ID {t_id} | Total Rp {total:,.0f}"
)
# 🔽 AMBIL DETAIL MENU
db = connect()
cur = db.cursor()
cur.execute("""
SELECT menu_nama, jumlah
FROM detail_transaksi
WHERE transaksi_id=?
""", (t_id,))
items = cur.fetchall()
db.close()
for nama, qty in items:
self.listbox.insert(
tk.END,
f" • {nama} x{qty}"
)
self.listbox.insert(tk.END, "-" * 50)
self.transaksi_data.append(row)
# =====================
# BAYAR
# =====================
def bayar(self): def bayar(self):
idx = self.order_list.curselection() idx = self.listbox.curselection()
if not idx: if not idx:
messagebox.showwarning("Pilih", "Pilih tagihan yang mau dibayar!") messagebox.showwarning("Pilih", "Pilih tagihan yang akan dibayar!")
return return
selected = self.data_orders[idx[0]] idx = self.listbox.curselection()
transaksi_id = selected[0] if idx:
nama = selected[1] teks_item = self.listbox.get(idx).split(" ")
total = selected[3] total = int(teks_item[len(teks_item)-1].replace(",",""))
meja = int(teks_item[1])
t_id = int(teks_item[4])
metode = self.metode.get()
if metode == "QRIS":
self.tampilkan_qr(t_id, meja, total)
elif metode == "E-Wallet":
self.konfirmasi_ewallet(t_id, meja, total)
else: # CASH
try:
uang = int(self.uang_entry.get())
except:
messagebox.showwarning("Error", "Masukkan uang cash!")
return
if uang < total:
messagebox.showwarning(
"Kurang",
f"Uang kurang!\nTotal: Rp {total:,.0f}"
)
return
kembalian = uang - total
self.selesaikan_pembayaran(
t_id, meja, total, metode,
uang, kembalian
)
# =====================
# QRIS (CLOSE WINDOW = LUNAS)
# =====================
def tampilkan_qr(self, t_id, meja, total):
win = tk.Toplevel(self.frame)
win.title("QRIS Payment")
win.geometry("320x420")
tk.Label(
win,
text="SCAN QR UNTUK BAYAR",
font=("Arial", 12, "bold")
).pack(pady=10)
qr_data = f"""
QRIS-DUMMY
Cafe Python
Transaksi: {t_id}
Meja: {meja}
Total: {total}
"""
qr = qrcode.make(qr_data)
qr_img = ImageTk.PhotoImage(qr)
lbl = tk.Label(win, image=qr_img)
lbl.image = qr_img
lbl.pack(pady=10)
tk.Label(
win,
text=f"Total: Rp {total:,.0f}",
font=("Arial", 11)
).pack(pady=5)
def selesai_qris():
self.selesaikan_pembayaran(t_id, meja, total, "QRIS")
win.destroy()
# ❌ WINDOW DITUTUP = TRANSAKSI BERHASIL
win.protocol("WM_DELETE_WINDOW", selesai_qris)
tk.Button(
win,
text="Konfirmasi Pembayaran",
bg="#d1e7dd",
width=25,
command=selesai_qris
).pack(pady=15)
# =====================
# E-WALLET
# =====================
def konfirmasi_ewallet(self, t_id, meja, total):
confirm = messagebox.askyesno(
"E-Wallet Payment",
f"""
Bayar menggunakan E-Wallet?
Meja : {meja}
Total : Rp {total:,.0f}
Lanjutkan pembayaran?
"""
)
confirm = messagebox.askyesno("Konfirmasi", f"Terima pembayaran Rp {int(total)} dari {nama}?")
if confirm: if confirm:
self.selesaikan_pembayaran(t_id, meja, total, "E-Wallet")
def selesaikan_pembayaran(self, t_id, meja, total, metode, uang=None, kembalian=None):
db = connect() db = connect()
cur = db.cursor() cur = db.cursor()
# Update status jadi 'Paid' dan metode pembayaran (misal Cash default) try:
cur.execute("UPDATE transaksi SET status='Paid', metode_pembayaran='Cash' WHERE id=?", (transaksi_id,)) cur.execute(
"""
UPDATE transaksi
SET status='Lunas',
tanggal = DATETIME('now')
WHERE id=?
""",
(t_id,)
)
db.commit() # ⭐️ PASTIKAN COMMIT DILAKUKAN
tanggal_sekarang = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sql_query = """
INSERT INTO transaksi (tanggal, total, meja_id, status, metode)
VALUES (?, ?, ?, ?, ?)
"""
data_transaksi = (tanggal_sekarang, total, meja, "Lunas", metode)
cur.execute(sql_query, data_transaksi)
db.commit() db.commit()
# Tambahkan print debug untuk konfirmasi
print(f"DEBUG: Transaksi ID {t_id} berhasil diupdate menjadi Lunas.")
except Exception as e:
# Jika ada error database
print(f"ERROR: Gagal update status transaksi: {e}")
messagebox.showerror("DB Error", "Gagal memperbarui status transaksi.")
finally:
db.close() db.close()
messagebox.showinfo("Lunas", "Transaksi Selesai & Lunas!") struk_tambahan = ""
self.update_data() if metode == "Cash" and uang is not None and kembalian is not None:
struk_tambahan = f"""
Uang Diterima: Rp {uang:,.0f}
Kembalian : Rp {kembalian:,.0f}"""
messagebox.showinfo(
"Struk Pembayaran",
f"""
======= STRUK =======
ID Transaksi : {t_id}
Meja : {meja}
Total : Rp {total:,.0f}
Metode : {metode}{struk_tambahan}
Status : Lunas
====================
Terima kasih 🙏
"""
)
self.load_tagihan()
def logout(self):
self.frame.destroy()
from main import LoginScreen
LoginScreen(self.parent)
View File
+23 -33
View File
@@ -1,56 +1,46 @@
import tkinter as tk import tkinter as tk
from tkinter import messagebox from tkinter import messagebox
from database import connect from database import connect
from admin_menu import AdminMenu
from pembeli_menu import PembeliMenu
from kasir import KasirPage
from pemilik import PemilikPage
class LoginPage(tk.Frame): class LoginPage:
def __init__(self, parent, controller): def __init__(self, root):
super().__init__(parent) self.root = root
self.controller = controller self.frame = tk.Frame(root)
self.frame.pack(expand=True)
# Center Content tk.Label(self.frame, text="LOGIN CAFE", font=("Arial",20,"bold")).pack(pady=10)
center_frame = tk.Frame(self)
center_frame.place(relx=0.5, rely=0.5, anchor="center")
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") tk.Button(self.frame, text="Login", command=self.login).pack(pady=5)
self.user = tk.Entry(center_frame, width=30)
self.user.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): def login(self):
username = self.user.get()
password = self.passw.get()
db = connect() db = connect()
cur = db.cursor() cur = db.cursor()
cur.execute("SELECT role FROM users WHERE username=? AND password=?", (username, password)) cur.execute("SELECT role FROM users WHERE username=? AND password=?",
(self.user.get(), self.passw.get()))
res = cur.fetchone() res = cur.fetchone()
db.close() db.close()
if not res: if not res:
messagebox.showerror("Error", "Username/Password salah!") messagebox.showerror("Error","Login gagal")
return return
self.frame.destroy()
role = res[0] role = res[0]
# Reset input
self.user.delete(0, tk.END)
self.passw.delete(0, tk.END)
# Arahkan sesuai Role (Nama Class harus sama dengan di main.py)
if role=="admin": if role=="admin":
self.controller.show_frame("AdminPage") AdminMenu(self.root)
elif role=="pembeli": elif role=="pembeli":
self.controller.show_frame("PembeliMenu") PembeliMenu(self.root)
elif role=="kasir": elif role=="kasir":
self.controller.show_frame("KasirPage") KasirPage(self.root)
elif role=="pemilik": elif role=="pemilik":
self.controller.show_frame("PemilikPage") PemilikPage(self.root)
elif role == "waiter":
self.controller.show_frame("WaiterPage")
+78 -35
View File
@@ -1,44 +1,87 @@
import tkinter as tk import tkinter as tk
from database import setup_database from database import setup_database, connect
from pembeli_menu import PembeliMenu
# 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 kasir import KasirPage
from pemilik import PemilikPage from pemilik import PemilikPage
from waiter_dashboard import WaiterPage from admin_menu import AdminMenu
from waiter_dashboard import WaiterDashboard
from tkinter import messagebox
class CafeApp(tk.Tk): 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="SISTEM CAFE", font=("Arial", 24, "bold")).pack(pady=30)
# --- Bagian Login Staff ---
frame_login = tk.LabelFrame(self.frame, text="Login Staff (Admin/Kasir/Waiter)", padx=20, pady=20)
frame_login.pack(pady=10)
tk.Label(frame_login, text="Username:").pack(anchor="w")
self.username_entry = tk.Entry(frame_login)
self.username_entry.pack(fill="x", pady=5)
tk.Label(frame_login, text="Password:").pack(anchor="w")
self.password_entry = tk.Entry(frame_login, show="*")
self.password_entry.pack(fill="x", pady=5)
tk.Button(frame_login, text="Login Staff", bg="#cfe2ff", command=self.login_staff).pack(pady=10, fill="x")
# --- Bagian Pelanggan (Tanpa Password) ---
tk.Label(self.frame, text="--- ATAU ---", fg="grey").pack(pady=5)
tk.Button(self.frame,
text="🍽️ MASUK SEBAGAI PELANGGAN",
bg="#d1e7dd", fg="#0f5132", font=("Arial", 12, "bold"),
height=2,
command=self.masuk_pelanggan).pack(pady=10, ipadx=20)
def login_staff(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 == "kasir":
KasirPage(self.root, self.root)
elif role == "pemilik":
PemilikPage(self.root, self.root)
elif role == "admin":
AdminMenu(self.root)
elif role == "waiter":
WaiterDashboard(self.root)
elif role == "pembeli":
# Jika ada user iseng login pakai akun pembeli di database
PembeliMenu(self.root)
else:
messagebox.showerror("Error", "Role tidak dikenali")
else:
messagebox.showerror("Error", "Username / Password salah")
def masuk_pelanggan(self):
# Langsung buka menu tanpa cek password
self.frame.destroy()
PembeliMenu(self.root)
# --- App Class ---
class App(tk.Tk):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.title("Sistem Manajemen Kafe") self.title("Sistem Cafe Python")
self.geometry("1000x700") self.geometry("800x600")
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__": if __name__ == "__main__":
app = CafeApp() setup_database()
app = App()
LoginScreen(app)
app.mainloop() app.mainloop()
+204 -142
View File
@@ -1,177 +1,239 @@
import tkinter as tk import tkinter as tk
from tkinter import messagebox, simpledialog from tkinter import messagebox, ttk
from PIL import Image, ImageTk from PIL import Image, ImageTk
import sqlite3
import datetime
import os import os
from database import connect
class PembeliMenu(tk.Frame): BASE_DIR = os.path.dirname(os.path.abspath(__file__))
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) --- # ================= DATABASE CONNECT =================
def connect():
return sqlite3.connect("cafe.db")
# 1. Frame Kiri (Daftar Menu) # ================= PEMBELI MENU =================
self.left_frame = tk.Frame(self) class PembeliMenu:
self.left_frame.pack(side="left", fill="both", expand=True) def __init__(self, parent):
self.parent = parent
self.frame = tk.Frame(parent)
self.frame.pack(fill="both", expand=True)
# Header Kiri self.cart = []
header = tk.Frame(self.left_frame, bg="#2c3e50", height=50) self.images = []
header.pack(fill="x")
tk.Label(header, text="DAFTAR MENU", font=("Arial", 14, "bold"), fg="white", bg="#2c3e50").pack(pady=10)
# Canvas untuk Scroll Menu # ================= HEADER =================
self.canvas = tk.Canvas(self.left_frame) header = tk.Frame(self.frame)
self.scrollbar = tk.Scrollbar(self.left_frame, orient="vertical", command=self.canvas.yview) header.pack(fill="x", padx=20, pady=10)
self.scrollable_frame = tk.Frame(self.canvas)
self.scrollable_frame.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))) tk.Label(
self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw") header,
self.canvas.configure(yscrollcommand=self.scrollbar.set) text="SELAMAT DATANG",
font=("Arial", 18, "bold")
).pack(side="left")
self.canvas.pack(side="left", fill="both", expand=True) tk.Button(
self.scrollbar.pack(side="right", fill="y") header,
text="⬅ Kembali",
bg="#f8d7da",
font=("Arial", 10, "bold"),
command=self.logout
).pack(side="right")
# 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
tk.Label(self.right_frame, text="KERANJANG SAYA", font=("Arial", 12, "bold"), bg="#bdc3c7", pady=10).pack(fill="x") # ================= INPUT DATA =================
info_frame = tk.Frame(self.frame, bd=2, relief="groove", padx=10, pady=10)
info_frame.pack(pady=5)
# Listbox Keranjang tk.Label(info_frame, text="Nama Anda:").grid(row=0, column=0, sticky="e")
self.cart_listbox = tk.Listbox(self.right_frame, font=("Arial", 10)) self.entry_nama = tk.Entry(info_frame)
self.cart_listbox.pack(fill="both", expand=True, padx=10, pady=10) self.entry_nama.grid(row=0, column=1, padx=5)
# Label Total Harga tk.Label(info_frame, text="Pilih Meja:").grid(row=0, column=2, sticky="e")
self.total_label = tk.Label(self.right_frame, text="Total: Rp 0", font=("Arial", 14, "bold"), bg="#ecf0f1", fg="#e74c3c") self.meja_var = tk.StringVar()
self.total_label.pack(pady=10) self.combo_meja = ttk.Combobox(
info_frame,
textvariable=self.meja_var,
state="readonly",
width=5
)
self.combo_meja.grid(row=0, column=3, padx=5)
# Tombol Aksi tk.Button(info_frame, text="🔄", command=self.refresh_available_tables).grid(row=0, column=4, padx=2)
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): self.refresh_available_tables()
"""Dipanggil saat halaman dibuka"""
# ================= CONTENT 2 KOLOM =================
content = tk.Frame(self.frame)
content.pack(fill="both", expand=True, padx=20, pady=10)
content.grid_columnconfigure(0, weight=3)
content.grid_columnconfigure(1, weight=1)
# ================= MENU (KIRI) =================
self.menu_frame = tk.Frame(content)
self.menu_frame.grid(row=0, column=0, sticky="nw")
self.load_menu() self.load_menu()
self.keranjang = [] # Reset keranjang tiap login baru
self.update_keranjang_ui()
def load_menu(self): # ================= KERANJANG (KANAN) =================
# Bersihkan area menu cart_frame = tk.LabelFrame(
for widget in self.scrollable_frame.winfo_children(): content,
widget.destroy() text="Keranjang Pesanan",
self.image_refs.clear() font=("Arial", 11, "bold"),
padx=10,
pady=10
)
cart_frame.grid(row=0, column=1, sticky="ne")
self.listbox = tk.Listbox(cart_frame, width=35, height=10, font=("Courier", 10))
self.listbox.pack(pady=5)
tk.Button(cart_frame, text="➖ Kurangi / Hapus Item", fg="red",
command=self.kurangi_item_cart).pack(pady=5)
self.total_lbl = tk.Label(cart_frame, text="Total: Rp 0",
font=("Arial", 12, "bold"))
self.total_lbl.pack(pady=5)
tk.Button(cart_frame, text="✅ KIRIM PESANAN", bg="#d1e7dd",
font=("Arial", 11, "bold"), command=self.checkout).pack(pady=10)
# ================= MEJA =================
def refresh_available_tables(self):
total_meja = [str(i) for i in range(1, 11)]
db = connect() db = connect()
cur = db.cursor() cur = db.cursor()
cur.execute("SELECT * FROM menu") cur.execute("SELECT DISTINCT meja_id FROM transaksi WHERE status != 'Selesai'")
items = cur.fetchall() terisi = [str(row[0]) for row in cur.fetchall()]
db.close() db.close()
columns = 3 tersedia = [m for m in total_meja if m not in terisi]
for index, item in enumerate(items): self.combo_meja['values'] = tersedia if tersedia else ["Penuh"]
self.create_card(item, index, columns) self.combo_meja.set(tersedia[0] if tersedia else "Penuh")
def create_card(self, item, index, columns): # ================= MENU =================
id_menu, nama, kategori, harga, stok, file_gambar = item def load_menu(self):
row = index // columns
col = index % columns
card = tk.Frame(self.scrollable_frame, bd=2, relief="groove", bg="white")
card.grid(row=row, column=col, padx=5, pady=5, sticky="nsew")
# 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.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() db = connect()
cur = db.cursor() cur = db.cursor()
cur.execute("SELECT nama, harga, gambar FROM menu")
data = cur.fetchall()
db.close()
columns = 4
for i, (nama, harga, gambar) in enumerate(data):
row, col = divmod(i, columns)
card = tk.Frame(self.menu_frame, width=180, height=220, bd=2, relief="ridge")
card.grid(row=row, column=col, padx=10, pady=10)
card.grid_propagate(False)
try: try:
# 1. Simpan Transaksi Utama if not gambar:
cur.execute(""" raise ValueError("gambar kosong")
INSERT INTO transaksi (nama_pelanggan, total, status)
VALUES (?, ?, 'Pending')
""", (nama_pelanggan, total_harga))
transaksi_id = cur.lastrowid
# 2. Simpan Detail Transaksi (Looping item di keranjang) # gambar dari DB sudah mengandung 'aset/...'
for item in self.keranjang: img_path = os.path.join(BASE_DIR, gambar)
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 if not os.path.exists(img_path):
cur.execute("UPDATE menu SET stok = stok - 1 WHERE id = ?", (menu_id,)) raise FileNotFoundError(img_path)
db.commit() img = Image.open(img_path)
messagebox.showinfo("Berhasil", "Pesanan berhasil dibuat! Silakan bayar di kasir.") img = img.resize((140, 90), Image.Resampling.LANCZOS)
photo = ImageTk.PhotoImage(img)
# Reset self.images.append(photo)
self.keranjang = [] tk.Label(card, image=photo).pack(pady=5)
self.update_keranjang_ui()
self.load_menu() # Reload menu untuk update stok visual
except Exception as e: except Exception as e:
db.rollback() print("IMG ERROR:", e)
messagebox.showerror("Error", f"Gagal Checkout: {e}") tk.Label(card, text="No Image", height=6).pack()
finally:
tk.Label(card, text=nama, font=("Arial", 10, "bold"), wraplength=160).pack()
tk.Label(card, text=f"Rp {harga:,.0f}").pack(pady=2)
tk.Button(card, text="Tambah", bg="#cfe2ff",
command=lambda n=nama, h=harga: self.add_to_cart(n, h)).pack(pady=5)
# ================= CART =================
def add_to_cart(self, nama, harga):
for item in self.cart:
if item['nama'] == nama:
item['qty'] += 1
item['subtotal'] = item['qty'] * harga
self.refresh_cart()
return
self.cart.append({'nama': nama, 'harga_satuan': harga, 'qty': 1, 'subtotal': harga})
self.refresh_cart()
def kurangi_item_cart(self):
if not self.listbox.curselection(): return
idx = self.listbox.curselection()[0]
item = self.cart[idx]
item['qty'] -= 1
if item['qty'] <= 0:
self.cart.pop(idx)
else:
item['subtotal'] = item['qty'] * item['harga_satuan']
self.refresh_cart()
def refresh_cart(self):
self.listbox.delete(0, tk.END)
total = 0
for item in self.cart:
self.listbox.insert(tk.END, f"{item['nama']} (x{item['qty']}) - Rp {item['subtotal']:,.0f}")
total += item['subtotal']
self.total_lbl.config(text=f"Total: Rp {total:,.0f}")
def checkout(self):
if not self.entry_nama.get() or not self.cart:
messagebox.showwarning("Error", "Data belum lengkap")
return
total = sum(item['subtotal'] for item in self.cart)
tanggal = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
db = connect()
cur = db.cursor()
# 1️⃣ Simpan transaksi
cur.execute("""
INSERT INTO transaksi (tanggal, total, meja_id, status)
VALUES (?, ?, ?, ?)
""", (tanggal, total, self.combo_meja.get(), "Pending"))
transaksi_id = cur.lastrowid # 🔥 AMBIL ID TRANSAKSI
# 2️⃣ Simpan detail menu
for item in self.cart:
cur.execute("""
INSERT INTO detail_transaksi
(transaksi_id, menu_nama, harga, jumlah, subtotal)
VALUES (?, ?, ?, ?, ?)
""", (
transaksi_id,
item['nama'],
item['harga_satuan'],
item['qty'],
item['subtotal']
))
db.commit()
db.close() db.close()
messagebox.showinfo("Berhasil", "Pesanan berhasil dikirim")
self.cart.clear()
self.refresh_cart()
def logout(self):
self.frame.destroy()
from main import LoginScreen
LoginScreen(self.parent)
# ================= RUN =================
if __name__ == '__main__':
root = tk.Tk()
root.title("Sistem Cafe Python")
root.geometry("1200x700")
PembeliMenu(root)
root.mainloop()
+163 -24
View File
@@ -1,43 +1,182 @@
import tkinter as tk import tkinter as tk
from tkinter import messagebox
from database import connect from database import connect
import matplotlib.pyplot as plt
class PemilikPage(tk.Frame):
def __init__(self, parent, controller): class PemilikPage:
super().__init__(parent) def __init__(self, parent, controller=None):
self.parent = parent
self.controller = controller self.controller = controller
# --- PERBAIKAN: Baris self.pack() sudah DIHAPUS --- self.frame = tk.Frame(parent)
# Kita tidak boleh packing diri sendiri karena main.py sudah mengatur posisi kita pakai Grid. self.frame.pack(fill="both", expand=True)
# Judul Halaman # =====================
tk.Label(self, text="LAPORAN PENJUALAN", font=("Arial", 18, "bold")).pack(pady=20) # JUDUL
# =====================
tk.Label(
self.frame,
text="LAPORAN PENJUALAN",
font=("Arial", 18, "bold")
).pack(pady=10)
# Label Total # =====================
self.total_lbl = tk.Label(self, text="Total Pendapatan: Rp 0", font=("Arial", 20, "bold"), fg="green") # FILTER PERIODE
self.total_lbl.pack(pady=20) # =====================
filter_frame = tk.Frame(self.frame)
filter_frame.pack(pady=5)
tk.Label(self, text="(Menghitung semua transaksi berstatus 'Paid')", fg="gray").pack() tk.Label(filter_frame, text="Periode:").pack(side=tk.LEFT)
# Tombol-tombol self.filter_var = tk.StringVar(value="Harian")
tk.Button(self, text="Refresh Laporan", bg="#cfe2ff", command=self.load).pack(pady=10) tk.OptionMenu(
tk.Button(self, text="Logout", bg="#f9e79f", command=lambda: controller.show_frame("LoginPage")).pack(pady=10) filter_frame,
self.filter_var,
"Harian", "Mingguan", "Bulanan"
).pack(side=tk.LEFT, padx=5)
tk.Button(
filter_frame,
text="Refresh",
bg="#cfe2ff",
command=self.load
).pack(side=tk.LEFT, padx=10)
# =====================
# TOTAL
# =====================
self.total_lbl = tk.Label(
self.frame,
text="Total (Harian): Rp 0",
font=("Arial", 14, "bold")
)
self.total_lbl.pack(pady=10)
# =====================
# GRAFIK
# =====================
tk.Button(
self.frame,
text="Tampilkan Grafik",
bg="#d1e7dd",
width=20,
command=self.show_graph
).pack(pady=5)
# =====================
# LOGOUT
# =====================
tk.Button(
self.frame,
text="Logout",
bg="#f9e79f",
command=self.logout
).pack(pady=5)
def update_data(self):
"""Dipanggil otomatis saat halaman dibuka"""
self.load() self.load()
# =====================
# LOAD TOTAL (SUM ASLI)
# =====================
def load(self): def load(self):
"""Ambil data total penjualan dari database"""
db = connect() db = connect()
cur = db.cursor() cur = db.cursor()
periode = self.filter_var.get()
# Hitung sum total dari transaksi yang sudah Paid (Lunas) if periode == "Harian":
cur.execute("SELECT SUM(total) FROM transaksi WHERE status='Paid'") cur.execute("""
result = cur.fetchone()[0] SELECT COALESCE(SUM(total), 0)
FROM transaksi
WHERE status='Lunas'
AND datetime(tanggal) >= datetime('now','-1 day')
""")
# Kalau belum ada penjualan, set 0 elif periode == "Mingguan":
total = result if result else 0 cur.execute("""
SELECT COALESCE(SUM(total), 0)
FROM transaksi
WHERE status='Lunas'
AND datetime(tanggal) >= datetime('now','-7 day')
""")
else: # Bulanan
cur.execute("""
SELECT COALESCE(SUM(total), 0)
FROM transaksi
WHERE status='Lunas'
AND strftime('%Y-%m', tanggal) = strftime('%Y-%m','now')
""")
total = cur.fetchone()[0]
db.close() db.close()
# Update tampilan self.total_lbl.config(
self.total_lbl.config(text=f"Total Pendapatan: Rp {int(total):,}") text=f"Total ({periode}): Rp {total:,.0f}"
)
# =====================
# GRAFIK (SUM SESUAI TOTAL)
# =====================
def show_graph(self):
plt.close('all')
db = connect()
cur = db.cursor()
periode = self.filter_var.get()
if periode == "Harian":
cur.execute("""
SELECT strftime('%H:%M', tanggal), SUM(total)
FROM transaksi
WHERE status='Lunas'
AND datetime(tanggal) >= datetime('now','-1 day')
GROUP BY strftime('%H', tanggal)
ORDER BY tanggal
""")
elif periode == "Mingguan":
cur.execute("""
SELECT date(tanggal), SUM(total)
FROM transaksi
WHERE status='Lunas'
AND datetime(tanggal) >= datetime('now','-7 day')
GROUP BY date(tanggal)
ORDER BY date(tanggal)
""")
else:
cur.execute("""
SELECT strftime('%Y-%m', tanggal), SUM(total)
FROM transaksi
WHERE status='Lunas'
GROUP BY strftime('%Y-%m', tanggal)
ORDER BY strftime('%Y-%m', tanggal)
""")
data = cur.fetchall()
db.close()
if not data:
messagebox.showinfo("Info", "Belum ada data penjualan")
return
labels = [d[0] for d in data]
totals = [d[1] for d in data]
plt.figure()
plt.plot(labels, totals, marker='o')
plt.title(f"Grafik Penjualan ({periode})")
plt.xlabel("Periode")
plt.ylabel("Total Penjualan")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
# =====================
# LOGOUT
# =====================
def logout(self):
self.frame.destroy()
from main import LoginScreen
LoginScreen(self.parent)
-10
View File
@@ -1,10 +0,0 @@
project/
│ main.py
│ database.py
│ pembeli_menu.py
│ kasir_page.py
│ pemilik_page.py
│ mie ayam.webp
│ mie kuah.webp
│ es teh.webp
│ jus jeruk.webp
+104 -36
View File
@@ -2,59 +2,127 @@ import tkinter as tk
from tkinter import messagebox from tkinter import messagebox
from database import connect from database import connect
class WaiterPage(tk.Frame): class WaiterDashboard:
def __init__(self, parent, controller): def __init__(self, parent):
super().__init__(parent) self.parent = parent
self.controller = controller self.frame = tk.Frame(parent)
self.frame.pack(fill="both", expand=True, padx=20, pady=10)
# Header # =====================
top = tk.Frame(self, bg="#FF9800") # HEADER
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.Label(
tk.Button(top, text="Logout", command=lambda: controller.show_frame("LoginPage")).pack(side="right", padx=10) self.frame,
text="DASHBOARD WAITER (DAPUR)",
font=("Arial", 18, "bold")
).pack(pady=10)
# List Pesanan top_btn = tk.Frame(self.frame)
tk.Label(self, text="Pesanan Status: PENDING (Perlu Diantar)", font=("Arial", 12, "bold")).pack(pady=10) top_btn.pack(pady=5)
self.list_orders = tk.Listbox(self, width=80, height=15, font=("Arial", 10)) tk.Button(top_btn, text="Refresh Data", bg="#cfe2ff",
self.list_orders.pack(pady=5) width=15, command=self.load_orders).grid(row=0, column=0, padx=5)
btn_frame = tk.Frame(self) tk.Button(top_btn, text="Logout", bg="#f8d7da",
btn_frame.pack(pady=10) width=10, command=self.logout).grid(row=0, column=1, padx=5)
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) # LIST PESANAN + SCROLL
# =====================
list_frame = tk.Frame(self.frame)
list_frame.pack(pady=10)
def update_data(self): scrollbar = tk.Scrollbar(list_frame)
self.list_orders.delete(0, tk.END) scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.data_orders = []
self.listbox = tk.Listbox(
list_frame,
width=90,
height=15,
yscrollcommand=scrollbar.set
)
self.listbox.pack(side=tk.LEFT)
scrollbar.config(command=self.listbox.yview)
# =====================
# BUTTON VALIDASI
# =====================
tk.Button(
self.frame,
text="✔ Pesanan Selesai Disajikan",
bg="#d1e7dd",
font=("Arial", 11, "bold"),
width=30,
command=self.validasi_layanan
).pack(pady=10)
self.order_ids = []
self.load_orders()
# =====================
# LOAD DATA
# =====================
def load_orders(self):
self.listbox.delete(0, tk.END)
self.order_ids = []
db = connect() db = connect()
cur = db.cursor() cur = db.cursor()
# Ambil Transaksi yang statusnya Pending
cur.execute("SELECT id, nama_pelanggan, meja_id, total FROM transaksi WHERE status='Pending'") cur.execute(
self.data_orders = cur.fetchall() "SELECT id, meja_id, total FROM transaksi WHERE status='Pending'"
)
rows = cur.fetchall()
for t_id, meja, total in rows:
cur.execute(
"SELECT menu_nama, jumlah FROM detail_transaksi WHERE transaksi_id=?",
(t_id,)
)
details = cur.fetchall()
self.listbox.insert(tk.END, f"Meja {meja} | ID Transaksi: {t_id}")
for menu, qty in details:
self.listbox.insert(tk.END, f" - {menu} x{qty}")
self.listbox.insert(tk.END, f"Total : Rp {total:,.0f}")
self.listbox.insert(tk.END, "-" * 70)
self.order_ids.append(t_id)
db.close() db.close()
for item in self.data_orders: # =====================
# item: (id, nama, meja, total) # VALIDASI
self.list_orders.insert(tk.END, f"ID Transaksi: {item[0]} | Meja: {item[2]} | A.N: {item[1]} | Total: Rp {int(item[3])}") # =====================
def validasi_layanan(self):
def mark_served(self): idx = self.listbox.curselection()
idx = self.list_orders.curselection()
if not idx: if not idx:
messagebox.showwarning("Pilih", "Pilih pesanan dulu!") messagebox.showwarning("Pilih", "Pilih pesanan yang sudah disajikan")
return return
selected = self.data_orders[idx[0]] # Cari ID terdekat ke atas
transaksi_id = selected[0] selected_index = idx[0]
transaksi_index = selected_index // 5
transaksi_id = self.order_ids[transaksi_index]
db = connect() db = connect()
cur = db.cursor() cur = db.cursor()
# Update Status Transaksi jadi Served cur.execute(
cur.execute("UPDATE transaksi SET status='Served' WHERE id=?", (transaksi_id,)) "UPDATE transaksi SET status='Disajikan' WHERE id=?",
(transaksi_id,)
)
db.commit() db.commit()
db.close() db.close()
messagebox.showinfo("Sukses", "Pesanan Meja selesai dilayani!") messagebox.showinfo(
self.update_data() "Sukses",
"Pesanan ditandai selesai.\nKasir bisa memproses pembayaran."
)
self.load_orders()
def logout(self):
self.frame.destroy()
from main import LoginScreen
LoginScreen(self.parent)