Manajemen Meja
This commit is contained in:
parent
17dd7a3d0c
commit
ddac4df06b
@ -5,3 +5,8 @@ id,transaksi_id,menu_id,qty,harga_satuan,subtotal_item
|
|||||||
4,5,2,1,25000.0,25000.0
|
4,5,2,1,25000.0,25000.0
|
||||||
5,5,4,1,25000.0,25000.0
|
5,5,4,1,25000.0,25000.0
|
||||||
6,5,6,1,19000.0,19000.0
|
6,5,6,1,19000.0,19000.0
|
||||||
|
7,6,2,1,25000.0,25000.0
|
||||||
|
8,6,4,1,25000.0,25000.0
|
||||||
|
9,6,3,1,30000.0,30000.0
|
||||||
|
10,7,4,2,25000.0,50000.0
|
||||||
|
11,7,3,2,30000.0,60000.0
|
||||||
|
|||||||
|
@ -3,3 +3,6 @@ user_id,menu_id,order_count,last_ordered
|
|||||||
1,2,1,2025-12-13 20:12:35
|
1,2,1,2025-12-13 20:12:35
|
||||||
1,4,1,2025-12-13 20:12:35
|
1,4,1,2025-12-13 20:12:35
|
||||||
1,6,1,2025-12-13 20:12:35
|
1,6,1,2025-12-13 20:12:35
|
||||||
|
4,2,1,2025-12-13 22:18:23
|
||||||
|
4,4,2,2025-12-13 22:22:02
|
||||||
|
4,3,2,2025-12-13 22:22:02
|
||||||
|
|||||||
|
539
main.py
539
main.py
@ -818,6 +818,7 @@ class App:
|
|||||||
def logout(self):
|
def logout(self):
|
||||||
self.session = None
|
self.session = None
|
||||||
self.img_cache.clear()
|
self.img_cache.clear()
|
||||||
|
self.notification_running = False
|
||||||
self.login_frame()
|
self.login_frame()
|
||||||
|
|
||||||
def dashboard_frame(self):
|
def dashboard_frame(self):
|
||||||
@ -860,6 +861,9 @@ class App:
|
|||||||
if self.session['role'] == 'waiter':
|
if self.session['role'] == 'waiter':
|
||||||
main.add(self.tab_waiter, text="🍽️ Kelola Pesanan")
|
main.add(self.tab_waiter, text="🍽️ Kelola Pesanan")
|
||||||
|
|
||||||
|
self.tab_meja = ttk.Frame(main)
|
||||||
|
main.add(self.tab_meja, text="🪑 Kelola Meja")
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# ROLE: KASIR (Order + Transaksi SAJA)
|
# ROLE: KASIR (Order + Transaksi SAJA)
|
||||||
# ==========================================
|
# ==========================================
|
||||||
@ -867,6 +871,9 @@ class App:
|
|||||||
self.tab_payment = ttk.Frame(main)
|
self.tab_payment = ttk.Frame(main)
|
||||||
main.add(self.tab_payment, text="💰 Transaksi")
|
main.add(self.tab_payment, text="💰 Transaksi")
|
||||||
|
|
||||||
|
self.tab_meja = ttk.Frame(main)
|
||||||
|
main.add(self.tab_meja, text="🪑 Kelola Meja")
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# ROLE: PEMILIK (Laporan SAJA)
|
# ROLE: PEMILIK (Laporan SAJA)
|
||||||
# ==========================================
|
# ==========================================
|
||||||
@ -874,6 +881,7 @@ class App:
|
|||||||
self.tab_report = ttk.Frame(main)
|
self.tab_report = ttk.Frame(main)
|
||||||
main.add(self.tab_report, text="📊 Laporan")
|
main.add(self.tab_report, text="📊 Laporan")
|
||||||
|
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# ROLE: ADMIN (Kelola Semua)
|
# ROLE: ADMIN (Kelola Semua)
|
||||||
# ==========================================
|
# ==========================================
|
||||||
@ -889,6 +897,9 @@ class App:
|
|||||||
# Laporan (akses pemilik)
|
# Laporan (akses pemilik)
|
||||||
self.tab_report = ttk.Frame(main)
|
self.tab_report = ttk.Frame(main)
|
||||||
main.add(self.tab_report, text="📊 Laporan")
|
main.add(self.tab_report, text="📊 Laporan")
|
||||||
|
|
||||||
|
self.tab_meja = ttk.Frame(main)
|
||||||
|
main.add(self.tab_meja, text="🪑 Kelola Meja")
|
||||||
|
|
||||||
# Kelola Menu & Promo (KHUSUS ADMIN)
|
# Kelola Menu & Promo (KHUSUS ADMIN)
|
||||||
main.add(self.tab_menu_manage, text="⚙️ Kelola Menu")
|
main.add(self.tab_menu_manage, text="⚙️ Kelola Menu")
|
||||||
@ -914,11 +925,13 @@ class App:
|
|||||||
# Waiter
|
# Waiter
|
||||||
if self.session['role'] == 'waiter':
|
if self.session['role'] == 'waiter':
|
||||||
self.build_waiter_tab(self.tab_waiter)
|
self.build_waiter_tab(self.tab_waiter)
|
||||||
|
self.build_meja_tab(self.tab_meja)
|
||||||
|
|
||||||
# Kasir (Order + Transaksi SAJA)
|
# Kasir (Order + Transaksi SAJA)
|
||||||
if self.session['role'] == 'kasir':
|
if self.session['role'] == 'kasir':
|
||||||
self.build_order_tab(self.tab_order)
|
self.build_order_tab(self.tab_order)
|
||||||
self.build_payment_tab(self.tab_payment)
|
self.build_payment_tab(self.tab_payment)
|
||||||
|
self.build_meja_tab(self.tab_meja)
|
||||||
|
|
||||||
# Pemilik (Laporan SAJA)
|
# Pemilik (Laporan SAJA)
|
||||||
if self.session['role'] == 'pemilik':
|
if self.session['role'] == 'pemilik':
|
||||||
@ -933,6 +946,7 @@ class App:
|
|||||||
self.build_menu_manage_tab(self.tab_menu_manage)
|
self.build_menu_manage_tab(self.tab_menu_manage)
|
||||||
self.build_promo_tab(self.tab_promo)
|
self.build_promo_tab(self.tab_promo)
|
||||||
self.build_user_manage_tab(self.tab_user_manage) # TAMBAHAN
|
self.build_user_manage_tab(self.tab_user_manage) # TAMBAHAN
|
||||||
|
self.build_meja_tab(self.tab_meja)
|
||||||
|
|
||||||
|
|
||||||
def build_menu_view_tab(self, parent):
|
def build_menu_view_tab(self, parent):
|
||||||
@ -1929,6 +1943,7 @@ class App:
|
|||||||
success, result = transaksi_add(self.session['id'], nomor_meja, self.cart_items, promo_code)
|
success, result = transaksi_add(self.session['id'], nomor_meja, self.cart_items, promo_code)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
|
meja_update_status(nomor_meja, "terisi", result)
|
||||||
messagebox.showinfo("Sukses", f"Pesanan berhasil! ID Transaksi: {result}\nStatus: Pending")
|
messagebox.showinfo("Sukses", f"Pesanan berhasil! ID Transaksi: {result}\nStatus: Pending")
|
||||||
# Reset
|
# Reset
|
||||||
self.cart_items = []
|
self.cart_items = []
|
||||||
@ -3001,83 +3016,88 @@ class App:
|
|||||||
|
|
||||||
|
|
||||||
def show_report_chart(self):
|
def show_report_chart(self):
|
||||||
"""Tampilkan grafik laporan menggunakan matplotlib"""
|
"""Tampilkan grafik laporan (dengan fallback jika matplotlib tidak ada)"""
|
||||||
import matplotlib.pyplot as plt
|
try:
|
||||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
import matplotlib.pyplot as plt
|
||||||
from datetime import datetime
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||||
|
from datetime import datetime
|
||||||
# Get data from tree
|
|
||||||
if not self.report_tree.get_children():
|
|
||||||
messagebox.showwarning("Tidak Ada Data", "Generate laporan terlebih dahulu")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Collect data
|
|
||||||
metode_counts = {}
|
|
||||||
total_per_day = {}
|
|
||||||
|
|
||||||
for item_id in self.report_tree.get_children():
|
|
||||||
values = self.report_tree.item(item_id)['values']
|
|
||||||
tid, tanggal, meja, total_str, metode, status = values
|
|
||||||
|
|
||||||
# Parse total
|
# Get data from tree
|
||||||
total = float(total_str.replace('Rp ', '').replace(',', '').replace('.', ''))
|
if not self.report_tree.get_children():
|
||||||
|
messagebox.showwarning("Tidak Ada Data", "Generate laporan terlebih dahulu")
|
||||||
|
return
|
||||||
|
|
||||||
# Count by metode
|
# Collect data
|
||||||
metode_counts[metode] = metode_counts.get(metode, 0) + 1
|
metode_counts = {}
|
||||||
|
total_per_day = {}
|
||||||
|
|
||||||
# Sum by date
|
for item_id in self.report_tree.get_children():
|
||||||
try:
|
values = self.report_tree.item(item_id)['values']
|
||||||
date_obj = datetime.strptime(tanggal, "%Y-%m-%d %H:%M:%S")
|
tid, tanggal, meja, total_str, metode, status = values
|
||||||
date_key = date_obj.strftime("%Y-%m-%d")
|
|
||||||
total_per_day[date_key] = total_per_day.get(date_key, 0) + total
|
# Parse total
|
||||||
except:
|
total = float(total_str.replace('Rp ', '').replace(',', '').replace('.', ''))
|
||||||
pass
|
|
||||||
|
# Count by metode
|
||||||
# Create window
|
metode_counts[metode] = metode_counts.get(metode, 0) + 1
|
||||||
chart_window = tk.Toplevel(self.root)
|
|
||||||
chart_window.title("📊 Grafik Laporan Penjualan")
|
# Sum by date
|
||||||
chart_window.geometry("900x600")
|
try:
|
||||||
|
date_obj = datetime.strptime(tanggal, "%Y-%m-%d %H:%M:%S")
|
||||||
# Create figure with 2 subplots
|
date_key = date_obj.strftime("%Y-%m-%d")
|
||||||
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
|
total_per_day[date_key] = total_per_day.get(date_key, 0) + total
|
||||||
|
except:
|
||||||
# Chart 1: Pie chart metode pembayaran
|
pass
|
||||||
if metode_counts:
|
|
||||||
labels = list(metode_counts.keys())
|
|
||||||
sizes = list(metode_counts.values())
|
|
||||||
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8']
|
|
||||||
|
|
||||||
ax1.pie(sizes, labels=labels, autopct='%1.1f%%', colors=colors, startangle=90)
|
# Create window
|
||||||
ax1.set_title('Transaksi per Metode Pembayaran', fontsize=12, fontweight='bold')
|
chart_window = tk.Toplevel(self.root)
|
||||||
|
chart_window.title("📊 Grafik Laporan Penjualan")
|
||||||
# Chart 2: Bar chart pendapatan per hari
|
chart_window.geometry("900x600")
|
||||||
if total_per_day:
|
|
||||||
dates = sorted(total_per_day.keys())
|
|
||||||
totals = [total_per_day[d] for d in dates]
|
|
||||||
|
|
||||||
date_labels = [
|
# Create figure with 2 subplots
|
||||||
datetime.strptime(d, "%Y-%m-%d").strftime("%d/%m")
|
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
|
||||||
for d in dates
|
|
||||||
]
|
|
||||||
|
|
||||||
ax2.bar(date_labels, totals, color='#4ECDC4')
|
# Chart 1: Pie chart metode pembayaran
|
||||||
ax2.set_xlabel('Tanggal', fontweight='bold')
|
if metode_counts:
|
||||||
ax2.set_ylabel('Pendapatan (Rp)', fontweight='bold')
|
labels = list(metode_counts.keys())
|
||||||
ax2.set_title('Pendapatan Harian', fontsize=12, fontweight='bold')
|
sizes = list(metode_counts.values())
|
||||||
ax2.tick_params(axis='x', rotation=45)
|
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8']
|
||||||
|
|
||||||
|
ax1.pie(sizes, labels=labels, autopct='%1.1f%%', colors=colors, startangle=90)
|
||||||
|
ax1.set_title('Transaksi per Metode Pembayaran', fontsize=12, fontweight='bold')
|
||||||
|
|
||||||
ax2.yaxis.set_major_formatter(
|
# Chart 2: Bar chart pendapatan per hari
|
||||||
plt.FuncFormatter(lambda x, p: f'Rp {x/1000:.0f}K')
|
if total_per_day:
|
||||||
)
|
dates = sorted(total_per_day.keys())
|
||||||
|
totals = [total_per_day[d] for d in dates]
|
||||||
plt.tight_layout()
|
|
||||||
|
date_labels = [
|
||||||
# Embed in Tkinter
|
datetime.strptime(d, "%Y-%m-%d").strftime("%d/%m")
|
||||||
canvas = FigureCanvasTkAgg(fig, master=chart_window)
|
for d in dates
|
||||||
canvas.draw()
|
]
|
||||||
canvas.get_tk_widget().pack(fill='both', expand=True)
|
|
||||||
|
ax2.bar(date_labels, totals, color='#4ECDC4')
|
||||||
ttk.Button(chart_window, text="Tutup", command=chart_window.destroy).pack(pady=10)
|
ax2.set_xlabel('Tanggal', fontweight='bold')
|
||||||
|
ax2.set_ylabel('Pendapatan (Rp)', fontweight='bold')
|
||||||
|
ax2.set_title('Pendapatan Harian', fontsize=12, fontweight='bold')
|
||||||
|
ax2.tick_params(axis='x', rotation=45)
|
||||||
|
|
||||||
|
ax2.yaxis.set_major_formatter(
|
||||||
|
plt.FuncFormatter(lambda x, p: f'Rp {x/1000:.0f}K')
|
||||||
|
)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
|
||||||
|
# Embed in Tkinter
|
||||||
|
canvas = FigureCanvasTkAgg(fig, master=chart_window)
|
||||||
|
canvas.draw()
|
||||||
|
canvas.get_tk_widget().pack(fill='both', expand=True)
|
||||||
|
|
||||||
|
ttk.Button(chart_window, text="Tutup", command=chart_window.destroy).pack(pady=10)
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
# Fallback: Tampilkan grafik ASCII sederhana
|
||||||
|
self.show_text_chart()
|
||||||
|
|
||||||
|
|
||||||
def reload_payment_orders(self):
|
def reload_payment_orders(self):
|
||||||
@ -3489,6 +3509,385 @@ class App:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
messagebox.showerror("Error", f"Gagal menyimpan struk: {e}")
|
messagebox.showerror("Error", f"Gagal menyimpan struk: {e}")
|
||||||
|
|
||||||
|
def check_new_orders(self):
|
||||||
|
"""Cek pesanan baru yang perlu perhatian waiter/kasir"""
|
||||||
|
if self.session['role'] not in ['waiter', 'kasir', 'admin']:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Hitung pesanan pending untuk waiter
|
||||||
|
if self.session['role'] in ['waiter', 'admin']:
|
||||||
|
pending_orders = transaksi_list(status='pending')
|
||||||
|
return len(pending_orders)
|
||||||
|
|
||||||
|
# Hitung pesanan selesai untuk kasir
|
||||||
|
if self.session['role'] in ['kasir', 'admin']:
|
||||||
|
selesai_orders = transaksi_list(status='selesai')
|
||||||
|
# Filter yang belum dibayar
|
||||||
|
unpaid_count = 0
|
||||||
|
for order in selesai_orders:
|
||||||
|
tid = order[0]
|
||||||
|
payment = pembayaran_get_by_transaksi(tid)
|
||||||
|
if not payment:
|
||||||
|
unpaid_count += 1
|
||||||
|
return unpaid_count
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def start_notification_check(self):
|
||||||
|
"""Start auto-refresh untuk notifikasi (setiap 10 detik)"""
|
||||||
|
if not hasattr(self, 'notification_running'):
|
||||||
|
self.notification_running = True
|
||||||
|
self.update_notification_badge()
|
||||||
|
|
||||||
|
|
||||||
|
def update_notification_badge(self):
|
||||||
|
"""Update badge notifikasi"""
|
||||||
|
if not self.notification_running:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
count = self.check_new_orders()
|
||||||
|
|
||||||
|
# Update badge di tab yang sesuai
|
||||||
|
if self.session['role'] in ['waiter', 'admin']:
|
||||||
|
if hasattr(self, 'tab_waiter'):
|
||||||
|
for tab_id in range(self.root.nametowidget('.!notebook').index('end')):
|
||||||
|
tab_text = self.root.nametowidget('.!notebook').tab(tab_id, 'text')
|
||||||
|
if 'Kelola Pesanan' in tab_text:
|
||||||
|
if count > 0:
|
||||||
|
self.root.nametowidget('.!notebook').tab(tab_id, text=f"🍽️ Kelola Pesanan ({count})")
|
||||||
|
else:
|
||||||
|
self.root.nametowidget('.!notebook').tab(tab_id, text="🍽️ Kelola Pesanan")
|
||||||
|
break
|
||||||
|
|
||||||
|
if self.session['role'] in ['kasir', 'admin']:
|
||||||
|
if hasattr(self, 'tab_payment'):
|
||||||
|
for tab_id in range(self.root.nametowidget('.!notebook').index('end')):
|
||||||
|
tab_text = self.root.nametowidget('.!notebook').tab(tab_id, 'text')
|
||||||
|
if 'Transaksi' in tab_text:
|
||||||
|
if count > 0:
|
||||||
|
self.root.nametowidget('.!notebook').tab(tab_id, text=f"💰 Transaksi ({count})")
|
||||||
|
else:
|
||||||
|
self.root.nametowidget('.!notebook').tab(tab_id, text="💰 Transaksi")
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Schedule next check (10 seconds)
|
||||||
|
if self.notification_running:
|
||||||
|
self.root.after(10000, self.update_notification_badge)
|
||||||
|
|
||||||
|
|
||||||
|
def show_text_chart(self):
|
||||||
|
"""Tampilkan grafik ASCII sebagai fallback jika matplotlib tidak ada"""
|
||||||
|
# Get data from tree
|
||||||
|
if not self.report_tree.get_children():
|
||||||
|
messagebox.showwarning("Tidak Ada Data", "Generate laporan terlebih dahulu")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Collect data
|
||||||
|
metode_counts = {}
|
||||||
|
|
||||||
|
for item_id in self.report_tree.get_children():
|
||||||
|
values = self.report_tree.item(item_id)['values']
|
||||||
|
tid, tanggal, meja, total_str, metode, status = values
|
||||||
|
|
||||||
|
# Count by metode
|
||||||
|
metode_counts[metode] = metode_counts.get(metode, 0) + 1
|
||||||
|
|
||||||
|
# Create ASCII bar chart
|
||||||
|
chart_text = "=" * 60 + "\n"
|
||||||
|
chart_text += "GRAFIK TRANSAKSI PER METODE PEMBAYARAN\n"
|
||||||
|
chart_text += "=" * 60 + "\n\n"
|
||||||
|
|
||||||
|
if metode_counts:
|
||||||
|
max_count = max(metode_counts.values())
|
||||||
|
|
||||||
|
for metode, count in sorted(metode_counts.items()):
|
||||||
|
# Calculate bar length (max 40 chars)
|
||||||
|
bar_length = int((count / max_count) * 40) if max_count > 0 else 0
|
||||||
|
bar = "█" * bar_length
|
||||||
|
|
||||||
|
percentage = (count / sum(metode_counts.values())) * 100 if sum(metode_counts.values()) > 0 else 0
|
||||||
|
|
||||||
|
chart_text += f"{metode.ljust(15)} | {bar} {count} ({percentage:.1f}%)\n"
|
||||||
|
|
||||||
|
chart_text += "\n" + "=" * 60 + "\n"
|
||||||
|
chart_text += f"Total Transaksi: {sum(metode_counts.values())}\n"
|
||||||
|
chart_text += "=" * 60
|
||||||
|
|
||||||
|
# Show in window
|
||||||
|
w = tk.Toplevel(self.root)
|
||||||
|
w.title("📊 Grafik Laporan (Text Mode)")
|
||||||
|
w.geometry("700x500")
|
||||||
|
|
||||||
|
frm = ttk.Frame(w, padding=15)
|
||||||
|
frm.pack(fill='both', expand=True)
|
||||||
|
|
||||||
|
ttk.Label(frm, text="ℹ️ Matplotlib tidak terinstall - Mode Text Chart", font=("Arial", 10), foreground='orange').pack(pady=10)
|
||||||
|
|
||||||
|
text = tk.Text(frm, width=80, height=25, font=("Courier New", 10))
|
||||||
|
text_scroll = ttk.Scrollbar(frm, orient='vertical', command=text.yview)
|
||||||
|
text.configure(yscrollcommand=text_scroll.set)
|
||||||
|
|
||||||
|
text.insert('1.0', chart_text)
|
||||||
|
text.config(state='disabled')
|
||||||
|
|
||||||
|
text.pack(side='left', fill='both', expand=True)
|
||||||
|
text_scroll.pack(side='right', fill='y')
|
||||||
|
|
||||||
|
ttk.Button(w, text="Tutup", command=w.destroy).pack(pady=10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# MEJA
|
||||||
|
|
||||||
|
def build_meja_tab(self, parent):
|
||||||
|
"""Tab untuk kelola status meja (admin/kasir/waiter)"""
|
||||||
|
for w in parent.winfo_children():
|
||||||
|
w.destroy()
|
||||||
|
|
||||||
|
# Header
|
||||||
|
header = ttk.Frame(parent)
|
||||||
|
header.pack(fill='x', padx=10, pady=8)
|
||||||
|
ttk.Label(header, text="🪑 Manajemen Meja Cafe", font=("Arial", 14, "bold")).pack(side='left')
|
||||||
|
ttk.Button(header, text="🔄 Refresh", command=self.reload_meja_status).pack(side='right', padx=6)
|
||||||
|
|
||||||
|
# Info panel
|
||||||
|
info_frame = ttk.LabelFrame(parent, text="ℹ️ Info Meja", padding=10)
|
||||||
|
info_frame.pack(fill='x', padx=10, pady=6)
|
||||||
|
|
||||||
|
info_inner = ttk.Frame(info_frame)
|
||||||
|
info_inner.pack()
|
||||||
|
|
||||||
|
ttk.Label(info_inner, text="🟢 Kosong:", font=("Arial", 10)).grid(row=0, column=0, padx=15, pady=3)
|
||||||
|
self.meja_kosong_label = ttk.Label(info_inner, text="0", font=("Arial", 10, "bold"), foreground='green')
|
||||||
|
self.meja_kosong_label.grid(row=0, column=1, padx=5, pady=3)
|
||||||
|
|
||||||
|
ttk.Label(info_inner, text="🔴 Terisi:", font=("Arial", 10)).grid(row=0, column=2, padx=15, pady=3)
|
||||||
|
self.meja_terisi_label = ttk.Label(info_inner, text="0", font=("Arial", 10, "bold"), foreground='red')
|
||||||
|
self.meja_terisi_label.grid(row=0, column=3, padx=5, pady=3)
|
||||||
|
|
||||||
|
# Container untuk card meja
|
||||||
|
canvas_frame = ttk.Frame(parent)
|
||||||
|
canvas_frame.pack(fill='both', expand=True, padx=10, pady=6)
|
||||||
|
|
||||||
|
# Canvas dengan scrollbar
|
||||||
|
canvas = tk.Canvas(canvas_frame, bg='#f5f5f5', highlightthickness=0)
|
||||||
|
scrollbar = ttk.Scrollbar(canvas_frame, orient="vertical", command=canvas.yview)
|
||||||
|
|
||||||
|
self.meja_cards_frame = ttk.Frame(canvas)
|
||||||
|
|
||||||
|
self.meja_cards_frame.bind(
|
||||||
|
"<Configure>",
|
||||||
|
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
|
||||||
|
)
|
||||||
|
|
||||||
|
canvas.create_window((0, 0), window=self.meja_cards_frame, anchor="nw")
|
||||||
|
canvas.configure(yscrollcommand=scrollbar.set)
|
||||||
|
|
||||||
|
canvas.pack(side="left", fill="both", expand=True)
|
||||||
|
scrollbar.pack(side="right", fill="y")
|
||||||
|
|
||||||
|
# Mouse wheel scroll
|
||||||
|
def _on_mousewheel(event):
|
||||||
|
canvas.yview_scroll(int(-1*(event.delta/120)), "units")
|
||||||
|
canvas.bind_all("<MouseWheel>", _on_mousewheel)
|
||||||
|
|
||||||
|
# Load meja cards
|
||||||
|
self.reload_meja_status()
|
||||||
|
|
||||||
|
|
||||||
|
def reload_meja_status(self):
|
||||||
|
"""Load semua meja dalam bentuk cards"""
|
||||||
|
# Clear existing cards
|
||||||
|
for widget in self.meja_cards_frame.winfo_children():
|
||||||
|
widget.destroy()
|
||||||
|
|
||||||
|
# Get all meja data
|
||||||
|
meja_list = read_all(MEJA_CSV)
|
||||||
|
|
||||||
|
# Hitung statistik
|
||||||
|
kosong_count = sum(1 for m in meja_list if m.get('status') == 'kosong')
|
||||||
|
terisi_count = sum(1 for m in meja_list if m.get('status') == 'terisi')
|
||||||
|
|
||||||
|
# Update info labels
|
||||||
|
self.meja_kosong_label.config(text=str(kosong_count))
|
||||||
|
self.meja_terisi_label.config(text=str(terisi_count))
|
||||||
|
|
||||||
|
# Render meja cards (5 kolom)
|
||||||
|
row = 0
|
||||||
|
col = 0
|
||||||
|
|
||||||
|
for meja in sorted(meja_list, key=lambda x: int(x.get('nomor_meja', 0))):
|
||||||
|
nomor = meja.get('nomor_meja')
|
||||||
|
status = meja.get('status', 'kosong')
|
||||||
|
transaksi_id = meja.get('transaksi_id', '')
|
||||||
|
|
||||||
|
# Determine color
|
||||||
|
if status == 'kosong':
|
||||||
|
bg_color = '#C8E6C9' # Light green
|
||||||
|
status_color = '#4CAF50'
|
||||||
|
status_text = '🟢 KOSONG'
|
||||||
|
else:
|
||||||
|
bg_color = '#FFCDD2' # Light red
|
||||||
|
status_color = '#F44336'
|
||||||
|
status_text = '🔴 TERISI'
|
||||||
|
|
||||||
|
# Create card
|
||||||
|
card = tk.Frame(
|
||||||
|
self.meja_cards_frame,
|
||||||
|
relief='solid',
|
||||||
|
borderwidth=2,
|
||||||
|
bg=bg_color,
|
||||||
|
padx=15,
|
||||||
|
pady=15
|
||||||
|
)
|
||||||
|
card.grid(row=row, column=col, padx=8, pady=8, sticky='nsew')
|
||||||
|
|
||||||
|
# Nomor meja (besar)
|
||||||
|
tk.Label(
|
||||||
|
card,
|
||||||
|
text=f"MEJA {nomor}",
|
||||||
|
font=("Arial", 16, "bold"),
|
||||||
|
bg=bg_color
|
||||||
|
).pack(pady=(0, 5))
|
||||||
|
|
||||||
|
# Status
|
||||||
|
tk.Label(
|
||||||
|
card,
|
||||||
|
text=status_text,
|
||||||
|
font=("Arial", 10, "bold"),
|
||||||
|
fg=status_color,
|
||||||
|
bg=bg_color
|
||||||
|
).pack(pady=5)
|
||||||
|
|
||||||
|
# Info transaksi (jika terisi)
|
||||||
|
if status == 'terisi' and transaksi_id:
|
||||||
|
tk.Label(
|
||||||
|
card,
|
||||||
|
text=f"Transaksi: #{transaksi_id}",
|
||||||
|
font=("Arial", 8),
|
||||||
|
bg=bg_color
|
||||||
|
).pack(pady=2)
|
||||||
|
|
||||||
|
# Get detail transaksi
|
||||||
|
transaksi_data = transaksi_get(int(transaksi_id))
|
||||||
|
if transaksi_data:
|
||||||
|
tid, uid, meja_num, total, status_trx, promo, subtotal, item_disc, promo_disc, tanggal = transaksi_data
|
||||||
|
|
||||||
|
tk.Label(
|
||||||
|
card,
|
||||||
|
text=f"Total: Rp {total:,.0f}",
|
||||||
|
font=("Arial", 8, "bold"),
|
||||||
|
bg=bg_color,
|
||||||
|
fg='#1976D2'
|
||||||
|
).pack(pady=2)
|
||||||
|
|
||||||
|
tk.Label(
|
||||||
|
card,
|
||||||
|
text=f"Status: {status_trx.upper()}",
|
||||||
|
font=("Arial", 7),
|
||||||
|
bg=bg_color
|
||||||
|
).pack(pady=2)
|
||||||
|
|
||||||
|
# Tombol aksi
|
||||||
|
btn_frame = tk.Frame(card, bg=bg_color)
|
||||||
|
btn_frame.pack(pady=(10, 0))
|
||||||
|
|
||||||
|
if status == 'terisi':
|
||||||
|
# Tombol Tutup Meja (hanya jika sudah dibayar)
|
||||||
|
if transaksi_id:
|
||||||
|
transaksi_data = transaksi_get(int(transaksi_id))
|
||||||
|
if transaksi_data and transaksi_data[4] == 'dibayar':
|
||||||
|
tk.Button(
|
||||||
|
btn_frame,
|
||||||
|
text="✅ Tutup Meja",
|
||||||
|
font=("Arial", 9, "bold"),
|
||||||
|
bg='#4CAF50',
|
||||||
|
fg='white',
|
||||||
|
width=12,
|
||||||
|
borderwidth=0,
|
||||||
|
cursor='hand2',
|
||||||
|
command=lambda n=nomor: self.tutup_meja(n)
|
||||||
|
).pack()
|
||||||
|
else:
|
||||||
|
tk.Label(
|
||||||
|
btn_frame,
|
||||||
|
text="⏳ Menunggu Pembayaran",
|
||||||
|
font=("Arial", 8),
|
||||||
|
bg=bg_color,
|
||||||
|
fg='orange'
|
||||||
|
).pack()
|
||||||
|
else:
|
||||||
|
# Meja kosong - tampilkan info saja
|
||||||
|
tk.Label(
|
||||||
|
btn_frame,
|
||||||
|
text="Siap digunakan",
|
||||||
|
font=("Arial", 8),
|
||||||
|
bg=bg_color,
|
||||||
|
fg='gray'
|
||||||
|
).pack()
|
||||||
|
|
||||||
|
# Next column
|
||||||
|
col += 1
|
||||||
|
if col >= 5: # 5 meja per row
|
||||||
|
col = 0
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# Configure grid weights
|
||||||
|
for i in range(5):
|
||||||
|
self.meja_cards_frame.columnconfigure(i, weight=1)
|
||||||
|
|
||||||
|
|
||||||
|
def tutup_meja(self, nomor_meja):
|
||||||
|
"""Tutup meja dan reset status"""
|
||||||
|
# Konfirmasi
|
||||||
|
if not messagebox.askyesno("Konfirmasi", f"Tutup meja {nomor_meja}?\n\nPastikan pelanggan sudah selesai dan transaksi sudah dibayar."):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Tutup meja
|
||||||
|
success = meja_tutup(nomor_meja)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
messagebox.showinfo("✅ Berhasil", f"Meja {nomor_meja} berhasil ditutup dan siap digunakan lagi")
|
||||||
|
self.reload_meja_status()
|
||||||
|
else:
|
||||||
|
messagebox.showerror("❌ Gagal", f"Gagal menutup meja {nomor_meja}")
|
||||||
|
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# FUNGSI TAMBAHAN UNTUK BACKEND
|
||||||
|
# (sudah ada tapi ditambahkan untuk kelengkapan)
|
||||||
|
# ========================================
|
||||||
|
|
||||||
|
def meja_list_all():
|
||||||
|
"""Ambil semua data meja"""
|
||||||
|
rows = read_all(MEJA_CSV)
|
||||||
|
out = []
|
||||||
|
|
||||||
|
for r in rows:
|
||||||
|
try:
|
||||||
|
nomor = int(r.get("nomor_meja") or 0)
|
||||||
|
except:
|
||||||
|
nomor = r.get("nomor_meja")
|
||||||
|
|
||||||
|
transaksi_id = r.get("transaksi_id") or ""
|
||||||
|
out.append((nomor, r.get("status"), transaksi_id))
|
||||||
|
|
||||||
|
out.sort(key=lambda x: int(x[0]) if isinstance(x[0], int) else 0)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
|
|||||||
6
menu.csv
6
menu.csv
@ -1,8 +1,8 @@
|
|||||||
id,nama,kategori,harga,stok,foto,tersedia,item_discount_pct
|
id,nama,kategori,harga,stok,foto,tersedia,item_discount_pct
|
||||||
1,Americano,Minuman,20000.0,7,img/americano.jpg,1,0.0
|
1,Americano,Minuman,20000.0,7,img/americano.jpg,1,0.0
|
||||||
2,Latte,Minuman,25000.0,6,img/latte.jpg,1,10.0
|
2,Latte,Minuman,25000.0,5,img/latte.jpg,1,10.0
|
||||||
3,Banana Cake,Dessert,30000.0,12,img/banana_cake.jpg,1,4.0
|
3,Banana Cake,Dessert,30000.0,9,img/banana_cake.jpg,1,4.0
|
||||||
4,Nasi Goreng,Makanan,25000.0,13,img/nasi_goreng.jpg,1,0.0
|
4,Nasi Goreng,Makanan,25000.0,10,img/nasi_goreng.jpg,1,0.0
|
||||||
5,Nasi Kuning,Makanan,18000.0,7,img/Nasi-Kuning.jpg,1,5.0
|
5,Nasi Kuning,Makanan,18000.0,7,img/Nasi-Kuning.jpg,1,5.0
|
||||||
6,Strawberry Milkshake,Minuman,19000.0,5,img/strawberry_milkshake.jpg,1,0.0
|
6,Strawberry Milkshake,Minuman,19000.0,5,img/strawberry_milkshake.jpg,1,0.0
|
||||||
7,Coconut Matcha,Minuman,21000.0,14,img/coconut_matcha.jpg,1,5.0
|
7,Coconut Matcha,Minuman,21000.0,14,img/coconut_matcha.jpg,1,5.0
|
||||||
|
|||||||
|
@ -4,3 +4,5 @@ id,user_id,nomor_meja,total,status,promo_code,subtotal,item_discount,promo_disco
|
|||||||
3,4,3,20000.0,dibayar,,20000.0,0.0,0.0,2025-12-13 16:33:41
|
3,4,3,20000.0,dibayar,,20000.0,0.0,0.0,2025-12-13 16:33:41
|
||||||
4,4,2,20000.0,dibayar,,20000.0,0.0,0.0,2025-12-13 17:22:52
|
4,4,2,20000.0,dibayar,,20000.0,0.0,0.0,2025-12-13 17:22:52
|
||||||
5,1,2,59500.0,dibayar,CAFETOTORO,69000.0,2500.0,7000.0,2025-12-13 20:12:35
|
5,1,2,59500.0,dibayar,CAFETOTORO,69000.0,2500.0,7000.0,2025-12-13 20:12:35
|
||||||
|
6,4,2,41965.0,diproses,MERDEKA,80000.0,3700.0,34335.0,2025-12-13 22:18:23
|
||||||
|
7,4,2,59180.0,dibayar,MERDEKA,110000.0,2400.0,48420.0,2025-12-13 22:22:02
|
||||||
|
|||||||
|
Loading…
x
Reference in New Issue
Block a user