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"}, ] 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 = {} # ------------------------- # 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()