π¨ FuzzuTech – Modern Hotel Management System (Python CTkinter + SQLite Demo) π
Demo :
Click Video πππ
Description :
Include the same description as YouTube, embed the YouTube Short, and add screenshots of the UI.
Features :
-
✅ Fully functional single-file Python project
-
✅ Professional UI using CTkinter
-
✅ Built-in database (SQLite)
-
✅ Ideal for hotel/room booking apps
-
✅ Beginner-friendly, extendable project
Code :
"""
FuzzuTech - Modern Hotel Management System (CTkinter + SQLite)
Single-file example: app.py
Requires: customtkinter, pillow(optional)
pip install customtkinter pillow
"""
import sqlite3
import os
from datetime import datetime
import customtkinter as ctk
from tkinter import ttk, messagebox, filedialog
from PIL import Image, ImageTk
# --------------------
# App Appearance
# --------------------
ctk.set_appearance_mode("dark") # "system", "dark", "light"
ctk.set_default_color_theme("blue") # or provide custom json
DB_FILE = "hotel.db"
# --------------------
# Database Helpers
# --------------------
def init_db():
conn = sqlite3.connect(DB_FILE)
cur = conn.cursor()
# guests table
cur.execute("""
CREATE TABLE IF NOT EXISTS guests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
phone TEXT,
email TEXT,
address TEXT,
created_at TEXT
)
""")
# rooms table
cur.execute("""
CREATE TABLE IF NOT EXISTS rooms (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room_number TEXT UNIQUE NOT NULL,
room_type TEXT,
price REAL,
status TEXT DEFAULT 'available',
description TEXT
)
""")
# bookings table
cur.execute("""
CREATE TABLE IF NOT EXISTS bookings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guest_id INTEGER,
room_id INTEGER,
check_in TEXT,
check_out TEXT,
total REAL,
status TEXT DEFAULT 'booked',
created_at TEXT,
FOREIGN KEY(guest_id) REFERENCES guests(id),
FOREIGN KEY(room_id) REFERENCES rooms(id)
)
""")
conn.commit()
conn.close()
def query(sql, params=(), fetch=False):
conn = sqlite3.connect(DB_FILE)
cur = conn.cursor()
cur.execute(sql, params)
data = None
if fetch:
data = cur.fetchall()
conn.commit()
conn.close()
return data
# --------------------
# Main App Class
# --------------------
class HotelApp(ctk.CTk):
def __init__(self):
super().__init__()
self.title("FuzzuTech — Hotel Management System")
self.geometry("1100x700")
self.minsize(1000, 600)
# ---- layout: Left menu + Right content ----
self.grid_columnconfigure(1, weight=1)
self.grid_rowconfigure(0, weight=1)
self.sidebar = ctk.CTkFrame(self, width=220, corner_radius=0)
self.sidebar.grid(row=0, column=0, sticky="nswe")
self.content = ctk.CTkFrame(self, corner_radius=0)
self.content.grid(row=0, column=1, sticky="nswe", padx=16, pady=16)
# Sidebar content
self._build_sidebar()
# initially show dashboard
self.current_frame = None
self.show_dashboard()
# --------------------
# Sidebar
# --------------------
def _build_sidebar(self):
logo_path = os.path.join("assets", "logo.png")
if os.path.exists(logo_path):
img = Image.open(logo_path).resize((80, 80))
self.logo_img = ImageTk.PhotoImage(img)
ctk.CTkLabel(self.sidebar, image=self.logo_img, text="").pack(pady=(12,6))
else:
ctk.CTkLabel(self.sidebar, text="FuzzuTech", font=ctk.CTkFont(size=20, weight="bold")).pack(pady=(18,6))
ctk.CTkLabel(self.sidebar, text="Hotel Management", font=ctk.CTkFont(size=12)).pack(pady=(0,12))
btn_dashboard = ctk.CTkButton(self.sidebar, text="Dashboard", command=self.show_dashboard)
btn_rooms = ctk.CTkButton(self.sidebar, text="Rooms", command=self.show_rooms)
btn_guests = ctk.CTkButton(self.sidebar, text="Guests", command=self.show_guests)
btn_bookings = ctk.CTkButton(self.sidebar, text="Bookings", command=self.show_bookings)
btn_reports = ctk.CTkButton(self.sidebar, text="Reports", command=self.show_reports)
btn_export = ctk.CTkButton(self.sidebar, text="Export CSV", command=self.export_csv)
for w in (btn_dashboard, btn_rooms, btn_guests, btn_bookings, btn_reports, btn_export):
w.pack(fill="x", padx=12, pady=6)
# --------------------
# Utility: clear content frame
# --------------------
def clear_content(self):
for child in self.content.winfo_children():
child.destroy()
# --------------------
# Dashboard
# --------------------
def show_dashboard(self):
self.clear_content()
header = ctk.CTkLabel(self.content, text="Dashboard", font=ctk.CTkFont(size=20, weight="bold"))
header.pack(anchor="nw")
frame = ctk.CTkFrame(self.content, corner_radius=10)
frame.pack(fill="both", expand=True, pady=12)
# stats
stats_frame = ctk.CTkFrame(frame)
stats_frame.pack(fill="x", padx=12, pady=12)
total_rooms = query("SELECT COUNT(*) FROM rooms", fetch=True)[0][0]
total_guests = query("SELECT COUNT(*) FROM guests", fetch=True)[0][0]
active_bookings = query("SELECT COUNT(*) FROM bookings WHERE status='booked'", fetch=True)[0][0]
stat1 = ctk.CTkLabel(stats_frame, text=f"Rooms\n{total_rooms}", anchor="w")
stat2 = ctk.CTkLabel(stats_frame, text=f"Guests\n{total_guests}", anchor="w")
stat3 = ctk.CTkLabel(stats_frame, text=f"Active Bookings\n{active_bookings}", anchor="w")
stat1.grid(row=0, column=0, padx=18, pady=10)
stat2.grid(row=0, column=1, padx=18, pady=10)
stat3.grid(row=0, column=2, padx=18, pady=10)
# recent bookings table
recent = ctk.CTkLabel(frame, text="Recent Bookings", font=ctk.CTkFont(size=14, weight="bold"))
recent.pack(anchor="nw", padx=12)
cols = ("id", "guest", "room", "check_in", "check_out", "status")
tree_frame = ctk.CTkFrame(frame)
tree_frame.pack(fill="both", expand=True, padx=12, pady=8)
tree = ttk.Treeview(tree_frame, columns=cols, show="headings", height=6)
for c in cols:
tree.heading(c, text=c.title())
tree.column(c, width=120)
tree.pack(fill="both", expand=True, side="left")
scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=tree.yview)
scrollbar.pack(side="right", fill="y")
tree.configure(yscroll=scrollbar.set)
bookings = query("""
SELECT b.id, g.name, r.room_number, b.check_in, b.check_out, b.status
FROM bookings b
LEFT JOIN guests g ON b.guest_id = g.id
LEFT JOIN rooms r ON b.room_id = r.id
ORDER BY b.created_at DESC LIMIT 10
""", fetch=True)
for b in bookings:
tree.insert("", "end", values=b)
# --------------------
# Rooms Management
# --------------------
def show_rooms(self):
self.clear_content()
header = ctk.CTkLabel(self.content, text="Rooms", font=ctk.CTkFont(size=18, weight="bold"))
header.pack(anchor="nw")
topbar = ctk.CTkFrame(self.content)
topbar.pack(fill="x", pady=8, padx=6)
add_btn = ctk.CTkButton(topbar, text="Add Room", command=self.open_add_room)
add_btn.pack(side="left", padx=6)
search_var = ctk.StringVar()
search_entry = ctk.CTkEntry(topbar, placeholder_text="Search room number or type...", textvariable=search_var, width=300)
search_entry.pack(side="right", padx=6)
# rooms table
cols = ("id", "room_number", "room_type", "price", "status")
tree_frame = ctk.CTkFrame(self.content)
tree_frame.pack(fill="both", expand=True, padx=6, pady=6)
self.room_tree = ttk.Treeview(tree_frame, columns=cols, show="headings")
for c in cols:
self.room_tree.heading(c, text=c.replace("_", " ").title())
self.room_tree.column(c, width=120)
self.room_tree.pack(fill="both", expand=True, side="left")
scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.room_tree.yview)
scrollbar.pack(side="right", fill="y")
self.room_tree.configure(yscroll=scrollbar.set)
self.room_tree.bind("<Double-1>", self.on_room_double)
# load rooms
self.load_rooms()
def on_search(*_):
q = search_var.get().strip()
if q == "":
self.load_rooms()
else:
rows = query("SELECT id, room_number, room_type, price, status FROM rooms WHERE room_number LIKE ? OR room_type LIKE ?",
(f"%{q}%", f"%{q}%"), fetch=True)
self.room_tree.delete(*self.room_tree.get_children())
for r in rows:
self.room_tree.insert("", "end", values=r)
search_var.trace_add("write", on_search)
def load_rooms(self):
rows = query("SELECT id, room_number, room_type, price, status FROM rooms ORDER BY id DESC", fetch=True)
self.room_tree.delete(*self.room_tree.get_children())
for r in rows:
self.room_tree.insert("", "end", values=r)
def open_add_room(self):
popup = ctk.CTkToplevel(self)
popup.title("Add Room")
popup.geometry("420x360")
ctk.CTkLabel(popup, text="New Room", font=ctk.CTkFont(size=16, weight="bold")).pack(pady=10)
frm = ctk.CTkFrame(popup)
frm.pack(padx=12, pady=8, fill="both", expand=True)
rn = ctk.StringVar()
rtype = ctk.StringVar()
price = ctk.StringVar()
desc = ctk.StringVar()
ctk.CTkLabel(frm, text="Room Number:").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=rn).pack(fill="x", pady=4)
ctk.CTkLabel(frm, text="Room Type:").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=rtype).pack(fill="x", pady=4)
ctk.CTkLabel(frm, text="Price (per night):").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=price).pack(fill="x", pady=4)
ctk.CTkLabel(frm, text="Description:").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=desc).pack(fill="x", pady=4)
def save_room():
try:
query("INSERT INTO rooms (room_number, room_type, price, description) VALUES (?, ?, ?, ?)",
(rn.get().strip(), rtype.get().strip(), float(price.get() or 0), desc.get().strip()))
messagebox.showinfo("Success", "Room added.")
popup.destroy()
self.load_rooms()
except Exception as e:
messagebox.showerror("Error", f"Failed to add room:\n{e}")
ctk.CTkButton(popup, text="Save", command=save_room).pack(pady=10)
def on_room_double(self, event):
sel = self.room_tree.selection()
if not sel: return
item = self.room_tree.item(sel[0])
room_id = item["values"][0]
self.open_edit_room(room_id)
def open_edit_room(self, room_id):
data = query("SELECT room_number, room_type, price, status, description FROM rooms WHERE id=?", (room_id,), fetch=True)
if not data: return
rn, rtype, price, status, desc = data[0]
popup = ctk.CTkToplevel(self)
popup.title(f"Edit Room {rn}")
popup.geometry("420x380")
frm = ctk.CTkFrame(popup)
frm.pack(padx=12, pady=8, fill="both", expand=True)
rn_var = ctk.StringVar(value=rn)
rtype_var = ctk.StringVar(value=rtype)
price_var = ctk.StringVar(value=str(price))
status_var = ctk.StringVar(value=status)
desc_var = ctk.StringVar(value=desc)
ctk.CTkLabel(frm, text="Room Number:").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=rn_var).pack(fill="x", pady=4)
ctk.CTkLabel(frm, text="Room Type:").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=rtype_var).pack(fill="x", pady=4)
ctk.CTkLabel(frm, text="Price (per night):").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=price_var).pack(fill="x", pady=4)
ctk.CTkLabel(frm, text="Status:").pack(anchor="w")
ctk.CTkComboBox(frm, values=["available", "occupied", "maintenance"], variable=status_var).pack(fill="x", pady=4)
ctk.CTkLabel(frm, text="Description:").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=desc_var).pack(fill="x", pady=4)
def save():
try:
query("UPDATE rooms SET room_number=?, room_type=?, price=?, status=?, description=? WHERE id=?",
(rn_var.get().strip(), rtype_var.get().strip(), float(price_var.get() or 0), status_var.get(), desc_var.get().strip(), room_id))
messagebox.showinfo("Saved", "Room updated.")
popup.destroy()
self.load_rooms()
except Exception as e:
messagebox.showerror("Error", str(e))
ctk.CTkButton(popup, text="Save Changes", command=save).pack(pady=8)
# --------------------
# Guests Management
# --------------------
def show_guests(self):
self.clear_content()
ctk.CTkLabel(self.content, text="Guests", font=ctk.CTkFont(size=18, weight="bold")).pack(anchor="nw")
top = ctk.CTkFrame(self.content)
top.pack(fill="x", pady=6, padx=6)
ctk.CTkButton(top, text="Add Guest", command=self.open_add_guest).pack(side="left", padx=6)
cols = ("id", "name", "phone", "email", "created_at")
frame = ctk.CTkFrame(self.content)
frame.pack(fill="both", expand=True, padx=6, pady=6)
self.guest_tree = ttk.Treeview(frame, columns=cols, show="headings")
for c in cols:
self.guest_tree.heading(c, text=c.title())
self.guest_tree.column(c, width=140)
self.guest_tree.pack(fill="both", expand=True, side="left")
scrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.guest_tree.yview)
scrollbar.pack(side="right", fill="y")
self.guest_tree.configure(yscroll=scrollbar.set)
self.guest_tree.bind("<Double-1>", self.on_guest_double)
self.load_guests()
def load_guests(self):
rows = query("SELECT id, name, phone, email, created_at FROM guests ORDER BY id DESC", fetch=True)
self.guest_tree.delete(*self.guest_tree.get_children())
for r in rows:
self.guest_tree.insert("", "end", values=r)
def open_add_guest(self):
popup = ctk.CTkToplevel(self)
popup.title("Add Guest")
popup.geometry("420x380")
frm = ctk.CTkFrame(popup)
frm.pack(padx=12, pady=8, fill="both", expand=True)
name = ctk.StringVar()
phone = ctk.StringVar()
email = ctk.StringVar()
address = ctk.StringVar()
ctk.CTkLabel(frm, text="Name:").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=name).pack(fill="x", pady=4)
ctk.CTkLabel(frm, text="Phone:").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=phone).pack(fill="x", pady=4)
ctk.CTkLabel(frm, text="Email:").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=email).pack(fill="x", pady=4)
ctk.CTkLabel(frm, text="Address:").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=address).pack(fill="x", pady=4)
def save():
if name.get().strip() == "":
messagebox.showwarning("Required", "Name required")
return
query("INSERT INTO guests (name, phone, email, address, created_at) VALUES (?, ?, ?, ?, ?)",
(name.get().strip(), phone.get().strip(), email.get().strip(), address.get().strip(), datetime.now().isoformat()))
messagebox.showinfo("Added", "Guest added.")
popup.destroy()
self.load_guests()
ctk.CTkButton(popup, text="Save", command=save).pack(pady=8)
def on_guest_double(self, event):
sel = self.guest_tree.selection()
if not sel: return
item = self.guest_tree.item(sel[0])
gid = item["values"][0]
self.open_edit_guest(gid)
def open_edit_guest(self, gid):
data = query("SELECT name, phone, email, address FROM guests WHERE id=?", (gid,), fetch=True)
if not data: return
name, phone, email, address = data[0]
popup = ctk.CTkToplevel(self)
popup.title("Edit Guest")
popup.geometry("420x380")
frm = ctk.CTkFrame(popup)
frm.pack(padx=12, pady=8, fill="both", expand=True)
name_var = ctk.StringVar(value=name)
phone_var = ctk.StringVar(value=phone)
email_var = ctk.StringVar(value=email)
address_var = ctk.StringVar(value=address)
ctk.CTkLabel(frm, text="Name:").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=name_var).pack(fill="x", pady=4)
ctk.CTkLabel(frm, text="Phone:").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=phone_var).pack(fill="x", pady=4)
ctk.CTkLabel(frm, text="Email:").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=email_var).pack(fill="x", pady=4)
ctk.CTkLabel(frm, text="Address:").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=address_var).pack(fill="x", pady=4)
def save():
query("UPDATE guests SET name=?, phone=?, email=?, address=? WHERE id=?",
(name_var.get().strip(), phone_var.get().strip(), email_var.get().strip(), address_var.get().strip(), gid))
messagebox.showinfo("Saved", "Guest updated.")
popup.destroy()
self.load_guests()
ctk.CTkButton(popup, text="Save Changes", command=save).pack(pady=8)
# --------------------
# Bookings
# --------------------
def show_bookings(self):
self.clear_content()
ctk.CTkLabel(self.content, text="Bookings", font=ctk.CTkFont(size=18, weight="bold")).pack(anchor="nw")
top = ctk.CTkFrame(self.content)
top.pack(fill="x", pady=6, padx=6)
ctk.CTkButton(top, text="New Booking", command=self.open_new_booking).pack(side="left", padx=6)
ctk.CTkButton(top, text="Check-out", command=self.checkout_booking).pack(side="left", padx=6)
cols = ("id", "guest", "room", "check_in", "check_out", "total", "status")
frame = ctk.CTkFrame(self.content)
frame.pack(fill="both", expand=True, padx=6, pady=6)
self.book_tree = ttk.Treeview(frame, columns=cols, show="headings")
for c in cols:
self.book_tree.heading(c, text=c.title())
self.book_tree.column(c, width=120)
self.book_tree.pack(fill="both", expand=True, side="left")
scrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.book_tree.yview)
scrollbar.pack(side="right", fill="y")
self.book_tree.configure(yscroll=scrollbar.set)
self.load_bookings()
def load_bookings(self):
rows = query("""
SELECT b.id, g.name, r.room_number, b.check_in, b.check_out, b.total, b.status
FROM bookings b
LEFT JOIN guests g ON b.guest_id = g.id
LEFT JOIN rooms r ON b.room_id = r.id
ORDER BY b.created_at DESC
""", fetch=True)
self.book_tree.delete(*self.book_tree.get_children())
for r in rows:
self.book_tree.insert("", "end", values=r)
def open_new_booking(self):
popup = ctk.CTkToplevel(self)
popup.title("New Booking")
popup.geometry("560x480")
frm = ctk.CTkFrame(popup)
frm.pack(padx=12, pady=8, fill="both", expand=True)
# Guest selection
guests = query("SELECT id, name FROM guests ORDER BY name", fetch=True)
guest_map = {f"{g[1]} (id:{g[0]})": g[0] for g in guests}
guest_var = ctk.StringVar()
ctk.CTkLabel(frm, text="Guest:").pack(anchor="w")
guest_combo = ctk.CTkComboBox(frm, values=list(guest_map.keys()), variable=guest_var)
guest_combo.pack(fill="x", pady=4)
# Room selection (only available)
rooms = query("SELECT id, room_number, price FROM rooms WHERE status='available' ORDER BY room_number", fetch=True)
room_map = {f"{r[1]} - ₹{r[2]} (id:{r[0]})": r[0] for r in rooms}
room_var = ctk.StringVar()
ctk.CTkLabel(frm, text="Room:").pack(anchor="w")
room_combo = ctk.CTkComboBox(frm, values=list(room_map.keys()), variable=room_var)
room_combo.pack(fill="x", pady=4)
check_in = ctk.StringVar(value=datetime.now().date().isoformat())
check_out = ctk.StringVar(value=(datetime.now().date()).isoformat())
ctk.CTkLabel(frm, text="Check-in (YYYY-MM-DD):").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=check_in).pack(fill="x", pady=4)
ctk.CTkLabel(frm, text="Check-out (YYYY-MM-DD):").pack(anchor="w")
ctk.CTkEntry(frm, textvariable=check_out).pack(fill="x", pady=4)
def book():
if not guest_var.get() or not room_var.get():
messagebox.showwarning("Required", "Select guest and room")
return
try:
g_id = guest_map[guest_var.get()]
r_id = room_map[room_var.get()]
ci = datetime.fromisoformat(check_in.get())
co = datetime.fromisoformat(check_out.get())
nights = max(1, (co - ci).days or 1)
price = query("SELECT price FROM rooms WHERE id=?", (r_id,), fetch=True)[0][0]
total = nights * price
query("INSERT INTO bookings (guest_id, room_id, check_in, check_out, total, status, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
(g_id, r_id, check_in.get(), check_out.get(), total, "booked", datetime.now().isoformat()))
query("UPDATE rooms SET status='occupied' WHERE id=?", (r_id,))
messagebox.showinfo("Booked", f"Booking created. Total ₹{total}")
popup.destroy()
self.load_bookings()
self.load_rooms()
except Exception as e:
messagebox.showerror("Error", str(e))
ctk.CTkButton(popup, text="Create Booking", command=book).pack(pady=10)
def checkout_booking(self):
sel = self.book_tree.selection()
if not sel:
messagebox.showwarning("Select", "Select a booking to checkout")
return
item = self.book_tree.item(sel[0])
booking_id = item["values"][0]
# set status and free the room
room_num = item["values"][2]
room = query("SELECT id FROM rooms WHERE room_number=?", (room_num,), fetch=True)
if room:
room_id = room[0][0]
query("UPDATE bookings SET status='checked_out' WHERE id=?", (booking_id,))
query("UPDATE rooms SET status='available' WHERE id=?", (room_id,))
messagebox.showinfo("Checked out", "Guest checked out and room freed.")
self.load_bookings()
self.load_rooms()
else:
messagebox.showerror("Error", "Room not found for booking.")
# --------------------
# Reports & Export
# --------------------
def show_reports(self):
self.clear_content()
ctk.CTkLabel(self.content, text="Reports", font=ctk.CTkFont(size=18, weight="bold")).pack(anchor="nw")
frame = ctk.CTkFrame(self.content)
frame.pack(fill="both", expand=True, padx=6, pady=6)
# Simple revenue report
revenue = query("SELECT SUM(total) FROM bookings WHERE status IN ('booked','checked_out')", fetch=True)[0][0] or 0
total_bookings = query("SELECT COUNT(*) FROM bookings", fetch=True)[0][0]
active_rooms = query("SELECT COUNT(*) FROM rooms WHERE status='occupied'", fetch=True)[0][0]
ctk.CTkLabel(frame, text=f"Total Revenue: ₹{revenue}", font=ctk.CTkFont(size=14)).pack(anchor="w", pady=6)
ctk.CTkLabel(frame, text=f"Total Bookings: {total_bookings}", font=ctk.CTkFont(size=14)).pack(anchor="w", pady=6)
ctk.CTkLabel(frame, text=f"Occupied Rooms: {active_rooms}", font=ctk.CTkFont(size=14)).pack(anchor="w", pady=6)
def export_csv(self):
path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV files","*.csv")])
if not path:
return
rows = query("""SELECT b.id, g.name, r.room_number, b.check_in, b.check_out, b.total, b.status
FROM bookings b
LEFT JOIN guests g ON b.guest_id=g.id
LEFT JOIN rooms r ON b.room_id=r.id""", fetch=True)
try:
import csv
with open(path, "w", newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(["BookingID","Guest","Room","CheckIn","CheckOut","Total","Status"])
writer.writerows(rows)
messagebox.showinfo("Exported", f"Bookings exported to {path}")
except Exception as e:
messagebox.showerror("Error", str(e))
# --------------------
# Launch
# --------------------
if __name__ == "__main__":
init_db()
app = HotelApp()
app.mainloop()
Comments
Post a Comment