Revisi Users Order System
This commit is contained in:
parent
f616da6fa0
commit
fb42d12435
708
main.py
708
main.py
@ -9,7 +9,7 @@
|
||||
import os
|
||||
import csv
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
from tkinter import ttk, messagebox, filedialog, simpledialog
|
||||
from PIL import Image, ImageTk
|
||||
from datetime import datetime
|
||||
|
||||
@ -22,19 +22,20 @@ FAVORITES_CSV = "favorites.csv"
|
||||
IMG_PREVIEW_SIZE = (120, 80)
|
||||
|
||||
COLORS = {
|
||||
'primary': '#2C3E50', # Dark Blue
|
||||
'secondary': '#E67E22', # Orange
|
||||
'success': '#27AE60', # Green
|
||||
'danger': '#E74C3C', # Red
|
||||
'warning': '#F39C12', # Yellow
|
||||
'info': '#3498DB', # Light Blue
|
||||
'light': '#ECF0F1', # Light Gray
|
||||
'dark': '#34495E', # Dark Gray
|
||||
'bg_gradient_start': '#667eea', # Purple
|
||||
'bg_gradient_end': '#764ba2', # Dark Purple
|
||||
'cafe_brown': '#8B4513', # Saddle Brown
|
||||
'cafe_cream': '#F5DEB3', # Wheat
|
||||
'cafe_green': '#228B22', # Forest Green
|
||||
# Totoro Forest Theme
|
||||
'primary': '#5D8A66', # Forest Green (warna daun)
|
||||
'secondary': '#8B7355', # Wood Brown (warna pohon)
|
||||
'success': '#7BA05B', # Soft Green
|
||||
'danger': '#C1666B', # Soft Red
|
||||
'warning': '#E8B86D', # Warm Yellow
|
||||
'info': '#6B9AC4', # Sky Blue
|
||||
'light': '#F5F3E7', # Cream (kertas antik)
|
||||
'dark': '#3E4E3A', # Dark Forest
|
||||
'totoro_gray': '#9CA3AF', # Abu-abu Totoro
|
||||
'totoro_belly': '#E8DCC4', # Perut Totoro (cream)
|
||||
'leaf_green': '#A8D5A3', # Hijau muda daun
|
||||
'tree_brown': '#6B4423', # Cokelat tua kayu
|
||||
'soft_cream': '#FAF8F1', # Background lembut
|
||||
}
|
||||
|
||||
|
||||
@ -385,17 +386,6 @@ def promo_get(code):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 19 juta lapangan badmin
|
||||
|
||||
# Buat logika diskon + promok
|
||||
@ -596,13 +586,18 @@ def get_user_favorites(user_id, limit=5):
|
||||
|
||||
|
||||
# Wilayah dikuasai UI
|
||||
|
||||
|
||||
def add_forest_decoration(parent_frame):
|
||||
deco = tk.Label(parent_frame, text="🌿🍃",
|
||||
font=("Arial", 12),
|
||||
bg=parent_frame['bg'] if 'bg' in parent_frame.keys() else COLORS['soft_cream'],
|
||||
fg=COLORS['leaf_green'])
|
||||
return deco
|
||||
|
||||
class App:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("🍵 Cafe Totoro Mania")
|
||||
self.root.title("🌳 Cafe Totoro - Forest Coffee & Treats 🐾")
|
||||
self.root.configure(bg=COLORS['tree_brown'])
|
||||
self.session = None
|
||||
self.img_cache = {}
|
||||
self.cart = []
|
||||
@ -613,24 +608,58 @@ class App:
|
||||
style = ttk.Style()
|
||||
style.theme_use('clam')
|
||||
|
||||
# Button styles
|
||||
style.configure('Primary.TButton', background=COLORS['secondary'],
|
||||
foreground='white', font=('Arial', 10, 'bold'))
|
||||
style.map('Primary.TButton', background=[('active', COLORS['warning'])])
|
||||
# Totoro Theme Colors
|
||||
style.configure('TFrame', background=COLORS['soft_cream'])
|
||||
style.configure('TLabel', background=COLORS['soft_cream'],
|
||||
foreground=COLORS['dark'], font=('Georgia', 10))
|
||||
style.configure('TNotebook', background=COLORS['light'])
|
||||
style.configure('TNotebook.Tab', padding=[20, 10],
|
||||
font=('Georgia', 10, 'bold'))
|
||||
|
||||
style.configure('Success.TButton', background=COLORS['success'],
|
||||
foreground='white', font=('Arial', 10, 'bold'))
|
||||
# Button styles - Totoro Theme
|
||||
style.configure('Primary.TButton',
|
||||
background=COLORS['primary'],
|
||||
foreground='white',
|
||||
font=('Georgia', 10, 'bold'),
|
||||
borderwidth=0,
|
||||
focuscolor='none')
|
||||
style.map('Primary.TButton',
|
||||
background=[('active', COLORS['leaf_green']),
|
||||
('pressed', COLORS['success'])])
|
||||
|
||||
style.configure('Danger.TButton', background=COLORS['danger'],
|
||||
foreground='white', font=('Arial', 10, 'bold'))
|
||||
style.configure('Success.TButton',
|
||||
background=COLORS['success'],
|
||||
foreground='white',
|
||||
font=('Georgia', 10, 'bold'))
|
||||
style.map('Success.TButton',
|
||||
background=[('active', COLORS['leaf_green'])])
|
||||
|
||||
# Treeview style
|
||||
style.configure('Treeview', background=COLORS['light'],
|
||||
foreground=COLORS['dark'], font=('Arial', 9))
|
||||
style.configure('Treeview.Heading', background=COLORS['primary'],
|
||||
foreground='white', font=('Arial', 10, 'bold'))
|
||||
style.map('Treeview', background=[('selected', COLORS['info'])])
|
||||
style.configure('Danger.TButton',
|
||||
background=COLORS['danger'],
|
||||
foreground='white',
|
||||
font=('Georgia', 10, 'bold'))
|
||||
|
||||
style.configure('Accent.TButton',
|
||||
background=COLORS['warning'],
|
||||
foreground=COLORS['dark'],
|
||||
font=('Georgia', 11, 'bold'),
|
||||
padding=10)
|
||||
|
||||
# Treeview style - Forest Theme
|
||||
style.configure('Treeview',
|
||||
background=COLORS['soft_cream'],
|
||||
foreground=COLORS['dark'],
|
||||
fieldbackground=COLORS['soft_cream'],
|
||||
font=('Georgia', 9),
|
||||
rowheight=25)
|
||||
style.configure('Treeview.Heading',
|
||||
background=COLORS['primary'],
|
||||
foreground='white',
|
||||
font=('Georgia', 10, 'bold'),
|
||||
relief='flat')
|
||||
style.map('Treeview',
|
||||
background=[('selected', COLORS['leaf_green'])],
|
||||
foreground=[('selected', 'white')])
|
||||
def setup_ui(self):
|
||||
self.root.geometry("1000x650")
|
||||
self.root.resizable(False, False)
|
||||
@ -639,41 +668,44 @@ class App:
|
||||
def login_frame(self):
|
||||
for w in self.root.winfo_children():
|
||||
w.destroy()
|
||||
|
||||
|
||||
# Main container dengan background cafe
|
||||
main_frame = tk.Frame(self.root, bg=COLORS['cafe_brown'])
|
||||
main_frame = tk.Frame(self.root, bg=COLORS['tree_brown'])
|
||||
main_frame.pack(fill='both', expand=True)
|
||||
|
||||
# Center frame
|
||||
# Center frame
|
||||
center = tk.Frame(main_frame, bg='white', relief='raised', bd=3)
|
||||
center.place(relx=0.5, rely=0.5, anchor='center', width=500, height=550)
|
||||
|
||||
# Logo/Header Section
|
||||
header_frame = tk.Frame(center, bg=COLORS['cafe_green'], height=120)
|
||||
|
||||
# Logo/Header Section - Totoro Theme
|
||||
header_frame = tk.Frame(center, bg=COLORS['primary'], height=140)
|
||||
header_frame.pack(fill='x')
|
||||
|
||||
tk.Label(header_frame, text="☕", font=("Arial", 48),
|
||||
bg=COLORS['cafe_green'], fg='white').pack(pady=5)
|
||||
|
||||
tk.Label(header_frame, text="CAFE TOTORO MANIA",
|
||||
font=("Arial", 24, "bold"), bg=COLORS['cafe_green'],
|
||||
fg='white').pack()
|
||||
|
||||
tk.Label(header_frame, text="~ Your Cozy Coffee Corner ~",
|
||||
font=("Arial", 11, "italic"), bg=COLORS['cafe_green'],
|
||||
fg=COLORS['cafe_cream']).pack(pady=2)
|
||||
# Totoro ASCII Art / Emoji
|
||||
tk.Label(header_frame, text="🌳", font=("Arial", 28),
|
||||
bg=COLORS['primary'], fg=COLORS['leaf_green']).pack(side='left', padx=10, pady=10)
|
||||
|
||||
tk.Label(header_frame, text="ㅤ🐾", font=("Arial", 40),
|
||||
bg=COLORS['primary'], fg=COLORS['totoro_gray']).pack(pady=5)
|
||||
|
||||
tk.Label(header_frame, text="🌿", font=("Arial", 28),
|
||||
bg=COLORS['primary'], fg=COLORS['leaf_green']).pack(side='right', padx=10, pady=10)
|
||||
|
||||
tk.Label(header_frame, text="CAFE TOTORO",
|
||||
font=("Georgia", 28, "bold"), bg=COLORS['primary'],
|
||||
fg=COLORS['soft_cream']).pack()
|
||||
|
||||
tk.Label(header_frame, text="✦ Welcome to the Forest Cafe ✦",
|
||||
font=("Georgia", 11, "italic"), bg=COLORS['primary'],
|
||||
fg=COLORS['totoro_belly']).pack(pady=5)
|
||||
|
||||
# Form Section
|
||||
form_frame = tk.Frame(center, bg='white', padx=40, pady=30)
|
||||
form_frame = tk.Frame(center, bg=COLORS['soft_cream'], padx=40, pady=30)
|
||||
form_frame.pack(fill='both', expand=True)
|
||||
|
||||
tk.Label(form_frame, text="Selamat Datang! 👋",
|
||||
font=("Arial", 18, "bold"), bg='white',
|
||||
fg=COLORS['cafe_brown']).pack(pady=(0,10))
|
||||
|
||||
tk.Label(form_frame, text="Silakan login untuk melanjutkan",
|
||||
font=("Arial", 10), bg='white',
|
||||
fg=COLORS['dark']).pack(pady=(0,25))
|
||||
tk.Label(form_frame, text="Mari masuk ke hutan ajaib kami",
|
||||
font=("Georgia", 10, "italic"), bg=COLORS['soft_cream'],
|
||||
fg=COLORS['totoro_gray']).pack(pady=(0,25))
|
||||
|
||||
# Username
|
||||
tk.Label(form_frame, text="👤 Username", font=("Arial", 11, "bold"),
|
||||
@ -681,34 +713,37 @@ class App:
|
||||
|
||||
self.username_var = tk.StringVar()
|
||||
username_entry = tk.Entry(form_frame, textvariable=self.username_var,
|
||||
font=("Arial", 12), relief='solid', bd=2)
|
||||
font=("Arial", 12), relief='solid', bd=2,
|
||||
bg='white', fg=COLORS['dark'])
|
||||
username_entry.pack(fill='x', ipady=8)
|
||||
|
||||
# Password
|
||||
tk.Label(form_frame, text="🔒 Password", font=("Arial", 11, "bold"),
|
||||
bg='white', fg=COLORS['dark']).pack(anchor='w', pady=(15,5))
|
||||
tk.Label(form_frame, text="🔒 Password", font=("Georgia", 11, "bold"),
|
||||
bg=COLORS['soft_cream'], fg=COLORS['dark']).pack(anchor='w', pady=(15,5))
|
||||
|
||||
self.password_var = tk.StringVar()
|
||||
password_entry = tk.Entry(form_frame, textvariable=self.password_var,
|
||||
show="●", font=("Arial", 12), relief='solid', bd=2)
|
||||
show="●", font=("Arial", 12), relief='solid', bd=2,
|
||||
bg='white', fg=COLORS['dark'])
|
||||
password_entry.pack(fill='x', ipady=8)
|
||||
|
||||
# Login Button
|
||||
login_btn = tk.Button(form_frame, text="🔐 LOGIN", command=self.handle_login,
|
||||
font=("Arial", 13, "bold"), bg=COLORS['cafe_green'],
|
||||
fg='white', relief='flat', cursor='hand2', bd=0)
|
||||
login_btn = tk.Button(form_frame, text="🌿 MASUK KE HUTAN", command=self.handle_login,
|
||||
font=("Georgia", 13, "bold"), bg=COLORS['primary'],
|
||||
fg='white', relief='flat', cursor='hand2', bd=0,
|
||||
activebackground=COLORS['success'])
|
||||
login_btn.pack(fill='x', pady=(25,10), ipady=10)
|
||||
|
||||
# Hover effect
|
||||
login_btn.bind("<Enter>", lambda e: login_btn.config(bg=COLORS['success']))
|
||||
login_btn.bind("<Leave>", lambda e: login_btn.config(bg=COLORS['cafe_green']))
|
||||
login_btn.bind("<Enter>", lambda e: login_btn.config(bg=COLORS['leaf_green']))
|
||||
login_btn.bind("<Leave>", lambda e: login_btn.config(bg=COLORS['primary']))
|
||||
|
||||
# Info text
|
||||
info_frame = tk.Frame(center, bg=COLORS['light'], height=60)
|
||||
info_frame = tk.Frame(center, bg=COLORS['totoro_belly'], height=60)
|
||||
info_frame.pack(fill='x', side='bottom')
|
||||
|
||||
tk.Label(info_frame, text="💡 Tip: Gunakan user/user123 untuk pembeli",
|
||||
font=("Arial", 9), bg=COLORS['light'],
|
||||
tk.Label(info_frame, text="🍃 Tip: Gunakan user/user123 untuk role pembeli",
|
||||
font=("Georgia", 9), bg=COLORS['totoro_belly'],
|
||||
fg=COLORS['dark']).pack(pady=15)
|
||||
|
||||
def handle_login(self):
|
||||
@ -734,28 +769,79 @@ class App:
|
||||
def dashboard_frame(self):
|
||||
for w in self.root.winfo_children():
|
||||
w.destroy()
|
||||
top = ttk.Frame(self.root)
|
||||
|
||||
# Header
|
||||
top = tk.Frame(self.root, bg=COLORS['primary'], height=50)
|
||||
top.pack(fill='x')
|
||||
ttk.Label(top, text=f"User: {self.session['username']} | Role: {self.session['role']}",
|
||||
font=("Arial", 12)).pack(side='left', padx=10, pady=6)
|
||||
ttk.Button(top, text="Logout", command=self.logout).pack(side='right', padx=10)
|
||||
|
||||
welcome_text = f"🐾 Halo, {self.session['username']} • {self.session['role'].upper()}"
|
||||
tk.Label(top, text=welcome_text,
|
||||
font=("Georgia", 12, "bold"),
|
||||
bg=COLORS['primary'],
|
||||
fg=COLORS['soft_cream']).pack(side='left', padx=20, pady=12)
|
||||
|
||||
logout_btn = tk.Button(top, text="🌙 Logout", command=self.logout,
|
||||
font=("Georgia", 10, "bold"),
|
||||
bg=COLORS['secondary'],
|
||||
fg='white',
|
||||
relief='flat',
|
||||
cursor='hand2',
|
||||
padx=15, pady=5)
|
||||
logout_btn.pack(side='right', padx=20, pady=8)
|
||||
logout_btn.bind("<Enter>", lambda e: logout_btn.config(bg=COLORS['danger']))
|
||||
logout_btn.bind("<Leave>", lambda e: logout_btn.config(bg=COLORS['secondary']))
|
||||
|
||||
# Notebook tabs
|
||||
main = ttk.Notebook(self.root)
|
||||
main.pack(fill='both', expand=True, padx=10, pady=8)
|
||||
self.tab_menu_manage = ttk.Frame(main)
|
||||
self.tab_menu_view = ttk.Frame(main)
|
||||
self.tab_promo = ttk.Frame(main)
|
||||
|
||||
main.add(self.tab_menu_view, text="Menu - View")
|
||||
if self.session['role'] == 'admin':
|
||||
main.add(self.tab_menu_manage, text="Menu - Manage")
|
||||
main.add(self.tab_promo, text="Promo - Manage")
|
||||
else:
|
||||
pass
|
||||
|
||||
self.build_menu_view_tab(self.tab_menu_view)
|
||||
if self.session['role'] == 'admin':
|
||||
main.pack(fill='both', expand=True, padx=10, pady=10)
|
||||
|
||||
role = self.session['role']
|
||||
|
||||
# Setup tabs berdasarkan role
|
||||
if role == 'pembeli':
|
||||
self.tab_order = ttk.Frame(main)
|
||||
self.tab_favorites = ttk.Frame(main)
|
||||
main.add(self.tab_order, text="🛒 Pesan Menu")
|
||||
main.add(self.tab_favorites, text="⭐ Menu Favorit")
|
||||
self.build_order_tab(self.tab_order)
|
||||
self.build_favorites_tab(self.tab_favorites)
|
||||
|
||||
elif role == 'waiter':
|
||||
self.tab_waiter = ttk.Frame(main)
|
||||
main.add(self.tab_waiter, text="📋 Dashboard Waiter")
|
||||
self.build_waiter_tab(self.tab_waiter)
|
||||
|
||||
elif role == 'admin':
|
||||
self.tab_menu_view = ttk.Frame(main)
|
||||
self.tab_menu_manage = ttk.Frame(main)
|
||||
self.tab_promo = ttk.Frame(main)
|
||||
self.tab_waiter = ttk.Frame(main)
|
||||
main.add(self.tab_menu_view, text="📖 Menu - View")
|
||||
main.add(self.tab_menu_manage, text="⚙️ Menu - Manage")
|
||||
main.add(self.tab_promo, text="🎁 Promo - Manage")
|
||||
main.add(self.tab_waiter, text="📋 Dashboard Waiter")
|
||||
self.build_menu_view_tab(self.tab_menu_view)
|
||||
self.build_menu_manage_tab(self.tab_menu_manage)
|
||||
self.build_promo_tab(self.tab_promo)
|
||||
self.build_waiter_tab(self.tab_waiter)
|
||||
|
||||
else: # kasir, owner
|
||||
self.tab_menu_view = ttk.Frame(main)
|
||||
main.add(self.tab_menu_view, text="📖 Menu - View")
|
||||
self.build_menu_view_tab(self.tab_menu_view)
|
||||
|
||||
def build_order_tab(self, parent):
|
||||
"""Tab pemesanan untuk pembeli"""
|
||||
print("🔧 build_order_tab called")
|
||||
|
||||
# Initialize cart dan qty variable PERTAMA KALI
|
||||
if not hasattr(self, 'cart'):
|
||||
self.cart = []
|
||||
print(" ✓ Cart initialized")
|
||||
|
||||
if not hasattr(self, 'order_qty_var'):
|
||||
self.order_qty_var = tk.StringVar(value="1")
|
||||
print(" ✓ Quantity variable initialized")
|
||||
|
||||
def build_menu_view_tab(self, parent):
|
||||
for w in parent.winfo_children():
|
||||
@ -1098,9 +1184,16 @@ class App:
|
||||
self.reload_promo_table()
|
||||
|
||||
|
||||
# ========== TAB: ORDER SYSTEM (PEMBELI) ==========
|
||||
# ========== TAB: ORDER SYSTEM (PEMBELI) ==========
|
||||
def build_order_tab(self, parent):
|
||||
"""Tab pemesanan untuk pembeli"""
|
||||
# Initialize cart kalau belum ada
|
||||
if not hasattr(self, 'cart'):
|
||||
self.cart = []
|
||||
print("🔧 Cart initialized")
|
||||
|
||||
# Clear parent
|
||||
for w in parent.winfo_children():
|
||||
w.destroy()
|
||||
|
||||
@ -1108,93 +1201,195 @@ class App:
|
||||
container = ttk.Frame(parent)
|
||||
container.pack(fill='both', expand=True, padx=10, pady=10)
|
||||
|
||||
# LEFT: Daftar Menu
|
||||
# ========== LEFT SIDE: DAFTAR MENU ==========
|
||||
left = ttk.Frame(container, relief='solid', borderwidth=1)
|
||||
left.pack(side='left', fill='both', expand=True, padx=(0, 5))
|
||||
|
||||
ttk.Label(left, text="🍽️ Menu Tersedia", font=("Arial", 14, "bold")).pack(pady=8)
|
||||
# Header
|
||||
ttk.Label(left, text="🌿 Menu Hutan Totoro",
|
||||
font=("Georgia", 14, "bold")).pack(pady=8)
|
||||
|
||||
# Filter pencarian
|
||||
filter_frm = ttk.Frame(left)
|
||||
filter_frm.pack(fill='x', padx=10, pady=5)
|
||||
|
||||
ttk.Label(filter_frm, text="Cari Menu:").pack(side='left', padx=5)
|
||||
self.order_search_var = tk.StringVar()
|
||||
ttk.Entry(filter_frm, textvariable=self.order_search_var, width=20).pack(side='left', padx=5)
|
||||
ttk.Button(filter_frm, text="🔍 Cari", command=self.reload_order_menu).pack(side='left', padx=3)
|
||||
ttk.Button(filter_frm, text="🔄 Reset", command=self.reset_order_search).pack(side='left', padx=3)
|
||||
ttk.Entry(filter_frm, textvariable=self.order_search_var,
|
||||
width=20).pack(side='left', padx=5)
|
||||
ttk.Button(filter_frm, text="🔍 Cari",
|
||||
command=self.reload_order_menu).pack(side='left', padx=3)
|
||||
ttk.Button(filter_frm, text="🔄 Reset",
|
||||
command=self.reset_order_search).pack(side='left', padx=3)
|
||||
|
||||
# Treeview menu
|
||||
cols = ("ID", "Nama", "Kategori", "Harga", "Stok")
|
||||
self.order_menu_tree = ttk.Treeview(left, columns=cols, show='headings', height=15)
|
||||
self.order_menu_tree = ttk.Treeview(left, columns=cols,
|
||||
show='headings', height=15)
|
||||
for c in cols:
|
||||
self.order_menu_tree.heading(c, text=c)
|
||||
w = 50 if c == "ID" else (180 if c == "Nama" else 100)
|
||||
self.order_menu_tree.column(c, width=w)
|
||||
self.order_menu_tree.pack(fill='both', expand=True, padx=10, pady=5)
|
||||
|
||||
# Tombol tambah ke keranjang
|
||||
add_frame = ttk.Frame(left)
|
||||
# ========== SECTION: TAMBAH KE KERANJANG ==========
|
||||
add_frame = tk.Frame(left, bg=COLORS['soft_cream'])
|
||||
add_frame.pack(fill='x', padx=10, pady=8)
|
||||
ttk.Label(add_frame, text="Jumlah:", font=("Arial", 10)).pack(side='left', padx=5)
|
||||
self.order_qty_var = tk.StringVar(value="1")
|
||||
ttk.Entry(add_frame, textvariable=self.order_qty_var, width=8).pack(side='left', padx=5)
|
||||
ttk.Button(add_frame, text="➕ Tambah ke Keranjang", command=self.add_to_cart).pack(side='left', padx=10)
|
||||
|
||||
# RIGHT: Keranjang
|
||||
tk.Label(add_frame, text="Jumlah:", font=("Georgia", 10, "bold"),
|
||||
bg=COLORS['soft_cream'], fg=COLORS['dark']).pack(side='left', padx=5)
|
||||
|
||||
# Initialize qty variable
|
||||
if not hasattr(self, 'order_qty_var'):
|
||||
self.order_qty_var = tk.StringVar(value="1")
|
||||
|
||||
# Frame untuk tombol +/-
|
||||
qty_frame = tk.Frame(add_frame, bg=COLORS['soft_cream'])
|
||||
qty_frame.pack(side='left', padx=5)
|
||||
|
||||
# Tombol MINUS
|
||||
btn_minus = tk.Button(qty_frame, text="➖", width=3,
|
||||
command=self.decrease_qty,
|
||||
bg=COLORS['danger'], fg='white',
|
||||
font=('Arial', 10, 'bold'),
|
||||
relief='flat', cursor='hand2',
|
||||
activebackground='#C1666B')
|
||||
btn_minus.pack(side='left', padx=2)
|
||||
|
||||
# Entry QUANTITY
|
||||
qty_entry = tk.Entry(qty_frame, textvariable=self.order_qty_var,
|
||||
width=6, justify='center',
|
||||
font=('Georgia', 11, 'bold'),
|
||||
bg='white', fg=COLORS['dark'])
|
||||
qty_entry.pack(side='left', padx=3)
|
||||
|
||||
# Tombol PLUS
|
||||
btn_plus = tk.Button(qty_frame, text="➕", width=3,
|
||||
command=self.increase_qty,
|
||||
bg=COLORS['success'], fg='white',
|
||||
font=('Arial', 10, 'bold'),
|
||||
relief='flat', cursor='hand2',
|
||||
activebackground='#7BA05B')
|
||||
btn_plus.pack(side='left', padx=2)
|
||||
|
||||
# Tombol TAMBAH KE KERANJANG
|
||||
btn_add = tk.Button(add_frame, text="🛒 Tambah ke Keranjang",
|
||||
command=self.add_to_cart,
|
||||
bg=COLORS['primary'], fg='white',
|
||||
font=('Georgia', 10, 'bold'),
|
||||
relief='flat', cursor='hand2',
|
||||
padx=15, pady=5,
|
||||
activebackground=COLORS['leaf_green'])
|
||||
btn_add.pack(side='left', padx=10)
|
||||
|
||||
# ========== RIGHT SIDE: KERANJANG ==========
|
||||
right = ttk.Frame(container, relief='solid', borderwidth=1)
|
||||
right.pack(side='right', fill='both', padx=(5, 0))
|
||||
right.config(width=350)
|
||||
right.config(width=380)
|
||||
|
||||
ttk.Label(right, text="🛒 Keranjang Belanja", font=("Arial", 14, "bold")).pack(pady=8)
|
||||
# Header keranjang
|
||||
ttk.Label(right, text="🧺 Keranjang Pesanan",
|
||||
font=("Georgia", 14, "bold")).pack(pady=8)
|
||||
|
||||
# Treeview keranjang
|
||||
cart_cols = ("Menu", "Harga", "Qty", "Subtotal")
|
||||
self.cart_tree = ttk.Treeview(right, columns=cart_cols, show='headings', height=12)
|
||||
self.cart_tree = ttk.Treeview(right, columns=cart_cols,
|
||||
show='headings', height=12)
|
||||
for c in cart_cols:
|
||||
self.cart_tree.heading(c, text=c)
|
||||
w = 120 if c == "Menu" else 70
|
||||
w = 130 if c == "Menu" else 75
|
||||
self.cart_tree.column(c, width=w)
|
||||
self.cart_tree.pack(fill='both', padx=10, pady=5)
|
||||
|
||||
# Tombol update & hapus
|
||||
cart_btn_frm = ttk.Frame(right)
|
||||
cart_btn_frm.pack(fill='x', padx=10, pady=5)
|
||||
ttk.Button(cart_btn_frm, text="🗑️ Hapus Item", command=self.remove_from_cart).pack(side='left', padx=3)
|
||||
ttk.Button(cart_btn_frm, text="📝 Update Qty", command=self.update_cart_qty).pack(side='left', padx=3)
|
||||
|
||||
# Total
|
||||
ttk.Button(cart_btn_frm, text="🗑️ Hapus Item",
|
||||
command=self.remove_from_cart,
|
||||
style='Danger.TButton').pack(side='left', padx=3)
|
||||
ttk.Button(cart_btn_frm, text="📝 Update Qty",
|
||||
command=self.update_cart_qty,
|
||||
style='Primary.TButton').pack(side='left', padx=3)
|
||||
|
||||
# Label total
|
||||
self.cart_total_var = tk.StringVar(value="Total: Rp 0")
|
||||
ttk.Label(right, textvariable=self.cart_total_var, font=("Arial", 12, "bold")).pack(pady=5)
|
||||
tk.Label(right, textvariable=self.cart_total_var,
|
||||
font=("Georgia", 13, "bold"),
|
||||
bg=COLORS['soft_cream'], fg=COLORS['primary'],
|
||||
pady=8).pack(pady=5, fill='x')
|
||||
|
||||
# Form nomor meja
|
||||
meja_frm = ttk.Frame(right)
|
||||
meja_frm = tk.Frame(right, bg=COLORS['soft_cream'], pady=10)
|
||||
meja_frm.pack(fill='x', padx=10, pady=8)
|
||||
ttk.Label(meja_frm, text="Nomor Meja:", font=("Arial", 10)).pack(side='left', padx=5)
|
||||
|
||||
tk.Label(meja_frm, text="📍 Nomor Meja:",
|
||||
font=("Georgia", 10, "bold"),
|
||||
bg=COLORS['soft_cream'], fg=COLORS['dark']).pack(side='left', padx=5)
|
||||
|
||||
self.nomor_meja_var = tk.StringVar()
|
||||
ttk.Entry(meja_frm, textvariable=self.nomor_meja_var, width=10).pack(side='left', padx=5)
|
||||
tk.Entry(meja_frm, textvariable=self.nomor_meja_var,
|
||||
width=10, font=('Arial', 11), justify='center').pack(side='left', padx=5)
|
||||
|
||||
# Tombol pesan
|
||||
ttk.Button(right, text="📋 PESAN SEKARANG", command=self.submit_order,
|
||||
style="Accent.TButton").pack(pady=10, padx=10, fill='x')
|
||||
# Tombol PESAN SEKARANG
|
||||
btn_order = tk.Button(right, text="📋 PESAN SEKARANG",
|
||||
command=self.submit_order,
|
||||
bg=COLORS['warning'], fg=COLORS['dark'],
|
||||
font=('Georgia', 12, 'bold'),
|
||||
relief='flat', cursor='hand2',
|
||||
pady=12,
|
||||
activebackground='#E8B86D')
|
||||
btn_order.pack(pady=10, padx=10, fill='x')
|
||||
|
||||
# Debug info
|
||||
print("🔧 Order tab initialized successfully")
|
||||
|
||||
# Load menu
|
||||
self.reload_order_menu()
|
||||
|
||||
|
||||
def reload_order_menu(self):
|
||||
"""Reload daftar menu untuk order"""
|
||||
if not hasattr(self, 'order_menu_tree'):
|
||||
return
|
||||
|
||||
for item in self.order_menu_tree.get_children():
|
||||
self.order_menu_tree.delete(item)
|
||||
|
||||
search = self.order_search_var.get().strip() if hasattr(self, 'order_search_var') else ""
|
||||
search = ""
|
||||
if hasattr(self, 'order_search_var'):
|
||||
search = self.order_search_var.get().strip()
|
||||
|
||||
menus = menu_list(available_only=True, search_text=search or None)
|
||||
|
||||
for m in menus:
|
||||
mid, nama, kategori, harga, stok, foto, tersedia, disc = m
|
||||
self.order_menu_tree.insert("", tk.END, values=(mid, nama, kategori, f"Rp {harga:,.0f}", stok))
|
||||
|
||||
self.order_menu_tree.insert("", tk.END, values=(
|
||||
mid, nama, kategori, f"Rp {harga:,.0f}", stok
|
||||
))
|
||||
|
||||
def reset_order_search(self):
|
||||
self.order_search_var.set("")
|
||||
"""Reset pencarian menu"""
|
||||
if hasattr(self, 'order_search_var'):
|
||||
self.order_search_var.set("")
|
||||
self.reload_order_menu()
|
||||
|
||||
|
||||
def increase_qty(self):
|
||||
"""Tingkatkan jumlah item"""
|
||||
try:
|
||||
current = int(self.order_qty_var.get())
|
||||
self.order_qty_var.set(str(current + 1))
|
||||
except:
|
||||
self.order_qty_var.set("1")
|
||||
|
||||
def decrease_qty(self):
|
||||
"""Kurangi jumlah item"""
|
||||
try:
|
||||
current = int(self.order_qty_var.get())
|
||||
if current > 1:
|
||||
self.order_qty_var.set(str(current - 1))
|
||||
except:
|
||||
self.order_qty_var.set("1")
|
||||
|
||||
def add_to_cart(self):
|
||||
"""Tambah item ke keranjang"""
|
||||
sel = self.order_menu_tree.selection()
|
||||
@ -1213,20 +1408,40 @@ class App:
|
||||
item_vals = self.order_menu_tree.item(sel)['values']
|
||||
menu_id = item_vals[0]
|
||||
nama = item_vals[1]
|
||||
harga_str = item_vals[3].replace("Rp ", "").replace(",", "")
|
||||
harga = float(harga_str)
|
||||
harga_display = str(item_vals[3])
|
||||
|
||||
# Parse harga - hapus semua kecuali angka
|
||||
harga_clean = ""
|
||||
for char in harga_display:
|
||||
if char.isdigit():
|
||||
harga_clean += char
|
||||
|
||||
try:
|
||||
harga = float(harga_clean)
|
||||
except:
|
||||
messagebox.showerror("Error", f"Format harga salah: {harga_display}")
|
||||
return
|
||||
|
||||
stok = int(item_vals[4])
|
||||
|
||||
if qty > stok:
|
||||
messagebox.showerror("Stok Habis", f"Stok hanya tersedia {stok}")
|
||||
return
|
||||
|
||||
if not hasattr(self, 'cart'):
|
||||
self.cart = []
|
||||
|
||||
# Cek apakah sudah ada di cart
|
||||
found = False
|
||||
for cart_item in self.cart:
|
||||
if cart_item['menu_id'] == menu_id:
|
||||
cart_item['qty'] += qty
|
||||
cart_item['subtotal'] = cart_item['harga'] * cart_item['qty']
|
||||
new_qty = cart_item['qty'] + qty
|
||||
if new_qty > stok:
|
||||
messagebox.showerror("Stok Habis",
|
||||
f"Total pesanan ({new_qty}) melebihi stok ({stok})")
|
||||
return
|
||||
cart_item['qty'] = new_qty
|
||||
cart_item['subtotal'] = cart_item['harga'] * new_qty
|
||||
found = True
|
||||
break
|
||||
|
||||
@ -1241,9 +1456,12 @@ class App:
|
||||
|
||||
self.update_cart_display()
|
||||
messagebox.showinfo("Berhasil", f"✅ {nama} x{qty} ditambahkan ke keranjang!")
|
||||
|
||||
|
||||
def update_cart_display(self):
|
||||
"""Update tampilan keranjang"""
|
||||
if not hasattr(self, 'cart_tree'):
|
||||
return
|
||||
|
||||
for item in self.cart_tree.get_children():
|
||||
self.cart_tree.delete(item)
|
||||
|
||||
@ -1257,8 +1475,9 @@ class App:
|
||||
))
|
||||
total += item['subtotal']
|
||||
|
||||
self.cart_total_var.set(f"Total: Rp {total:,.0f}")
|
||||
|
||||
if hasattr(self, 'cart_total_var'):
|
||||
self.cart_total_var.set(f"Total: Rp {total:,.0f}")
|
||||
|
||||
def remove_from_cart(self):
|
||||
"""Hapus item dari keranjang"""
|
||||
sel = self.cart_tree.selection()
|
||||
@ -1270,7 +1489,7 @@ class App:
|
||||
del self.cart[idx]
|
||||
self.update_cart_display()
|
||||
messagebox.showinfo("Dihapus", "Item berhasil dihapus dari keranjang")
|
||||
|
||||
|
||||
def update_cart_qty(self):
|
||||
"""Update jumlah item di keranjang"""
|
||||
sel = self.cart_tree.selection()
|
||||
@ -1281,20 +1500,72 @@ class App:
|
||||
idx = self.cart_tree.index(sel)
|
||||
item = self.cart[idx]
|
||||
|
||||
# Dialog input qty baru
|
||||
new_qty = tk.simpledialog.askinteger("Update Jumlah",
|
||||
f"Jumlah baru untuk {item['nama']}:",
|
||||
initialvalue=item['qty'],
|
||||
minvalue=1)
|
||||
if new_qty:
|
||||
item['qty'] = new_qty
|
||||
item['subtotal'] = item['harga'] * new_qty
|
||||
self.update_cart_display()
|
||||
|
||||
dialog = tk.Toplevel(self.root)
|
||||
dialog.title("Update Jumlah")
|
||||
dialog.geometry("320x180")
|
||||
dialog.transient(self.root)
|
||||
dialog.grab_set()
|
||||
dialog.configure(bg=COLORS['soft_cream'])
|
||||
|
||||
tk.Label(dialog, text=f"🍽️ {item['nama']}",
|
||||
font=("Georgia", 12, "bold"),
|
||||
bg=COLORS['soft_cream'], fg=COLORS['dark']).pack(pady=15)
|
||||
|
||||
qty_frame = tk.Frame(dialog, bg=COLORS['soft_cream'])
|
||||
qty_frame.pack(pady=15)
|
||||
|
||||
qty_var = tk.StringVar(value=str(item['qty']))
|
||||
|
||||
def decrease():
|
||||
try:
|
||||
val = int(qty_var.get())
|
||||
if val > 1:
|
||||
qty_var.set(str(val - 1))
|
||||
except:
|
||||
pass
|
||||
|
||||
def increase():
|
||||
try:
|
||||
val = int(qty_var.get())
|
||||
qty_var.set(str(val + 1))
|
||||
except:
|
||||
pass
|
||||
|
||||
tk.Button(qty_frame, text="➖", width=3, command=decrease,
|
||||
bg=COLORS['danger'], fg='white',
|
||||
font=('Arial', 11, 'bold'), relief='flat').pack(side='left', padx=5)
|
||||
|
||||
tk.Entry(qty_frame, textvariable=qty_var, width=8,
|
||||
justify='center', font=('Georgia', 12, 'bold')).pack(side='left', padx=5)
|
||||
|
||||
tk.Button(qty_frame, text="➕", width=3, command=increase,
|
||||
bg=COLORS['success'], fg='white',
|
||||
font=('Arial', 11, 'bold'), relief='flat').pack(side='left', padx=5)
|
||||
|
||||
def save():
|
||||
try:
|
||||
new_qty = int(qty_var.get())
|
||||
if new_qty > 0:
|
||||
item['qty'] = new_qty
|
||||
item['subtotal'] = item['harga'] * new_qty
|
||||
self.update_cart_display()
|
||||
dialog.destroy()
|
||||
messagebox.showinfo("Berhasil", f"✅ {item['nama']} diupdate!")
|
||||
else:
|
||||
messagebox.showerror("Error", "Jumlah harus lebih dari 0!")
|
||||
except:
|
||||
messagebox.showerror("Error", "Input salah!")
|
||||
|
||||
tk.Button(dialog, text="✅ Simpan", command=save,
|
||||
bg=COLORS['primary'], fg='white',
|
||||
font=('Georgia', 11, 'bold'),
|
||||
relief='flat', padx=30, pady=8).pack(pady=15)
|
||||
|
||||
def submit_order(self):
|
||||
"""Submit pesanan"""
|
||||
if not self.cart:
|
||||
messagebox.showwarning("Keranjang Kosong", "Tambahkan menu ke keranjang terlebih dahulu!")
|
||||
if not self.cart or len(self.cart) == 0:
|
||||
messagebox.showwarning("Keranjang Kosong",
|
||||
"Tambahkan menu ke keranjang terlebih dahulu!")
|
||||
return
|
||||
|
||||
nomor_meja = self.nomor_meja_var.get().strip()
|
||||
@ -1302,22 +1573,21 @@ class App:
|
||||
messagebox.showwarning("Nomor Meja", "Masukkan nomor meja!")
|
||||
return
|
||||
|
||||
total = sum(i['subtotal'] for i in self.cart)
|
||||
|
||||
try:
|
||||
trans_id = create_transaksi(self.session['id'], nomor_meja, self.cart)
|
||||
|
||||
# Tambah ke favorites
|
||||
for item in self.cart:
|
||||
add_to_favorites(self.session['id'], item['menu_id'])
|
||||
|
||||
messagebox.showinfo("Berhasil",
|
||||
f"✅ Pesanan berhasil dibuat!\n\n"
|
||||
f"ID Transaksi: {trans_id}\n"
|
||||
f"Nomor Meja: {nomor_meja}\n"
|
||||
f"Total: Rp {sum(i['subtotal'] for i in self.cart):,.0f}\n\n"
|
||||
f"Status: Menunggu\n"
|
||||
f"Pesanan Anda akan segera diproses oleh waiter!")
|
||||
f"✅ Pesanan berhasil dibuat!\n\n"
|
||||
f"🆔 ID Transaksi: {trans_id}\n"
|
||||
f"📍 Nomor Meja: {nomor_meja}\n"
|
||||
f"💰 Total: Rp {total:,.0f}\n\n"
|
||||
f"⏳ Status: Menunggu")
|
||||
|
||||
# Reset
|
||||
self.cart = []
|
||||
self.nomor_meja_var.set("")
|
||||
self.order_qty_var.set("1")
|
||||
@ -1325,7 +1595,7 @@ class App:
|
||||
self.reload_order_menu()
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Gagal membuat pesanan: {e}")
|
||||
messagebox.showerror("Error", f"Gagal membuat pesanan:\n{str(e)}")
|
||||
|
||||
# ========== TAB: WAITER DASHBOARD ==========
|
||||
def build_waiter_tab(self, parent):
|
||||
@ -1333,8 +1603,8 @@ class App:
|
||||
for w in parent.winfo_children():
|
||||
w.destroy()
|
||||
|
||||
ttk.Label(parent, text="📋 Dashboard Waiter - Kelola Pesanan",
|
||||
font=("Arial", 16, "bold")).pack(pady=10)
|
||||
ttk.Label(parent, text="🐾 Totoro's Kitchen - Kelola Pesanan",
|
||||
font=("Georgia", 16, "bold")).pack(pady=10)
|
||||
|
||||
# Filter status
|
||||
filter_frm = ttk.Frame(parent)
|
||||
@ -1450,11 +1720,11 @@ class App:
|
||||
for w in parent.winfo_children():
|
||||
w.destroy()
|
||||
|
||||
ttk.Label(parent, text="⭐ Menu Favorit Saya",
|
||||
font=("Arial", 16, "bold")).pack(pady=10)
|
||||
|
||||
ttk.Label(parent, text="Menu yang paling sering Anda pesan",
|
||||
font=("Arial", 10)).pack(pady=5)
|
||||
ttk.Label(parent, text="⭐ Menu Favorit Totoro",
|
||||
font=("Georgia", 16, "bold")).pack(pady=10)
|
||||
|
||||
ttk.Label(parent, text="🍃 Menu yang paling sering Anda pesan dari hutan kami",
|
||||
font=("Georgia", 10, "italic")).pack(pady=5)
|
||||
|
||||
# Treeview favorites
|
||||
cols = ("Nama Menu", "Kategori", "Harga", "Sering Dipesan")
|
||||
@ -1482,25 +1752,33 @@ class App:
|
||||
"""Reload menu favorit user"""
|
||||
for item in self.fav_tree.get_children():
|
||||
self.fav_tree.delete(item)
|
||||
|
||||
favs = get_user_favorites(self.session['id'], limit=10)
|
||||
|
||||
if not favs:
|
||||
self.fav_tree.insert("", tk.END, values=(
|
||||
"Belum ada menu favorit", "-", "-", "0"
|
||||
))
|
||||
return
|
||||
|
||||
for fav in favs:
|
||||
menu = fav['menu']
|
||||
mid, nama, kategori, harga, stok, foto, tersedia, disc = menu
|
||||
self.fav_tree.insert("", tk.END, values=(
|
||||
nama,
|
||||
kategori,
|
||||
f"Rp {harga:,.0f}",
|
||||
f"{fav['count']}x"
|
||||
), tags=(str(mid),))
|
||||
|
||||
try:
|
||||
favs = get_user_favorites(self.session['id'], limit=10)
|
||||
|
||||
if not favs:
|
||||
# Tampilkan pesan kosong
|
||||
item_id = self.fav_tree.insert("", tk.END, values=(
|
||||
"🍃 Belum ada menu favorit. Pesan sekarang!", "-", "-", "0"
|
||||
))
|
||||
self.fav_tree.item(item_id, tags=('empty',))
|
||||
return
|
||||
|
||||
for fav in favs:
|
||||
menu = fav['menu']
|
||||
mid, nama, kategori, harga, stok, foto, tersedia, disc = menu
|
||||
self.fav_tree.insert("", tk.END, values=(
|
||||
f"⭐ {nama}",
|
||||
kategori,
|
||||
f"Rp {harga:,.0f}",
|
||||
f"🔥 {fav['count']}x"
|
||||
), tags=(str(mid),))
|
||||
except Exception as e:
|
||||
print(f"Error reload favorites: {e}")
|
||||
self.fav_tree.insert("", tk.END, values=(
|
||||
"❌ Error memuat favorit", "-", "-", "0"
|
||||
))
|
||||
|
||||
def quick_order_from_fav(self):
|
||||
"""Pesan langsung dari menu favorit"""
|
||||
sel = self.fav_tree.selection()
|
||||
@ -1508,10 +1786,14 @@ class App:
|
||||
messagebox.showwarning("Pilih Menu", "Pilih menu favorit terlebih dahulu!")
|
||||
return
|
||||
|
||||
item_vals = self.fav_tree.item(sel)['values']
|
||||
if item_vals[0] == "Belum ada menu favorit":
|
||||
# Cek jika kosong
|
||||
tags = self.fav_tree.item(sel)['tags']
|
||||
if 'empty' in tags:
|
||||
messagebox.showinfo("Info", "Belum ada menu favorit. Silakan pesan menu terlebih dahulu!")
|
||||
return
|
||||
|
||||
item_vals = self.fav_tree.item(sel)['values']
|
||||
|
||||
# Ambil menu_id dari tags
|
||||
menu_id = int(self.fav_tree.item(sel)['tags'][0])
|
||||
menu = menu_get(menu_id)
|
||||
@ -1568,62 +1850,6 @@ class App:
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Gagal membuat pesanan: {e}")
|
||||
|
||||
# ========== UPDATE DASHBOARD FRAME ==========
|
||||
# GANTI fungsi dashboard_frame yang lama dengan ini:
|
||||
|
||||
def dashboard_frame(self):
|
||||
for w in self.root.winfo_children():
|
||||
w.destroy()
|
||||
|
||||
# Header
|
||||
top = ttk.Frame(self.root)
|
||||
top.pack(fill='x')
|
||||
ttk.Label(top, text=f"👤 User: {self.session['username']} | Role: {self.session['role'].upper()}",
|
||||
font=("Arial", 12, "bold")).pack(side='left', padx=15, pady=8)
|
||||
ttk.Button(top, text="🚪 Logout", command=self.logout).pack(side='right', padx=15)
|
||||
|
||||
# Notebook tabs
|
||||
main = ttk.Notebook(self.root)
|
||||
main.pack(fill='both', expand=True, padx=10, pady=10)
|
||||
|
||||
role = self.session['role']
|
||||
|
||||
# Setup tabs berdasarkan role
|
||||
if role == 'pembeli':
|
||||
self.tab_order = ttk.Frame(main)
|
||||
self.tab_favorites = ttk.Frame(main)
|
||||
main.add(self.tab_order, text="🛒 Pesan Menu")
|
||||
main.add(self.tab_favorites, text="⭐ Menu Favorit")
|
||||
self.build_order_tab(self.tab_order)
|
||||
self.build_favorites_tab(self.tab_favorites)
|
||||
|
||||
elif role == 'waiter':
|
||||
self.tab_waiter = ttk.Frame(main)
|
||||
main.add(self.tab_waiter, text="📋 Dashboard Waiter")
|
||||
self.build_waiter_tab(self.tab_waiter)
|
||||
|
||||
elif role == 'admin':
|
||||
self.tab_menu_view = ttk.Frame(main)
|
||||
self.tab_menu_manage = ttk.Frame(main)
|
||||
self.tab_promo = ttk.Frame(main)
|
||||
self.tab_waiter = ttk.Frame(main)
|
||||
main.add(self.tab_menu_view, text="📖 Menu - View")
|
||||
main.add(self.tab_menu_manage, text="⚙️ Menu - Manage")
|
||||
main.add(self.tab_promo, text="🎁 Promo - Manage")
|
||||
main.add(self.tab_waiter, text="📋 Dashboard Waiter")
|
||||
self.build_menu_view_tab(self.tab_menu_view)
|
||||
self.build_menu_manage_tab(self.tab_menu_manage)
|
||||
self.build_promo_tab(self.tab_promo)
|
||||
self.build_waiter_tab(self.tab_waiter)
|
||||
|
||||
else: # kasir, owner
|
||||
self.tab_menu_view = ttk.Frame(main)
|
||||
main.add(self.tab_menu_view, text="📖 Menu - View")
|
||||
self.build_menu_view_tab(self.tab_menu_view)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Done
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user