Babajaan commited on
Commit
cb2e8f0
Β·
verified Β·
1 Parent(s): 4da7596

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +665 -0
  2. requirements.txt +3 -0
app.py ADDED
@@ -0,0 +1,665 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import requests
4
+ import json
5
+ import os
6
+ from datetime import datetime, date
7
+
8
+ # Custom CSS for modern styling
9
+ CUSTOM_CSS = """
10
+ .gradio-container {
11
+ max-width: 1400px !important;
12
+ margin: auto !important;
13
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
14
+ padding: 20px !important;
15
+ border-radius: 20px !important;
16
+ box-shadow: 0 20px 40px rgba(0,0,0,0.15) !important;
17
+ }
18
+
19
+ .welcome-section {
20
+ background: rgba(255, 255, 255, 0.95) !important;
21
+ border-radius: 20px !important;
22
+ padding: 30px !important;
23
+ margin: 20px 0 !important;
24
+ box-shadow: 0 10px 30px rgba(0,0,0,0.15) !important;
25
+ text-align: center !important;
26
+ }
27
+
28
+ .stats-container {
29
+ display: grid;
30
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
31
+ gap: 20px;
32
+ margin: 25px 0;
33
+ }
34
+
35
+ .stat-card {
36
+ background: linear-gradient(135deg, rgba(255,255,255,0.25), rgba(255,255,255,0.15)) !important;
37
+ border-radius: 15px !important;
38
+ padding: 25px !important;
39
+ text-align: center !important;
40
+ backdrop-filter: blur(10px) !important;
41
+ border: 1px solid rgba(255,255,255,0.3) !important;
42
+ box-shadow: 0 8px 25px rgba(0,0,0,0.1) !important;
43
+ }
44
+
45
+ .stat-number {
46
+ font-size: 2.5em !important;
47
+ font-weight: bold !important;
48
+ color: #FFE082 !important;
49
+ display: block !important;
50
+ margin-bottom: 8px !important;
51
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3) !important;
52
+ }
53
+
54
+ .stat-label {
55
+ color: white !important;
56
+ font-size: 1.1em !important;
57
+ font-weight: 500 !important;
58
+ text-transform: uppercase !important;
59
+ letter-spacing: 1px !important;
60
+ }
61
+
62
+ .main-header {
63
+ text-align: center;
64
+ color: white !important;
65
+ font-size: 3.2em !important;
66
+ font-weight: bold !important;
67
+ margin-bottom: 15px !important;
68
+ text-shadow: 3px 3px 6px rgba(0,0,0,0.4) !important;
69
+ }
70
+
71
+ .subtitle {
72
+ text-align: center;
73
+ color: rgba(255,255,255,0.9) !important;
74
+ font-size: 1.3em !important;
75
+ margin-bottom: 30px !important;
76
+ }
77
+
78
+ .card {
79
+ background: rgba(255, 255, 255, 0.95) !important;
80
+ border-radius: 15px !important;
81
+ padding: 25px !important;
82
+ margin: 15px 5px !important;
83
+ box-shadow: 0 8px 32px rgba(0,0,0,0.12) !important;
84
+ border: 1px solid rgba(255, 255, 255, 0.2) !important;
85
+ }
86
+
87
+ .form-section {
88
+ background: rgba(102, 126, 234, 0.1) !important;
89
+ border-radius: 12px !important;
90
+ padding: 20px !important;
91
+ margin: 15px 0 !important;
92
+ border: 1px solid rgba(102, 126, 234, 0.2) !important;
93
+ }
94
+
95
+ .data-preview {
96
+ background: linear-gradient(135deg, #f8f9fa, #e9ecef) !important;
97
+ border-radius: 15px !important;
98
+ padding: 20px !important;
99
+ margin: 20px 0 !important;
100
+ box-shadow: inset 0 2px 10px rgba(0,0,0,0.05) !important;
101
+ }
102
+
103
+ .connection-status {
104
+ text-align: center;
105
+ padding: 15px;
106
+ margin: 20px 0;
107
+ border-radius: 10px;
108
+ font-weight: bold;
109
+ font-size: 1.1em;
110
+ }
111
+
112
+ .status-connected {
113
+ background: linear-gradient(135deg, #4CAF50, #45a049);
114
+ color: white;
115
+ }
116
+
117
+ .status-error {
118
+ background: linear-gradient(135deg, #f44336, #d32f2f);
119
+ color: white;
120
+ }
121
+
122
+ .footer-info {
123
+ text-align: center;
124
+ margin-top: 40px;
125
+ padding: 25px;
126
+ background: rgba(255,255,255,0.15);
127
+ border-radius: 20px;
128
+ color: white;
129
+ backdrop-filter: blur(10px);
130
+ border: 1px solid rgba(255,255,255,0.2);
131
+ }
132
+ """
133
+
134
+ class SupabaseRestClient:
135
+ def __init__(self):
136
+ """Initialize Supabase REST API client"""
137
+ self.url = "https://bnjblzcqaumctpehgoid.supabase.co"
138
+
139
+ # Get API key from environment (HF secret) or fallback
140
+ self.api_key = os.getenv('SUPABASE_KEY',
141
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJuamJsemNxYXVtY3RwZWhnb2lkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTgyOTA0OTQsImV4cCI6MjA3Mzg2NjQ5NH0.L4wPzuBj9dlSKpYxO1eDX-57KcP0mbNfN8stmTB-STM")
142
+
143
+ self.headers = {
144
+ "apikey": self.api_key,
145
+ "Authorization": f"Bearer {self.api_key}",
146
+ "Content-Type": "application/json"
147
+ }
148
+
149
+ # Test connection
150
+ self.connection_status = self.test_connection()
151
+
152
+ def test_connection(self):
153
+ """Test the Supabase REST API connection"""
154
+ try:
155
+ response = requests.get(
156
+ f"{self.url}/rest/v1/member?select=count",
157
+ headers=self.headers,
158
+ timeout=10
159
+ )
160
+
161
+ if response.status_code == 200:
162
+ key_source = "πŸ” HF Secret" if os.getenv('SUPABASE_KEY') else "πŸ”‘ Local Key"
163
+ return f"🟒 Connected to Supabase Database ({key_source})"
164
+ else:
165
+ return f"πŸ”΄ Connection failed: HTTP {response.status_code}"
166
+
167
+ except Exception as e:
168
+ return f"πŸ”΄ Connection error: {str(e)}"
169
+
170
+ def get_table_data(self, table_name, limit=100):
171
+ """Get data from a Supabase table"""
172
+ try:
173
+ response = requests.get(
174
+ f"{self.url}/rest/v1/{table_name}?limit={limit}",
175
+ headers=self.headers,
176
+ timeout=10
177
+ )
178
+
179
+ if response.status_code == 200:
180
+ return response.json()
181
+ else:
182
+ return []
183
+
184
+ except Exception as e:
185
+ print(f"Error fetching {table_name}: {e}")
186
+ return []
187
+
188
+ def insert_data(self, table_name, data):
189
+ """Insert data into a Supabase table"""
190
+ try:
191
+ response = requests.post(
192
+ f"{self.url}/rest/v1/{table_name}",
193
+ headers=self.headers,
194
+ json=data,
195
+ timeout=10
196
+ )
197
+
198
+ return response.status_code == 201
199
+
200
+ except Exception as e:
201
+ print(f"Error inserting into {table_name}: {e}")
202
+ return False
203
+
204
+ def update_data(self, table_name, id_field, id_value, data):
205
+ """Update data in a Supabase table"""
206
+ try:
207
+ response = requests.patch(
208
+ f"{self.url}/rest/v1/{table_name}?{id_field}=eq.{id_value}",
209
+ headers=self.headers,
210
+ json=data,
211
+ timeout=10
212
+ )
213
+
214
+ return response.status_code == 204
215
+
216
+ except Exception as e:
217
+ print(f"Error updating {table_name}: {e}")
218
+ return False
219
+
220
+ class LibraryManagement:
221
+ def __init__(self):
222
+ """Initialize library management with Supabase REST API"""
223
+ self.supabase = SupabaseRestClient()
224
+ self.connection_status = self.supabase.connection_status
225
+
226
+ def get_dashboard_stats(self):
227
+ """Get dashboard statistics"""
228
+ try:
229
+ members_data = self.supabase.get_table_data("member")
230
+ books_data = self.supabase.get_table_data("book")
231
+ borrows_data = self.supabase.get_table_data("borrow")
232
+
233
+ # Count active borrows (where return_date is null)
234
+ active_borrows = len([b for b in borrows_data if b.get('return_date') is None])
235
+
236
+ return len(members_data), len(books_data), active_borrows
237
+ except:
238
+ return 0, 0, 0
239
+
240
+ def get_all_members(self):
241
+ """Get all library members with enhanced formatting"""
242
+ try:
243
+ data = self.supabase.get_table_data("member")
244
+ if data:
245
+ df = pd.DataFrame(data)
246
+ df.columns = ['πŸ†” ID', 'πŸ‘€ Name', 'πŸ“§ Email']
247
+ return df
248
+ else:
249
+ return pd.DataFrame([{"πŸ“Œ Status": "No members found or connection issue"}])
250
+ except Exception as e:
251
+ return pd.DataFrame([{"❌ Error": str(e)}])
252
+
253
+ def get_all_books(self):
254
+ """Get all books with enhanced formatting"""
255
+ try:
256
+ data = self.supabase.get_table_data("book")
257
+ if data:
258
+ df = pd.DataFrame(data)
259
+ df.columns = ['πŸ†” ID', 'πŸ“– Title', '✍️ Author', 'πŸ“… Year']
260
+ return df
261
+ else:
262
+ return pd.DataFrame([{"πŸ“Œ Status": "No books found or connection issue"}])
263
+ except Exception as e:
264
+ return pd.DataFrame([{"❌ Error": str(e)}])
265
+
266
+ def get_current_borrows(self):
267
+ """Get current borrows with enhanced formatting"""
268
+ try:
269
+ # Get active borrows
270
+ borrows_data = self.supabase.get_table_data("borrow")
271
+ active_borrows = [b for b in borrows_data if b.get('return_date') is None]
272
+
273
+ if not active_borrows:
274
+ return pd.DataFrame([{"πŸ“Œ Status": "No active borrows"}])
275
+
276
+ # Get members and books for joining
277
+ members_data = self.supabase.get_table_data("member")
278
+ books_data = self.supabase.get_table_data("book")
279
+
280
+ # Create lookup dictionaries
281
+ member_dict = {m['id']: m for m in members_data}
282
+ book_dict = {b['id']: b for b in books_data}
283
+
284
+ # Build enhanced result
285
+ result_data = []
286
+ for borrow in active_borrows:
287
+ member = member_dict.get(borrow['member_id'], {})
288
+ book = book_dict.get(borrow['book_id'], {})
289
+
290
+ # Calculate days borrowed
291
+ try:
292
+ borrow_date = datetime.strptime(borrow['borrow_date'], '%Y-%m-%d').date()
293
+ days_borrowed = (date.today() - borrow_date).days
294
+ except:
295
+ days_borrowed = 0
296
+
297
+ result_data.append({
298
+ 'πŸ†” ID': borrow['id'],
299
+ 'πŸ‘€ Member': member.get('name', 'Unknown'),
300
+ 'πŸ“§ Email': member.get('email', 'Unknown'),
301
+ 'πŸ“– Book': book.get('title', 'Unknown'),
302
+ '✍️ Author': book.get('author', 'Unknown'),
303
+ 'πŸ“… Date': borrow['borrow_date'],
304
+ '⏱️ Days': days_borrowed
305
+ })
306
+
307
+ return pd.DataFrame(result_data)
308
+
309
+ except Exception as e:
310
+ return pd.DataFrame([{"❌ Error": str(e)}])
311
+
312
+ def add_member(self, name: str, email: str):
313
+ """Add a new member"""
314
+ try:
315
+ if not name or not email:
316
+ return "❌ Name and email are required"
317
+
318
+ data = {"name": name, "email": email}
319
+ success = self.supabase.insert_data("member", data)
320
+
321
+ if success:
322
+ return f"βœ… Successfully added member: {name}"
323
+ else:
324
+ return "❌ Failed to add member"
325
+
326
+ except Exception as e:
327
+ if "duplicate" in str(e).lower():
328
+ return "❌ Email already exists"
329
+ return f"❌ Error: {str(e)}"
330
+
331
+ def add_book(self, title: str, author: str, year: int):
332
+ """Add a new book"""
333
+ try:
334
+ if not title or not author:
335
+ return "❌ Title and author are required"
336
+
337
+ if year < 1900 or year > datetime.now().year + 1:
338
+ return "❌ Invalid year"
339
+
340
+ data = {"title": title, "author": author, "year": year}
341
+ success = self.supabase.insert_data("book", data)
342
+
343
+ if success:
344
+ return f"βœ… Successfully added book: {title}"
345
+ else:
346
+ return "❌ Failed to add book"
347
+
348
+ except Exception as e:
349
+ return f"❌ Error: {str(e)}"
350
+
351
+ def get_member_choices(self):
352
+ """Get member choices for dropdown"""
353
+ try:
354
+ data = self.supabase.get_table_data("member")
355
+ return [(f"πŸ‘€ {m['name']} (ID: {m['id']})", m['id']) for m in data]
356
+ except:
357
+ return [("No members found", 0)]
358
+
359
+ def get_book_choices(self):
360
+ """Get book choices for dropdown"""
361
+ try:
362
+ books_data = self.supabase.get_table_data("book")
363
+ borrows_data = self.supabase.get_table_data("borrow")
364
+
365
+ # Get currently borrowed book IDs
366
+ borrowed_book_ids = set()
367
+ for borrow in borrows_data:
368
+ if borrow.get('return_date') is None:
369
+ borrowed_book_ids.add(borrow['book_id'])
370
+
371
+ # Filter available books
372
+ available_books = [b for b in books_data if b['id'] not in borrowed_book_ids]
373
+
374
+ return [(f"πŸ“– {b['title']} by {b['author']}", b['id']) for b in available_books]
375
+ except:
376
+ return [("No books found", 0)]
377
+
378
+ def get_borrow_choices(self):
379
+ """Get active borrow choices for dropdown"""
380
+ try:
381
+ borrows_data = self.supabase.get_table_data("borrow")
382
+ members_data = self.supabase.get_table_data("member")
383
+ books_data = self.supabase.get_table_data("book")
384
+
385
+ # Create lookup dictionaries
386
+ member_dict = {m['id']: m for m in members_data}
387
+ book_dict = {b['id']: b for b in books_data}
388
+
389
+ active_borrows = [b for b in borrows_data if b.get('return_date') is None]
390
+
391
+ choices = []
392
+ for borrow in active_borrows:
393
+ member = member_dict.get(borrow['member_id'], {})
394
+ book = book_dict.get(borrow['book_id'], {})
395
+
396
+ label = f"πŸ“š {member.get('name', 'Unknown')} - {book.get('title', 'Unknown')}"
397
+ choices.append((label, borrow['id']))
398
+
399
+ return choices if choices else [("No active borrows", 0)]
400
+ except:
401
+ return [("Error loading borrows", 0)]
402
+
403
+ # Initialize the library management system
404
+ library = LibraryManagement()
405
+
406
+ # Interface functions
407
+ def refresh_dashboard():
408
+ """Refresh dashboard stats"""
409
+ members_count, books_count, active_borrows = library.get_dashboard_stats()
410
+
411
+ members_html = f'<div class="stat-card"><span class="stat-number">{members_count}</span><span class="stat-label">πŸ‘₯ Members</span></div>'
412
+ books_html = f'<div class="stat-card"><span class="stat-number">{books_count}</span><span class="stat-label">πŸ“š Books</span></div>'
413
+ borrows_html = f'<div class="stat-card"><span class="stat-number">{active_borrows}</span><span class="stat-label">πŸ“– Active Loans</span></div>'
414
+
415
+ return members_html, books_html, borrows_html
416
+
417
+ def add_new_member(name, email):
418
+ status = library.add_member(name, email)
419
+ members_df = library.get_all_members()
420
+ return status, members_df, "", "" # Clear form
421
+
422
+ def add_new_book(title, author, year):
423
+ status = library.add_book(title, author, year)
424
+ books_df = library.get_all_books()
425
+ return status, books_df, "", "", 2024 # Clear form
426
+
427
+ def borrow_book_action(member_choice, book_choice):
428
+ """Create a new borrow record"""
429
+ try:
430
+ if not member_choice or not book_choice:
431
+ return "❌ Please select both member and book", library.get_current_borrows()
432
+
433
+ data = {
434
+ "member_id": member_choice,
435
+ "book_id": book_choice,
436
+ "borrow_date": date.today().isoformat()
437
+ }
438
+
439
+ success = library.supabase.insert_data("borrow", data)
440
+
441
+ if success:
442
+ return "βœ… Book borrowed successfully! πŸ“š", library.get_current_borrows()
443
+ else:
444
+ return "❌ Failed to create borrow record", library.get_current_borrows()
445
+
446
+ except Exception as e:
447
+ return f"❌ Error: {str(e)}", library.get_current_borrows()
448
+
449
+ def return_book_action(borrow_choice):
450
+ """Return a borrowed book"""
451
+ try:
452
+ if not borrow_choice:
453
+ return "❌ Please select a borrow record", library.get_current_borrows()
454
+
455
+ data = {"return_date": date.today().isoformat()}
456
+ success = library.supabase.update_data("borrow", "id", borrow_choice, data)
457
+
458
+ if success:
459
+ return "βœ… Book returned successfully! πŸ“šβž‘οΈ", library.get_current_borrows()
460
+ else:
461
+ return "❌ Failed to return book", library.get_current_borrows()
462
+
463
+ except Exception as e:
464
+ return f"❌ Error: {str(e)}", library.get_current_borrows()
465
+
466
+ # Create the Gradio interface
467
+ with gr.Blocks(title="πŸ›οΈ Modern Library Management System", css=CUSTOM_CSS, theme=gr.themes.Soft()) as app:
468
+
469
+ # Header
470
+ gr.HTML('<h1 class="main-header">πŸ›οΈ Modern Library Management System</h1>')
471
+ gr.HTML('<div class="subtitle">πŸ“š Your Complete Digital Library Solution</div>')
472
+
473
+ # Connection status
474
+ status_class = "status-connected" if "🟒" in library.connection_status else "status-error"
475
+ gr.HTML(f'<div class="connection-status {status_class}">πŸ“‘ {library.connection_status}</div>')
476
+
477
+ # Welcome section with immediate data display
478
+ with gr.Column():
479
+ gr.HTML('<div class="welcome-section">')
480
+ gr.Markdown("## 🌟 Welcome to Your Library!")
481
+ gr.Markdown("### πŸ“Š Live Dashboard")
482
+
483
+ with gr.Row():
484
+ members_stat = gr.HTML()
485
+ books_stat = gr.HTML()
486
+ borrows_stat = gr.HTML()
487
+
488
+ refresh_dashboard_btn = gr.Button("πŸ”„ Refresh Dashboard", variant="primary", size="lg")
489
+ gr.HTML('</div>')
490
+
491
+ # Immediate data preview
492
+ gr.HTML('<div class="data-preview">')
493
+ gr.Markdown("### πŸ“‹ Current Library Data")
494
+
495
+ with gr.Row():
496
+ with gr.Column():
497
+ gr.Markdown("#### πŸ‘₯ Library Members")
498
+ members_preview = gr.Dataframe(
499
+ label="Members",
500
+ interactive=False,
501
+ height=250
502
+ )
503
+
504
+ with gr.Column():
505
+ gr.Markdown("#### πŸ“š Book Catalog")
506
+ books_preview = gr.Dataframe(
507
+ label="Books",
508
+ interactive=False,
509
+ height=250
510
+ )
511
+
512
+ with gr.Row():
513
+ gr.Markdown("#### πŸ“– Active Borrowings")
514
+ borrows_preview = gr.Dataframe(
515
+ label="Current Borrowings",
516
+ interactive=False,
517
+ height=200
518
+ )
519
+
520
+ gr.HTML('</div>')
521
+
522
+ with gr.Tabs():
523
+ # Members Management
524
+ with gr.Tab("πŸ‘₯ Members Management"):
525
+ gr.HTML('<div class="card">')
526
+ gr.Markdown("### πŸ‘₯ Complete Members Management")
527
+
528
+ refresh_members_btn = gr.Button("πŸ”„ Refresh Members", variant="secondary")
529
+ members_table = gr.Dataframe(label="πŸ“‹ All Members", interactive=False, wrap=True, height=300)
530
+
531
+ gr.HTML('<div class="form-section">')
532
+ gr.Markdown("#### βž• Add New Member")
533
+
534
+ with gr.Row():
535
+ member_name = gr.Textbox(label="πŸ‘€ Name", placeholder="Enter member name")
536
+ member_email = gr.Textbox(label="πŸ“§ Email", placeholder="Enter email address")
537
+
538
+ add_member_btn = gr.Button("βž• Add Member", variant="primary", size="lg")
539
+ member_status = gr.Textbox(label="πŸ“ Status", interactive=False)
540
+ gr.HTML('</div>')
541
+ gr.HTML('</div>')
542
+
543
+ # Books Management
544
+ with gr.Tab("πŸ“š Books Management"):
545
+ gr.HTML('<div class="card">')
546
+ gr.Markdown("### πŸ“š Complete Books Management")
547
+
548
+ refresh_books_btn = gr.Button("πŸ”„ Refresh Books", variant="secondary")
549
+ books_table = gr.Dataframe(label="πŸ“‹ All Books", interactive=False, wrap=True, height=300)
550
+
551
+ gr.HTML('<div class="form-section">')
552
+ gr.Markdown("#### βž• Add New Book")
553
+
554
+ with gr.Row():
555
+ book_title = gr.Textbox(label="πŸ“– Title", placeholder="Enter book title")
556
+ book_author = gr.Textbox(label="✍️ Author", placeholder="Enter author name")
557
+ book_year = gr.Number(label="πŸ“… Year", value=2024, minimum=1900, maximum=2030)
558
+
559
+ add_book_btn = gr.Button("βž• Add Book", variant="primary", size="lg")
560
+ book_status = gr.Textbox(label="πŸ“ Status", interactive=False)
561
+ gr.HTML('</div>')
562
+ gr.HTML('</div>')
563
+
564
+ # Borrowing Management
565
+ with gr.Tab("πŸ“ Borrowing Management"):
566
+ gr.HTML('<div class="card">')
567
+ gr.Markdown("### πŸ“– Complete Borrowing Management")
568
+
569
+ refresh_borrows_btn = gr.Button("πŸ”„ Refresh Borrowings", variant="secondary")
570
+ borrows_table = gr.Dataframe(label="πŸ“‹ All Borrowings", interactive=False, wrap=True, height=300)
571
+
572
+ gr.HTML('<div class="form-section">')
573
+ gr.Markdown("#### πŸ“š Issue New Book")
574
+
575
+ with gr.Row():
576
+ member_dropdown = gr.Dropdown(label="πŸ‘€ Select Member", value=None)
577
+ book_dropdown = gr.Dropdown(label="πŸ“– Select Available Book", value=None)
578
+
579
+ refresh_dropdowns_btn = gr.Button("πŸ”„ Refresh Options", size="sm")
580
+ borrow_btn = gr.Button("πŸ“š Issue Book", variant="primary", size="lg")
581
+ borrow_status = gr.Textbox(label="πŸ“ Issue Status", interactive=False)
582
+ gr.HTML('</div>')
583
+
584
+ gr.HTML('<div class="form-section">')
585
+ gr.Markdown("#### ↩️ Return Book")
586
+
587
+ borrow_dropdown = gr.Dropdown(label="πŸ“š Select Book to Return", value=None)
588
+ refresh_return_btn = gr.Button("πŸ”„ Refresh Returns", size="sm")
589
+ return_btn = gr.Button("↩️ Return Book", variant="secondary", size="lg")
590
+ return_status = gr.Textbox(label="πŸ“ Return Status", interactive=False)
591
+ gr.HTML('</div>')
592
+ gr.HTML('</div>')
593
+
594
+ # Load initial data
595
+ app.load(
596
+ lambda: [
597
+ *refresh_dashboard(),
598
+ library.get_all_members(),
599
+ library.get_all_books(),
600
+ library.get_current_borrows(),
601
+ gr.Dropdown(choices=library.get_member_choices()),
602
+ gr.Dropdown(choices=library.get_book_choices()),
603
+ gr.Dropdown(choices=library.get_borrow_choices())
604
+ ],
605
+ outputs=[
606
+ members_stat, books_stat, borrows_stat,
607
+ members_preview, books_preview, borrows_preview,
608
+ member_dropdown, book_dropdown, borrow_dropdown
609
+ ]
610
+ )
611
+
612
+ # Event handlers
613
+ refresh_dashboard_btn.click(refresh_dashboard, outputs=[members_stat, books_stat, borrows_stat])
614
+ refresh_members_btn.click(library.get_all_members, outputs=members_table)
615
+ refresh_books_btn.click(library.get_all_books, outputs=books_table)
616
+ refresh_borrows_btn.click(library.get_current_borrows, outputs=borrows_table)
617
+
618
+ add_member_btn.click(
619
+ add_new_member,
620
+ inputs=[member_name, member_email],
621
+ outputs=[member_status, members_table, member_name, member_email]
622
+ )
623
+
624
+ add_book_btn.click(
625
+ add_new_book,
626
+ inputs=[book_title, book_author, book_year],
627
+ outputs=[book_status, books_table, book_title, book_author, book_year]
628
+ )
629
+
630
+ refresh_dropdowns_btn.click(
631
+ lambda: [gr.Dropdown(choices=library.get_member_choices()),
632
+ gr.Dropdown(choices=library.get_book_choices())],
633
+ outputs=[member_dropdown, book_dropdown]
634
+ )
635
+
636
+ refresh_return_btn.click(
637
+ lambda: gr.Dropdown(choices=library.get_borrow_choices()),
638
+ outputs=borrow_dropdown
639
+ )
640
+
641
+ borrow_btn.click(
642
+ borrow_book_action,
643
+ inputs=[member_dropdown, book_dropdown],
644
+ outputs=[borrow_status, borrows_table]
645
+ )
646
+
647
+ return_btn.click(
648
+ return_book_action,
649
+ inputs=[borrow_dropdown],
650
+ outputs=[return_status, borrows_table]
651
+ )
652
+
653
+ # Footer
654
+ gr.HTML('''
655
+ <div class="footer-info">
656
+ <h3>πŸ›οΈ Modern Library Management System</h3>
657
+ <p><strong>✨ Features:</strong> Real-time dashboard β€’ Member management β€’ Book catalog β€’ Borrowing system</p>
658
+ <p><strong>πŸ”— Connection:</strong> Direct Supabase REST API | Production Ready</p>
659
+ <p><strong>πŸš€ Status:</strong> Deployed on Hugging Face Spaces | Mobile Responsive | Secure</p>
660
+ </div>
661
+ ''')
662
+
663
+ # Launch the app
664
+ if __name__ == "__main__":
665
+ app.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio==4.44.1
2
+ pandas==2.3.2
3
+ requests==2.31.0