This commit is contained in:
Hijau-dev 2025-12-02 19:41:37 +07:00
parent bcdae5e702
commit 794c73f73e

468
Main.py
View File

@ -1,9 +1,471 @@
# Bagian Anggota 1:
import os
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog, filedialog
# Try Pillow for image support; fallback to Tkinter PhotoImage
try:
from PIL import Image, ImageTk
PIL_AVAILABLE = True
except Exception:
PIL_AVAILABLE = False
# -------------------------
# In-memory "database"
# -------------------------
users = [
{"username": "admin", "password": "123", "role": "admin"},
{"username": "kasir", "password": "123", "role": "kasir"},
{"username": "waiter", "password": "123", "role": "waiter"},
{"username": "owner", "password": "123", "role": "owner"},
{"username": "pembeli", "password": "123", "role": "pembeli"},
]
# Bagian Anggota 2:
menu = [
{"id": 1, "nama": "Es Teh", "harga": 5000, "kategori": "Minuman", "stok": 10, "foto": None, "promo": None},
{"id": 2, "nama": "Kopi Susu", "harga": 12000, "kategori": "Minuman", "stok": 8, "foto": None, "promo": "Diskon 10%"},
{"id": 3, "nama": "Nasi Goreng", "harga": 15000, "kategori": "Makanan", "stok": 5, "foto": None, "promo": None},
]
bahan = {
"teh": 50,
"kopi": 20,
"susu": 15,
"beras": 10,
"bumbu": 10
}
current_user = None
cart = []
_image_refs = {}
# Bagian Anggota 3:
# -------------------------
# Helpers
# -------------------------
def find_menu_by_id(mid):
for it in menu:
if it["id"] == mid:
return it
return None
def ensure_image(path, maxsize=(240, 160)):
if not path or not os.path.exists(path):
return None
key = (path, maxsize)
if key in _image_refs:
return _image_refs[key]
try:
if PIL_AVAILABLE:
img = Image.open(path)
img.thumbnail(maxsize)
tkimg = ImageTk.PhotoImage(img)
else:
tkimg = tk.PhotoImage(file=path)
_image_refs[key] = tkimg
return tkimg
except Exception:
return None
def reset_image_refs():
_image_refs.clear()
# -------------------------
# Keranjang Functions
# -------------------------
def _add_to_cart(self):
sel = self.tree.selection()
if not sel:
messagebox.showwarning("Pilih Menu", "Pilih menu dulu.")
return
mid = int(sel[0])
it = find_menu_by_id(mid)
if not it:
return
try:
qty = int(simpledialog.askstring("Jumlah", f"Berapa {it['nama']} yang ingin ditambahkan?"))
except:
return
if qty <= 0:
messagebox.showerror("Error", "Jumlah harus > 0")
return
if qty > it.get("stok",0):
messagebox.showerror("Error", f"Stok tidak cukup ({it.get('stok',0)})")
return
for c in cart:
if c["menu_id"] == mid:
c["jumlah"] += qty
break
else:
cart.append({"menu_id": mid, "jumlah": qty})
messagebox.showinfo("Keranjang", f"{it['nama']} x{qty} berhasil ditambahkan ke keranjang.")
def _view_cart(self):
if not cart:
messagebox.showinfo("Keranjang", "Keranjang kosong.")
return
s = ""
total = 0
for c in cart:
it = find_menu_by_id(c["menu_id"])
harga = it["harga"] * c["jumlah"]
s += f"{it['nama']} x {c['jumlah']} = Rp{harga}\n"
total += harga
s += f"\nTotal: Rp{total}"
if messagebox.askyesno("Checkout", s + "\n\nCheckout sekarang?"):
for c in cart:
it = find_menu_by_id(c["menu_id"])
it["stok"] -= c["jumlah"]
cart.clear()
self._refresh_menu()
messagebox.showinfo("Sukses", "Pesanan berhasil dibuat!")
# -------------------------
# GUI App
# -------------------------
class Anggota1App:
def __init__(self, root):
self.root = root
self.root.title("Cafe Ceria")
self.root.geometry("1000x650")
self.root.minsize(920, 600)
self._build_login()
# ---------- login ----------
def _clear_root(self):
for w in self.root.winfo_children():
w.destroy()
def _build_login(self):
self._clear_root()
frame = ttk.Frame(self.root, padding=20)
frame.pack(expand=True)
ttk.Label(frame, text="LOGIN", font=("Segoe UI", 18)).grid(row=0, column=0, columnspan=2, pady=(0,10))
ttk.Label(frame, text="Username:").grid(row=1, column=0, sticky="e", padx=5, pady=5)
self.e_user = ttk.Entry(frame)
self.e_user.grid(row=1, column=1, padx=5, pady=5)
ttk.Label(frame, text="Password:").grid(row=2, column=0, sticky="e", padx=5, pady=5)
self.e_pass = ttk.Entry(frame, show="*")
self.e_pass.grid(row=2, column=1, padx=5, pady=5)
btn = ttk.Button(frame, text="Login", command=self._handle_login)
btn.grid(row=3, column=0, columnspan=2, pady=12)
ttk.Label(frame, text="(users: admin/kasir/waiter/owner/pembeli ; pass: 123)").grid(row=4, column=0, columnspan=2, pady=(8,0))
def _handle_login(self):
global current_user
u = self.e_user.get().strip()
p = self.e_pass.get().strip()
for usr in users:
if usr["username"] == u and usr["password"] == p:
current_user = usr
messagebox.showinfo("Login", f"Berhasil login sebagai {u} ({usr['role']})")
self._build_main_ui()
return
messagebox.showerror("Login Gagal", "Username atau password salah.")
# ---------- main UI ----------
def _build_main_ui(self):
self._clear_root()
topbar = ttk.Frame(self.root)
topbar.pack(fill="x", padx=6, pady=6)
ttk.Label(topbar, text=f"User: {current_user['username']} ({current_user['role']})", font=("Segoe UI", 10)).pack(side="left")
ttk.Button(topbar, text="Logout", command=self._logout).pack(side="right")
main = ttk.Frame(self.root)
main.pack(fill="both", expand=True, padx=8, pady=4)
ctrl = ttk.Frame(main, width=320, padding=8)
ctrl.pack(side="left", fill="y")
ttk.Label(ctrl, text="Search / Cari:").pack(anchor="w")
self.search_var = tk.StringVar()
sframe = ttk.Frame(ctrl)
sframe.pack(fill="x", pady=4)
self.search_entry = ttk.Entry(sframe, textvariable=self.search_var)
self.search_entry.pack(side="left", fill="x", expand=True)
ttk.Button(sframe, text="Search", command=self._search_menu).pack(side="left", padx=4)
ttk.Button(sframe, text="Refresh", command=self._refresh_menu).pack(side="left")
ttk.Label(ctrl, text="Filter Kategori:").pack(anchor="w", pady=(10,0))
self.filter_var = tk.StringVar()
values = ["All"] + sorted({it.get("kategori","Undefined") for it in menu})
self.filter_cb = ttk.Combobox(ctrl, values=values, state="readonly", textvariable=self.filter_var)
self.filter_cb.set("All")
self.filter_cb.pack(fill="x", pady=4)
ttk.Button(ctrl, text="Apply Filter", command=self._apply_filter).pack(fill="x", pady=(0,6))
ttk.Label(ctrl, text="Sort:").pack(anchor="w", pady=(6,0))
self.sort_var = tk.StringVar()
self.sort_cb = ttk.Combobox(ctrl, values=["Default","Harga - Rendah→Tinggi","Harga - Tinggi→Rendah","Stok - Rendah→Tinggi","Stok - Tinggi→Rendah"], state="readonly", textvariable=self.sort_var)
self.sort_cb.set("Default")
self.sort_cb.pack(fill="x", pady=4)
ttk.Button(ctrl, text="Apply Sort", command=self._apply_sort).pack(fill="x", pady=(0,6))
ttk.Separator(ctrl).pack(fill="x", pady=8)
self.btn_add = ttk.Button(ctrl, text="Tambah Menu (+Foto)", command=self._gui_add_menu)
self.btn_edit = ttk.Button(ctrl, text="Edit Menu (+Ganti Foto)", command=self._gui_edit_menu)
self.btn_delete = ttk.Button(ctrl, text="Hapus Menu", command=self._gui_delete_menu)
self.btn_refresh = ttk.Button(ctrl, text="Refresh List", command=self._refresh_menu)
self.btn_add.pack(fill="x", pady=4)
self.btn_edit.pack(fill="x", pady=4)
self.btn_delete.pack(fill="x", pady=4)
self.btn_refresh.pack(fill="x", pady=6)
if current_user["role"] != "admin":
self.btn_add.state(["disabled"])
self.btn_edit.state(["disabled"])
self.btn_delete.state(["disabled"])
ttk.Label(ctrl, text="Stok Bahan:", font=("Segoe UI", 10, "bold")).pack(anchor="w", pady=(10,0))
self.bahan_listbox = tk.Listbox(ctrl, height=8)
self.bahan_listbox.pack(fill="both", expand=False, pady=(4,6))
self._refresh_bahan_listbox()
self.btn_edit_bahan = ttk.Button(ctrl, text="Tambah/Edit Stok Bahan", command=self._gui_edit_bahan)
self.btn_edit_bahan.pack(fill="x")
if current_user["role"] != "admin":
self.btn_edit_bahan.state(["disabled"])
right = ttk.Frame(main, padding=8)
right.pack(side="right", fill="both", expand=True)
ttk.Label(right, text="Daftar Menu", font=("Segoe UI", 12)).pack(anchor="w")
self.tree = ttk.Treeview(right, columns=("harga","kategori","stok"), show="headings", selectmode="browse")
self.tree.heading("harga", text="Harga")
self.tree.heading("kategori", text="Kategori")
self.tree.heading("stok", text="Stok")
self.tree.column("harga", width=120, anchor="center")
self.tree.column("kategori", width=120, anchor="center")
self.tree.column("stok", width=80, anchor="center")
self.tree.pack(fill="both", expand=True, pady=(6,8))
self.tree.bind("<<TreeviewSelect>>", self._on_tree_select)
detail_frame = ttk.Frame(right)
detail_frame.pack(fill="x", pady=4)
self.img_label = ttk.Label(detail_frame)
self.img_label.pack(side="left", padx=6)
info_frame = ttk.Frame(detail_frame)
info_frame.pack(side="left", fill="both", expand=True, padx=6)
self.detail_text = tk.Text(info_frame, height=8, state="disabled")
self.detail_text.pack(fill="both", expand=True)
# --- Tambahkan tombol keranjang untuk pembeli ---
if current_user["role"] == "pembeli":
ttk.Button(right, text="Tambah ke Keranjang", command=lambda: _add_to_cart(self)).pack(fill="x", pady=4)
ttk.Button(right, text="Lihat Keranjang / Checkout", command=lambda: _view_cart(self)).pack(fill="x", pady=4)
self._populate_tree(menu)
# ---------- Data ops ----------
def _populate_tree(self, items):
for r in self.tree.get_children():
self.tree.delete(r)
for it in items:
self.tree.insert("", "end", iid=str(it["id"]), values=(f"Rp{it['harga']}", it.get("kategori","-"), it.get("stok",0)), text=it["nama"])
self._clear_details()
def _refresh_menu(self):
self.search_var.set("")
self.filter_cb['values'] = ["All"] + sorted({it.get("kategori","Undefined") for it in menu})
self.filter_cb.set("All")
self.sort_cb.set("Default")
self._populate_tree(menu)
reset_image_refs()
self._refresh_bahan_listbox()
def _apply_filter(self):
cat = self.filter_var.get() if self.filter_var.get() else "All"
if cat == "All":
filtered = list(menu)
else:
filtered = [it for it in menu if it.get("kategori","") == cat]
self._populate_tree(filtered)
def _search_menu(self):
q = self.search_var.get().strip().lower()
if not q:
self._populate_tree(menu)
return
res = []
for it in menu:
if q in it["nama"].lower() or q in str(it["harga"]) or q in it.get("kategori","").lower():
res.append(it)
self._populate_tree(res)
def _apply_sort(self):
key = self.sort_var.get()
arr = list(menu)
if key == "Harga - Rendah→Tinggi":
arr.sort(key=lambda x: x["harga"])
elif key == "Harga - Tinggi→Rendah":
arr.sort(key=lambda x: -x["harga"])
elif key == "Stok - Rendah→Tinggi":
arr.sort(key=lambda x: x.get("stok",0))
elif key == "Stok - Tinggi→Rendah":
arr.sort(key=lambda x: -x.get("stok",0))
self._populate_tree(arr)
def _clear_details(self):
self.detail_text.config(state="normal")
self.detail_text.delete("1.0", tk.END)
self.detail_text.config(state="disabled")
self.img_label.config(image="", text="(No Image)")
self.img_label.image = None
def _on_tree_select(self, evt):
sel = self.tree.selection()
if not sel:
return
mid = int(sel[0])
it = find_menu_by_id(mid)
if not it: return
self.detail_text.config(state="normal")
self.detail_text.delete("1.0", tk.END)
s = f"ID: {it['id']}\nNama: {it['nama']}\nHarga: Rp{it['harga']}\nKategori: {it.get('kategori','-')}\nStok: {it.get('stok',0)}\nFoto: {os.path.basename(it['foto']) if it.get('foto') else 'Tidak ada'}\n"
self.detail_text.insert(tk.END, s)
self.detail_text.config(state="disabled")
img = ensure_image(it.get('foto'))
if img:
self.img_label.config(image=img, text="")
self.img_label.image = img
else:
self.img_label.config(image="", text="(No Image)")
self.img_label.image = None
# ---------- CRUD Menu ----------
def _gui_add_menu(self):
if current_user["role"] != "admin":
messagebox.showerror("Akses", "Hanya admin bisa menambah menu.")
return
dialog = AddEditDialog(self.root, title="Tambah Menu")
self.root.wait_window(dialog.top)
if dialog.result is None:
return
nama, harga, stok, kategori, foto_path = dialog.result
try:
harga = int(harga); stok = int(stok)
except:
messagebox.showerror("Error", "Harga & Stok harus angka.")
return
new_id = menu[-1]["id"] + 1 if menu else 1
menu.append({"id": new_id, "nama": nama, "harga": harga, "kategori": kategori, "stok": stok, "foto": foto_path})
messagebox.showinfo("Sukses", "Menu ditambahkan.")
self._refresh_menu()
def _gui_edit_menu(self):
if current_user["role"] != "admin":
messagebox.showerror("Akses", "Hanya admin bisa edit menu.")
return
sel = self.tree.selection()
if not sel:
messagebox.showwarning("Pilih", "Pilih menu untuk diedit.")
return
mid = int(sel[0])
it = find_menu_by_id(mid)
if not it:
messagebox.showerror("Error", "Menu tidak ditemukan.")
return
dialog = AddEditDialog(self.root, title="Edit Menu", existing=it)
self.root.wait_window(dialog.top)
if dialog.result is None:
return
nama, harga, stok, kategori, foto_path = dialog.result
try:
harga = int(harga); stok = int(stok)
except:
messagebox.showerror("Error", "Harga & Stok harus angka.")
return
it.update({"nama": nama, "harga": harga, "kategori": kategori, "stok": stok, "foto": foto_path})
messagebox.showinfo("Sukses", "Menu diperbarui.")
self._refresh_menu()
def _gui_delete_menu(self):
if current_user["role"] != "admin":
messagebox.showerror("Akses", "Hanya admin bisa hapus menu.")
return
sel = self.tree.selection()
if not sel:
messagebox.showwarning("Pilih", "Pilih menu untuk dihapus.")
return
mid = int(sel[0])
it = find_menu_by_id(mid)
if not it:
messagebox.showerror("Error", "Menu tidak ditemukan.")
return
if messagebox.askyesno("Hapus", f"Yakin ingin hapus {it['nama']}?"):
menu.remove(it)
self._refresh_menu()
# ---------- Bahan ----------
def _refresh_bahan_listbox(self):
self.bahan_listbox.delete(0, tk.END)
for k,v in bahan.items():
self.bahan_listbox.insert(tk.END, f"{k}: {v}")
def _gui_edit_bahan(self):
if current_user["role"] != "admin":
messagebox.showerror("Akses", "Hanya admin bisa edit stok bahan.")
return
k = simpledialog.askstring("Bahan", "Nama bahan:")
if not k:
return
try:
v = int(simpledialog.askstring("Stok", "Jumlah:"))
except:
return
bahan[k] = v
self._refresh_bahan_listbox()
def _logout(self):
global current_user
current_user = None
self._build_login()
# ---------- Dialog ----------
class AddEditDialog:
def __init__(self, parent, title="Tambah/Edit", existing=None):
self.top = tk.Toplevel(parent)
self.top.title(title)
self.result = None
self.existing = existing
ttk.Label(self.top, text="Nama:").grid(row=0, column=0, sticky="e", padx=5, pady=4)
self.e_nama = ttk.Entry(self.top)
self.e_nama.grid(row=0, column=1, padx=5, pady=4)
ttk.Label(self.top, text="Harga:").grid(row=1, column=0, sticky="e", padx=5, pady=4)
self.e_harga = ttk.Entry(self.top)
self.e_harga.grid(row=1, column=1, padx=5, pady=4)
ttk.Label(self.top, text="Stok:").grid(row=2, column=0, sticky="e", padx=5, pady=4)
self.e_stok = ttk.Entry(self.top)
self.e_stok.grid(row=2, column=1, padx=5, pady=4)
ttk.Label(self.top, text="Kategori:").grid(row=3, column=0, sticky="e", padx=5, pady=4)
self.e_kategori = ttk.Entry(self.top)
self.e_kategori.grid(row=3, column=1, padx=5, pady=4)
ttk.Label(self.top, text="Foto:").grid(row=4, column=0, sticky="e", padx=5, pady=4)
self.foto_path = tk.StringVar()
self.e_foto = ttk.Entry(self.top, textvariable=self.foto_path)
self.e_foto.grid(row=4, column=1, padx=5, pady=4)
ttk.Button(self.top, text="Browse", command=self._browse_file).grid(row=4, column=2, padx=4, pady=4)
ttk.Button(self.top, text="OK", command=self._ok).grid(row=5, column=0, pady=8)
ttk.Button(self.top, text="Cancel", command=self.top.destroy).grid(row=5, column=1)
if existing:
self.e_nama.insert(0, existing["nama"])
self.e_harga.insert(0, str(existing["harga"]))
self.e_stok.insert(0, str(existing.get("stok",0)))
self.e_kategori.insert(0, existing.get("kategori",""))
self.foto_path.set(existing.get("foto",""))
def _browse_file(self):
path = filedialog.askopenfilename(filetypes=[("Image files","*.png *.jpg *.jpeg *.gif")])
if path:
self.foto_path.set(path)
def _ok(self):
self.result = (
self.e_nama.get(),
self.e_harga.get(),
self.e_stok.get(),
self.e_kategori.get(),
self.foto_path.get()
)
self.top.destroy()
# -------------------------
# Main
# -------------------------
if __name__ == "__main__":
root = tk.Tk()
app = Anggota1App(root)
root.mainloop()