Compare commits
46
Commits
1bb52206cc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f349274710 | ||
|
|
4c8221971a | ||
|
|
5bd728b1bd | ||
|
|
9053434a83 | ||
|
|
e8edfe939f | ||
|
|
17bdf516b3 | ||
|
|
94e45a7aad | ||
|
|
54c14826c4 | ||
|
|
52233b09be | ||
|
|
3ab63e6327 | ||
|
|
e8bc7113d1 | ||
|
|
509715f629 | ||
|
|
c4b6e1fba9 | ||
|
|
e5c6da3417 | ||
|
|
92abbb95fa | ||
|
|
6e6218f2fa | ||
|
|
3dc145beae | ||
|
|
78c730f245 | ||
|
|
984b1ecba5 | ||
|
|
eaf12c10a4 | ||
|
|
29f0700324 | ||
|
|
2a9b7a4edc | ||
|
|
787bf561e4 | ||
|
|
b1a5224a67 | ||
|
|
a710f36393 | ||
|
|
67c6169a7e | ||
|
|
20386a867a | ||
|
|
f62c4904cd | ||
|
|
6359376da0 | ||
|
|
5f55320865 | ||
|
|
02aa1da64f | ||
|
|
db80a043d7 | ||
|
|
69e133c6e3 | ||
|
|
40a0ed8d5b | ||
|
|
deafb9c440 | ||
|
|
2482a8d090 | ||
|
|
9ae17ff2b7 | ||
|
|
10b5cc4a02 | ||
|
|
130a6e0a4f | ||
|
|
91cfa7c8c8 | ||
|
|
85e501b109 | ||
|
|
a3ae8295b9 | ||
|
|
785c873eda | ||
|
|
eb162b1888 | ||
|
|
2bc30165e2 | ||
|
|
2d2669361d |
@@ -0,0 +1 @@
|
|||||||
|
*.pyc
|
||||||
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.
+192
-97
@@ -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))
|
(
|
||||||
db.commit()
|
self.nama.get(),
|
||||||
messagebox.showinfo("Sukses", "Menu berhasil ditambahkan!")
|
self.kategori.get(),
|
||||||
self.update_data()
|
float(self.harga.get()),
|
||||||
except Exception as e:
|
self.gambar.get()
|
||||||
messagebox.showerror("Error", str(e))
|
)
|
||||||
finally:
|
)
|
||||||
db.close()
|
db.commit()
|
||||||
|
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()
|
||||||
|
|
||||||
selected = self.data_menu[idx[0]]
|
def update(self):
|
||||||
if messagebox.askyesno("Hapus", f"Hapus {selected[1]}?"):
|
idx = self.listbox.curselection()
|
||||||
|
if not idx:
|
||||||
|
messagebox.showwarning("Pilih", "Pilih menu dulu")
|
||||||
|
return
|
||||||
|
|
||||||
|
menu_id = self.menu_data[idx[0]][0]
|
||||||
|
|
||||||
|
db = connect()
|
||||||
|
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.close()
|
||||||
|
|
||||||
|
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()
|
db = connect()
|
||||||
db.cursor().execute("DELETE FROM menu WHERE id=?", (selected[0],))
|
cur = db.cursor()
|
||||||
|
cur.execute("DELETE FROM menu WHERE id=?", (menu_id,))
|
||||||
db.commit()
|
db.commit()
|
||||||
db.close()
|
db.close()
|
||||||
self.update_data()
|
|
||||||
|
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 |
Binary file not shown.
+54
-23
@@ -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()
|
|
||||||
@@ -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()
|
|
||||||
+330
-42
@@ -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
|
||||||
|
|
||||||
def bayar(self):
|
self.listbox.insert(
|
||||||
idx = self.order_list.curselection()
|
tk.END,
|
||||||
if not idx:
|
f"Meja {meja} | ID {t_id} | Total Rp {total:,.0f}"
|
||||||
messagebox.showwarning("Pilih", "Pilih tagihan yang mau dibayar!")
|
)
|
||||||
return
|
|
||||||
|
|
||||||
selected = self.data_orders[idx[0]]
|
# 🔽 AMBIL DETAIL MENU
|
||||||
transaksi_id = selected[0]
|
|
||||||
nama = selected[1]
|
|
||||||
total = selected[3]
|
|
||||||
|
|
||||||
confirm = messagebox.askyesno("Konfirmasi", f"Terima pembayaran Rp {int(total)} dari {nama}?")
|
|
||||||
if confirm:
|
|
||||||
db = connect()
|
db = connect()
|
||||||
cur = db.cursor()
|
cur = db.cursor()
|
||||||
|
cur.execute("""
|
||||||
# Update status jadi 'Paid' dan metode pembayaran (misal Cash default)
|
SELECT menu_nama, jumlah
|
||||||
cur.execute("UPDATE transaksi SET status='Paid', metode_pembayaran='Cash' WHERE id=?", (transaksi_id,))
|
FROM detail_transaksi
|
||||||
|
WHERE transaksi_id=?
|
||||||
db.commit()
|
""", (t_id,))
|
||||||
|
items = cur.fetchall()
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
messagebox.showinfo("Lunas", "Transaksi Selesai & Lunas!")
|
for nama, qty in items:
|
||||||
self.update_data()
|
self.listbox.insert(
|
||||||
|
tk.END,
|
||||||
|
f" • {nama} x{qty}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.listbox.insert(tk.END, "-" * 50)
|
||||||
|
self.transaksi_data.append(row)
|
||||||
|
|
||||||
|
|
||||||
|
# =====================
|
||||||
|
# BAYAR
|
||||||
|
# =====================
|
||||||
|
def bayar(self):
|
||||||
|
idx = self.listbox.curselection()
|
||||||
|
if not idx:
|
||||||
|
messagebox.showwarning("Pilih", "Pilih tagihan yang akan dibayar!")
|
||||||
|
return
|
||||||
|
|
||||||
|
idx = self.listbox.curselection()
|
||||||
|
if idx:
|
||||||
|
teks_item = self.listbox.get(idx).split(" ")
|
||||||
|
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?
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
cur = db.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
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()
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
|
||||||
|
struk_tambahan = ""
|
||||||
|
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)
|
||||||
|
|||||||
+27
-37
@@ -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
|
if role=="admin":
|
||||||
self.user.delete(0, tk.END)
|
AdminMenu(self.root)
|
||||||
self.passw.delete(0, tk.END)
|
elif role=="pembeli":
|
||||||
|
PembeliMenu(self.root)
|
||||||
# Arahkan sesuai Role (Nama Class harus sama dengan di main.py)
|
elif role=="kasir":
|
||||||
if role == "admin":
|
KasirPage(self.root)
|
||||||
self.controller.show_frame("AdminPage")
|
elif role=="pemilik":
|
||||||
elif role == "pembeli":
|
PemilikPage(self.root)
|
||||||
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")
|
|
||||||
|
|||||||
+78
-35
@@ -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()
|
||||||
+194
-132
@@ -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
|
db = connect()
|
||||||
col = index % columns
|
cur = db.cursor()
|
||||||
|
cur.execute("SELECT nama, harga, gambar FROM menu")
|
||||||
|
data = cur.fetchall()
|
||||||
|
db.close()
|
||||||
|
|
||||||
card = tk.Frame(self.scrollable_frame, bd=2, relief="groove", bg="white")
|
columns = 4
|
||||||
card.grid(row=row, column=col, padx=5, pady=5, sticky="nsew")
|
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)
|
||||||
|
|
||||||
# Load Gambar
|
try:
|
||||||
path = os.path.join("aset", file_gambar)
|
if not gambar:
|
||||||
try:
|
raise ValueError("gambar kosong")
|
||||||
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()
|
# gambar dari DB sudah mengandung 'aset/...'
|
||||||
tk.Label(card, text=f"Rp {int(harga):,}", fg="green", bg="white").pack()
|
img_path = os.path.join(BASE_DIR, gambar)
|
||||||
|
|
||||||
# Tombol Tambah
|
if not os.path.exists(img_path):
|
||||||
state = "normal" if stok > 0 else "disabled"
|
raise FileNotFoundError(img_path)
|
||||||
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):
|
img = Image.open(img_path)
|
||||||
# item = (id, nama, kategori, harga, stok, gambar)
|
img = img.resize((140, 90), Image.Resampling.LANCZOS)
|
||||||
self.keranjang.append(item)
|
photo = ImageTk.PhotoImage(img)
|
||||||
self.update_keranjang_ui()
|
|
||||||
|
|
||||||
def update_keranjang_ui(self):
|
self.images.append(photo)
|
||||||
self.cart_listbox.delete(0, tk.END)
|
tk.Label(card, image=photo).pack(pady=5)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print("IMG ERROR:", e)
|
||||||
|
tk.Label(card, text="No Image", height=6).pack()
|
||||||
|
|
||||||
|
|
||||||
|
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
|
total = 0
|
||||||
for item in self.keranjang:
|
for item in self.cart:
|
||||||
nama = item[1]
|
self.listbox.insert(tk.END, f"{item['nama']} (x{item['qty']}) - Rp {item['subtotal']:,.0f}")
|
||||||
harga = item[3]
|
total += item['subtotal']
|
||||||
self.cart_listbox.insert(tk.END, f"{nama} - Rp {int(harga):,}")
|
self.total_lbl.config(text=f"Total: Rp {total:,.0f}")
|
||||||
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):
|
def checkout(self):
|
||||||
if not self.keranjang:
|
if not self.entry_nama.get() or not self.cart:
|
||||||
messagebox.showwarning("Kosong", "Keranjang masih kosong!")
|
messagebox.showwarning("Error", "Data belum lengkap")
|
||||||
return
|
return
|
||||||
|
|
||||||
nama_pelanggan = simpledialog.askstring("Input", "Masukkan Nama Pelanggan:")
|
total = sum(item['subtotal'] for item in self.cart)
|
||||||
if not nama_pelanggan: return
|
tanggal = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
total_harga = sum(item[3] for item in self.keranjang)
|
|
||||||
|
|
||||||
# Simpan ke Database
|
|
||||||
db = connect()
|
db = connect()
|
||||||
cur = db.cursor()
|
cur = db.cursor()
|
||||||
|
|
||||||
try:
|
# 1️⃣ Simpan transaksi
|
||||||
# 1. Simpan Transaksi Utama
|
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("""
|
cur.execute("""
|
||||||
INSERT INTO transaksi (nama_pelanggan, total, status)
|
INSERT INTO detail_transaksi
|
||||||
VALUES (?, ?, 'Pending')
|
(transaksi_id, menu_nama, harga, jumlah, subtotal)
|
||||||
""", (nama_pelanggan, total_harga))
|
VALUES (?, ?, ?, ?, ?)
|
||||||
transaksi_id = cur.lastrowid
|
""", (
|
||||||
|
transaksi_id,
|
||||||
|
item['nama'],
|
||||||
|
item['harga_satuan'],
|
||||||
|
item['qty'],
|
||||||
|
item['subtotal']
|
||||||
|
))
|
||||||
|
|
||||||
# 2. Simpan Detail Transaksi (Looping item di keranjang)
|
db.commit()
|
||||||
for item in self.keranjang:
|
db.close()
|
||||||
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
|
messagebox.showinfo("Berhasil", "Pesanan berhasil dikirim")
|
||||||
cur.execute("UPDATE menu SET stok = stok - 1 WHERE id = ?", (menu_id,))
|
self.cart.clear()
|
||||||
|
self.refresh_cart()
|
||||||
|
|
||||||
db.commit()
|
|
||||||
messagebox.showinfo("Berhasil", "Pesanan berhasil dibuat! Silakan bayar di kasir.")
|
|
||||||
|
|
||||||
# Reset
|
def logout(self):
|
||||||
self.keranjang = []
|
self.frame.destroy()
|
||||||
self.update_keranjang_ui()
|
from main import LoginScreen
|
||||||
self.load_menu() # Reload menu untuk update stok visual
|
LoginScreen(self.parent)
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
db.rollback()
|
# ================= RUN =================
|
||||||
messagebox.showerror("Error", f"Gagal Checkout: {e}")
|
if __name__ == '__main__':
|
||||||
finally:
|
root = tk.Tk()
|
||||||
db.close()
|
root.title("Sistem Cafe Python")
|
||||||
|
root.geometry("1200x700")
|
||||||
|
PembeliMenu(root)
|
||||||
|
root.mainloop()
|
||||||
+163
-24
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user