diff --git a/Main.py b/Main.py index 64debe6..7aebd37 100644 --- a/Main.py +++ b/Main.py @@ -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: \ No newline at end of file +# ------------------------- +# 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("<>", 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()