Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import os | |
| from datetime import datetime | |
| from supabase import create_client, Client | |
| # ============ SUPABASE CONFIGURATION ============ | |
| SUPABASE_URL = os.getenv("SUPABASE_URL") | |
| SUPABASE_KEY = os.getenv("SUPABASE_KEY") | |
| if not SUPABASE_URL or not SUPABASE_KEY: | |
| raise ValueError("β SUPABASE_URL or SUPABASE_KEY not set in Space Secrets!") | |
| supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY) | |
| # ============ SUPABASE HELPER FUNCTIONS ============ | |
| def fetch_books(): | |
| """Fetch all books from Supabase""" | |
| try: | |
| res = supabase.table("books").select("*").execute() | |
| return res.data or [] | |
| except Exception as e: | |
| print(f"Error fetching books: {e}") | |
| return [] | |
| def fetch_borrows(): | |
| """Fetch all active borrow records from Supabase""" | |
| try: | |
| res = supabase.table("borrows").select("*").execute() | |
| return res.data or [] | |
| except Exception as e: | |
| print(f"Error fetching borrows: {e}") | |
| return [] | |
| def fetch_history(): | |
| """Fetch all transactions from Supabase""" | |
| try: | |
| res = supabase.table("transactions").select("entry").execute() | |
| entries = [row["entry"] for row in (res.data or [])] | |
| return entries[-30:] # Return last 30 entries | |
| except Exception as e: | |
| print(f"Error fetching history: {e}") | |
| return [] | |
| def book_titles(): | |
| """Get list of book titles for dropdowns - ALWAYS FRESH FROM DB""" | |
| books = fetch_books() | |
| return [b["title"] for b in books] | |
| # ============ DASHBOARD FUNCTIONS ============ | |
| def get_dashboard_stats(): | |
| books = fetch_books() | |
| total_books = len(books) | |
| total_copies = sum(b["total"] for b in books) | |
| available_copies = sum(b["copies"] for b in books) | |
| borrowed_copies = total_copies - available_copies | |
| return f""" | |
| π **Library Dashboard** | |
| π Total Books: **{total_books}** | |
| β Available Copies: **{available_copies}** | |
| π€ Borrowed Copies: **{borrowed_copies}** | |
| π¦ Total Copies: **{total_copies}** | |
| """ | |
| def get_library_table(): | |
| rows = [] | |
| for b in fetch_books(): | |
| status = "β Available" if b["copies"] > 0 else "β Out of Stock" | |
| rows.append([b["title"], b["author"], b["copies"], b["total"], status]) | |
| return rows | |
| # ============ BROWSE FUNCTIONS ============ | |
| def browse_books(search_query="", sort_by="Title"): | |
| books_list = [] | |
| for b in fetch_books(): | |
| if search_query.lower() in b["title"].lower() or search_query.lower() in b["author"].lower(): | |
| books_list.append({ | |
| "Title": b["title"], | |
| "Author": b["author"], | |
| "Available": b["copies"], | |
| "Total": b["total"], | |
| "Status": "β Available" if b["copies"] > 0 else "β Out of Stock" | |
| }) | |
| if sort_by == "Title": | |
| books_list.sort(key=lambda x: x["Title"]) | |
| elif sort_by == "Author": | |
| books_list.sort(key=lambda x: x["Author"]) | |
| elif sort_by == "Availability": | |
| books_list.sort(key=lambda x: x["Available"], reverse=True) | |
| return books_list | |
| # ============ BORROW FUNCTION ============ | |
| def borrow_book(selected_book, num_copies, name, phone, address): | |
| if not selected_book or selected_book == "": | |
| return "β Please select a book first" | |
| if not name or not phone or not address: | |
| return "β Please fill in all personal details (Name, Phone, Address)" | |
| try: | |
| num_copies = int(num_copies) if num_copies else 1 | |
| if num_copies < 1: | |
| return "β Number of copies must be at least 1" | |
| except Exception as e: | |
| return f"β Invalid number of copies: {e}" | |
| try: | |
| # Get book from database | |
| res = supabase.table("books").select("*").eq("title", selected_book).execute() | |
| books = res.data | |
| if not books: | |
| return "β Book not found in library" | |
| book = books[0] | |
| available = book["copies"] | |
| if available < num_copies: | |
| return f"π Sorry, only {available} copies available. You requested {num_copies}" | |
| # Update copies in database | |
| supabase.table("books").update( | |
| {"copies": available - num_copies} | |
| ).eq("id", book["id"]).execute() | |
| # Record borrow | |
| now = datetime.now().strftime('%Y-%m-%d %H:%M') | |
| supabase.table("borrows").insert({ | |
| "book_title": selected_book, | |
| "name": name, | |
| "phone": phone, | |
| "address": address, | |
| "copies": num_copies, | |
| "date": now | |
| }).execute() | |
| # Add to transaction history | |
| entry = f"[{now}] BORROWED - Book: {selected_book} | Copies: {num_copies} | Borrower: {name} | Phone: {phone} | Address: {address}" | |
| supabase.table("transactions").insert({"entry": entry}).execute() | |
| return f"β {name} successfully borrowed {num_copies} copy/copies of '{selected_book}'! Enjoy reading! π" | |
| except Exception as e: | |
| return f"β Error processing borrow: {e}" | |
| # ============ RETURN FUNCTION ============ | |
| def return_book(selected_book, returner_name): | |
| if not selected_book or selected_book == "": | |
| return "β Please select a book first" | |
| if not returner_name: | |
| return "β Please enter your name" | |
| try: | |
| # Find borrow record | |
| res = supabase.table("borrows").select("*") \ | |
| .eq("book_title", selected_book) \ | |
| .eq("name", returner_name).execute() | |
| records = res.data or [] | |
| if not records: | |
| return f"β No record found for {returner_name} borrowing '{selected_book}'" | |
| record = records[0] | |
| copies = record["copies"] | |
| # Get book | |
| res = supabase.table("books").select("*").eq("title", selected_book).execute() | |
| books = res.data | |
| if not books: | |
| return "π This book doesn't belong to our library" | |
| book = books[0] | |
| # Update book copies | |
| supabase.table("books").update( | |
| {"copies": book["copies"] + copies} | |
| ).eq("id", book["id"]).execute() | |
| # Delete borrow record | |
| supabase.table("borrows").delete().eq("id", record["id"]).execute() | |
| # Add to transaction history | |
| now = datetime.now().strftime('%Y-%m-%d %H:%M') | |
| entry = f"[{now}] RETURNED - Book: {selected_book} | Copies: {copies} | Returner: {returner_name} | Phone: {record['phone']} | Address: {record['address']}" | |
| supabase.table("transactions").insert({"entry": entry}).execute() | |
| return f"β Thank you {returner_name} for returning {copies} copy/copies of '{selected_book}'! π" | |
| except Exception as e: | |
| return f"β Error processing return: {e}" | |
| # ============ ADD BOOK FUNCTION ============ | |
| def add_book(title, author, copies): | |
| if not title or not author or not copies: | |
| return "β Please fill all fields correctly" | |
| try: | |
| copies = int(copies) | |
| if copies < 1: | |
| return "β Number of copies must be at least 1" | |
| # Check if book exists | |
| res = supabase.table("books").select("id").eq("title", title).execute() | |
| if res.data: | |
| return f"β Book '{title}' already exists in the library" | |
| # Insert new book | |
| supabase.table("books").insert({ | |
| "title": title, | |
| "author": author, | |
| "copies": copies, | |
| "total": copies | |
| }).execute() | |
| # Add to transaction history | |
| entry = f"[{datetime.now().strftime('%Y-%m-%d %H:%M')}] ADDED - Book: {title} by {author} ({copies} copies)" | |
| supabase.table("transactions").insert({"entry": entry}).execute() | |
| return f"β '{title}' successfully added to library! π" | |
| except Exception as e: | |
| return f"β Error adding book: {e}" | |
| # ============ HISTORY FUNCTION ============ | |
| def get_history(): | |
| rows = fetch_history() | |
| if not rows: | |
| return "No transactions yet." | |
| return "\n".join(rows) | |
| # ============ GET BORROWER DETAILS FUNCTION ============ | |
| def get_borrower_details(): | |
| records = fetch_borrows() | |
| if not records: | |
| return "No active borrowing records." | |
| details = "π **Active Borrowing Records**\n\n" | |
| by_book = {} | |
| for r in records: | |
| by_book.setdefault(r["book_title"], []).append(r) | |
| for title, recs in by_book.items(): | |
| details += f"**π {title}**\n" | |
| for r in recs: | |
| details += f" β’ Name: {r['name']}\n" | |
| details += f" β’ Phone: {r['phone']}\n" | |
| details += f" β’ Address: {r['address']}\n" | |
| details += f" β’ Copies: {r['copies']}\n" | |
| details += f" β’ Borrowed on: {r['date']}\n\n" | |
| return details | |
| # ============ GRADIO INTERFACE ============ | |
| demo = gr.Blocks(title="π Library Management System") | |
| with demo: | |
| gr.Markdown("# π Library Management System") | |
| gr.Markdown("*Your Smart Library Management Solution (Powered by Supabase)*") | |
| # ===== TAB 1: DASHBOARD ===== | |
| with gr.Tab("π Dashboard"): | |
| dashboard_output = gr.Markdown(get_dashboard_stats()) | |
| gr.Markdown("### π Book Collection") | |
| def update_dashboard(): | |
| return get_dashboard_stats() | |
| def refresh_dashboard_table(): | |
| return get_library_table() | |
| demo.load(update_dashboard, outputs=dashboard_output) | |
| library_table = gr.Dataframe( | |
| value=get_library_table(), | |
| headers=["Title", "Author", "Available", "Total", "Status"], | |
| interactive=False, | |
| label="π Books in Library" | |
| ) | |
| refresh_dashboard_btn = gr.Button("π Refresh Dashboard") | |
| refresh_dashboard_btn.click(update_dashboard, outputs=dashboard_output) | |
| refresh_dashboard_btn.click(refresh_dashboard_table, outputs=library_table) | |
| # ===== TAB 2: BROWSE BOOKS ===== | |
| with gr.Tab("π Browse Books"): | |
| search_input = gr.Textbox(label="Search by title or author", placeholder="e.g., 'Atomic' or 'James Clear'") | |
| sort_dropdown = gr.Dropdown( | |
| choices=["Title", "Author", "Availability"], | |
| value="Title", | |
| label="Sort by" | |
| ) | |
| browse_output = gr.Dataframe( | |
| headers=["Title", "Author", "Available", "Total", "Status"], | |
| interactive=False, | |
| label="π Search Results" | |
| ) | |
| def update_browse(search, sort): | |
| results = browse_books(search, sort) | |
| if results: | |
| return [[r["Title"], r["Author"], r["Available"], r["Total"], r["Status"]] for r in results] | |
| return [] | |
| search_input.change(update_browse, inputs=[search_input, sort_dropdown], outputs=browse_output) | |
| sort_dropdown.change(update_browse, inputs=[search_input, sort_dropdown], outputs=browse_output) | |
| # ===== TAB 3: BORROW BOOK ===== | |
| with gr.Tab("π€ Borrow Book"): | |
| gr.Markdown("### Borrow a book from the library") | |
| books_list = book_titles() | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| borrow_select = gr.Dropdown( | |
| choices=books_list if books_list else ["No books available"], | |
| value=books_list[0] if books_list else None, | |
| label="Select Book *", | |
| interactive=True | |
| ) | |
| borrow_copies = gr.Slider( | |
| label="Number of Copies to Borrow *", | |
| minimum=1, | |
| maximum=10, | |
| step=1, | |
| value=1 | |
| ) | |
| with gr.Column(scale=1): | |
| borrower_name = gr.Textbox( | |
| label="Your Name *", | |
| placeholder="e.g., 'John Doe'" | |
| ) | |
| borrower_phone = gr.Textbox( | |
| label="Your Phone Number *", | |
| placeholder="e.g., '9876543210'" | |
| ) | |
| borrower_address = gr.Textbox( | |
| label="Your Address *", | |
| placeholder="e.g., '123 Main St, City'" | |
| ) | |
| borrow_output = gr.Textbox(label="Result", interactive=False) | |
| borrow_btn = gr.Button("π Borrow Books") | |
| refresh_borrow_btn = gr.Button("π Refresh Book List") | |
| borrow_btn.click( | |
| borrow_book, | |
| inputs=[borrow_select, borrow_copies, borrower_name, borrower_phone, borrower_address], | |
| outputs=borrow_output | |
| ) | |
| def refresh_borrow_list(): | |
| titles = book_titles() | |
| return gr.Dropdown.update( | |
| choices=titles if titles else ["No books available"], | |
| value=titles[0] if titles else None | |
| ) | |
| refresh_borrow_btn.click(refresh_borrow_list, outputs=borrow_select) | |
| # ===== TAB 4: RETURN BOOK ===== | |
| with gr.Tab("β©οΈ Return Book"): | |
| gr.Markdown("### Return a borrowed book") | |
| books_list = book_titles() | |
| with gr.Row(): | |
| return_select = gr.Dropdown( | |
| choices=books_list if books_list else ["No books available"], | |
| value=books_list[0] if books_list else None, | |
| label="Select Book to Return *", | |
| interactive=True, | |
| scale=1 | |
| ) | |
| returner_name = gr.Textbox( | |
| label="Your Name (as per borrow record) *", | |
| placeholder="e.g., 'John Doe'", | |
| scale=1 | |
| ) | |
| return_output = gr.Textbox(label="Result", interactive=False) | |
| return_btn = gr.Button("β©οΈ Return This Book") | |
| refresh_return_btn = gr.Button("π Refresh Book List") | |
| return_btn.click( | |
| return_book, | |
| inputs=[return_select, returner_name], | |
| outputs=return_output | |
| ) | |
| def refresh_return_list(): | |
| titles = book_titles() | |
| return gr.Dropdown.update( | |
| choices=titles if titles else ["No books available"], | |
| value=titles[0] if titles else None | |
| ) | |
| refresh_return_btn.click(refresh_return_list, outputs=return_select) | |
| # ===== TAB 5: ADD BOOK ===== | |
| with gr.Tab("β Add Book"): | |
| gr.Markdown("### Add a new book to the library") | |
| with gr.Row(): | |
| title_input = gr.Textbox(label="Book Title *", placeholder="e.g., 'The Alchemist'", scale=1) | |
| author_input = gr.Textbox(label="Author Name *", placeholder="e.g., 'Paulo Coelho'", scale=1) | |
| copies_input = gr.Slider(label="Number of Copies *", minimum=1, maximum=100, step=1, value=1) | |
| add_output = gr.Textbox(label="Result", interactive=False) | |
| add_btn = gr.Button("β Add Book to Library") | |
| add_btn.click(add_book, inputs=[title_input, author_input, copies_input], outputs=add_output) | |
| # ===== TAB 6: BORROWER DETAILS ===== | |
| with gr.Tab("π₯ Active Borrowers"): | |
| gr.Markdown("### Currently active borrowing records") | |
| borrower_details = gr.Markdown(get_borrower_details()) | |
| refresh_borrowers_btn = gr.Button("π Refresh Records") | |
| refresh_borrowers_btn.click(get_borrower_details, outputs=borrower_details) | |
| # ===== TAB 7: HISTORY ===== | |
| with gr.Tab("π Transaction History"): | |
| gr.Markdown("### All transactions (Borrow, Return, Add)") | |
| history_output = gr.Textbox( | |
| label="History", | |
| value=get_history(), | |
| interactive=False, | |
| lines=20 | |
| ) | |
| refresh_btn = gr.Button("π Refresh History") | |
| refresh_btn.click(get_history, outputs=history_output) | |
| # Launch the app | |
| if __name__ == "__main__": | |
| demo.launch() | |