Jofax commited on
Commit
6f2184f
Β·
verified Β·
1 Parent(s): 351a54d

(requirements.txt)
pandas

Files changed (1) hide show
  1. app.py +761 -0
app.py ADDED
@@ -0,0 +1,761 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ from datetime import datetime
4
+
5
+ # --- LUXURY CUSTOM CSS ---
6
+ custom_css = """
7
+ @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@300;400;600;700&family=Montserrat:wght@300;400;500;600&display=swap');
8
+
9
+ :root {
10
+ --gold: #D4AF37;
11
+ --gold-dark: #B8941F;
12
+ --navy: #0A1828;
13
+ --navy-light: #1A2F42;
14
+ --cream: #F8F6F0;
15
+ --white: #FFFFFF;
16
+ --shadow: rgba(10, 24, 40, 0.15);
17
+ --shadow-heavy: rgba(10, 24, 40, 0.3);
18
+ }
19
+
20
+ .gradio-container {
21
+ background: linear-gradient(135deg, #0A1828 0%, #1A2F42 50%, #0A1828 100%) !important;
22
+ font-family: 'Montserrat', sans-serif !important;
23
+ position: relative;
24
+ overflow-x: hidden;
25
+ }
26
+
27
+ .gradio-container::before {
28
+ content: '';
29
+ position: fixed;
30
+ top: 0;
31
+ left: 0;
32
+ right: 0;
33
+ bottom: 0;
34
+ background-image:
35
+ radial-gradient(circle at 20% 30%, rgba(212, 175, 55, 0.05) 0%, transparent 50%),
36
+ radial-gradient(circle at 80% 70%, rgba(212, 175, 55, 0.03) 0%, transparent 50%);
37
+ pointer-events: none;
38
+ z-index: 0;
39
+ }
40
+
41
+ /* Header Styling */
42
+ .luxury-header {
43
+ text-align: center;
44
+ padding: 60px 20px 40px;
45
+ position: relative;
46
+ z-index: 1;
47
+ }
48
+
49
+ .luxury-header h1 {
50
+ font-family: 'Cormorant Garamond', serif !important;
51
+ font-size: 4.5em !important;
52
+ font-weight: 300 !important;
53
+ color: var(--cream) !important;
54
+ margin: 0 0 15px 0 !important;
55
+ letter-spacing: 3px !important;
56
+ text-transform: uppercase;
57
+ animation: fadeInDown 1s ease-out;
58
+ }
59
+
60
+ .luxury-header .subtitle {
61
+ font-size: 1.1em;
62
+ color: var(--gold);
63
+ letter-spacing: 4px;
64
+ text-transform: uppercase;
65
+ font-weight: 300;
66
+ margin-top: 10px;
67
+ animation: fadeInUp 1s ease-out 0.3s both;
68
+ }
69
+
70
+ .luxury-header .divider {
71
+ width: 100px;
72
+ height: 2px;
73
+ background: linear-gradient(90deg, transparent, var(--gold), transparent);
74
+ margin: 25px auto;
75
+ animation: expandWidth 1.2s ease-out 0.5s both;
76
+ }
77
+
78
+ @keyframes fadeInDown {
79
+ from {
80
+ opacity: 0;
81
+ transform: translateY(-30px);
82
+ }
83
+ to {
84
+ opacity: 1;
85
+ transform: translateY(0);
86
+ }
87
+ }
88
+
89
+ @keyframes fadeInUp {
90
+ from {
91
+ opacity: 0;
92
+ transform: translateY(30px);
93
+ }
94
+ to {
95
+ opacity: 1;
96
+ transform: translateY(0);
97
+ }
98
+ }
99
+
100
+ @keyframes expandWidth {
101
+ from {
102
+ width: 0;
103
+ }
104
+ to {
105
+ width: 100px;
106
+ }
107
+ }
108
+
109
+ /* Promotion Banner */
110
+ .promo-banner {
111
+ background: linear-gradient(135deg, var(--gold-dark) 0%, var(--gold) 100%);
112
+ color: var(--navy);
113
+ padding: 35px;
114
+ border-radius: 0;
115
+ margin: 30px 0;
116
+ text-align: center;
117
+ position: relative;
118
+ overflow: hidden;
119
+ box-shadow: 0 10px 40px var(--shadow-heavy);
120
+ animation: slideInFromTop 0.8s ease-out;
121
+ }
122
+
123
+ .promo-banner::before {
124
+ content: '';
125
+ position: absolute;
126
+ top: -50%;
127
+ left: -50%;
128
+ width: 200%;
129
+ height: 200%;
130
+ background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.15) 50%, transparent 70%);
131
+ animation: shimmer 3s infinite;
132
+ }
133
+
134
+ @keyframes shimmer {
135
+ 0% {
136
+ transform: translateX(-100%) translateY(-100%) rotate(45deg);
137
+ }
138
+ 100% {
139
+ transform: translateX(100%) translateY(100%) rotate(45deg);
140
+ }
141
+ }
142
+
143
+ @keyframes slideInFromTop {
144
+ from {
145
+ opacity: 0;
146
+ transform: translateY(-50px);
147
+ }
148
+ to {
149
+ opacity: 1;
150
+ transform: translateY(0);
151
+ }
152
+ }
153
+
154
+ .promo-banner .promo-title {
155
+ font-family: 'Cormorant Garamond', serif;
156
+ font-size: 1.4em;
157
+ font-weight: 600;
158
+ letter-spacing: 2px;
159
+ text-transform: uppercase;
160
+ margin-bottom: 10px;
161
+ }
162
+
163
+ .promo-banner .promo-message {
164
+ font-size: 2em;
165
+ font-weight: 600;
166
+ margin: 15px 0;
167
+ font-family: 'Cormorant Garamond', serif;
168
+ }
169
+
170
+ .promo-banner .promo-time {
171
+ font-size: 0.85em;
172
+ opacity: 0.8;
173
+ letter-spacing: 1px;
174
+ text-transform: uppercase;
175
+ }
176
+
177
+ /* Tab Styling */
178
+ .tabitem {
179
+ background: rgba(248, 246, 240, 0.05) !important;
180
+ border: none !important;
181
+ border-radius: 15px !important;
182
+ padding: 40px !important;
183
+ margin-top: 20px !important;
184
+ backdrop-filter: blur(10px);
185
+ }
186
+
187
+ button.selected {
188
+ background: var(--gold) !important;
189
+ color: var(--navy) !important;
190
+ font-weight: 600 !important;
191
+ border: none !important;
192
+ letter-spacing: 1px !important;
193
+ }
194
+
195
+ .tabs button {
196
+ color: var(--cream) !important;
197
+ font-size: 1em !important;
198
+ padding: 15px 35px !important;
199
+ border: 1px solid rgba(212, 175, 55, 0.3) !important;
200
+ background: transparent !important;
201
+ transition: all 0.3s ease !important;
202
+ text-transform: uppercase;
203
+ letter-spacing: 1.5px;
204
+ font-weight: 500;
205
+ }
206
+
207
+ .tabs button:hover {
208
+ background: rgba(212, 175, 55, 0.1) !important;
209
+ border-color: var(--gold) !important;
210
+ }
211
+
212
+ /* Hotel Cards - Luxury Table Styling */
213
+ .dataframe {
214
+ background: var(--cream) !important;
215
+ border-radius: 15px !important;
216
+ overflow: hidden !important;
217
+ box-shadow: 0 15px 50px var(--shadow-heavy) !important;
218
+ font-family: 'Montserrat', sans-serif !important;
219
+ border: 1px solid rgba(212, 175, 55, 0.2) !important;
220
+ }
221
+
222
+ .dataframe thead {
223
+ background: linear-gradient(135deg, var(--navy) 0%, var(--navy-light) 100%) !important;
224
+ }
225
+
226
+ .dataframe thead th {
227
+ color: var(--gold) !important;
228
+ font-weight: 600 !important;
229
+ text-transform: uppercase !important;
230
+ letter-spacing: 1.5px !important;
231
+ padding: 20px !important;
232
+ font-size: 0.85em !important;
233
+ border: none !important;
234
+ }
235
+
236
+ .dataframe tbody td {
237
+ padding: 25px 20px !important;
238
+ color: var(--navy) !important;
239
+ font-size: 0.95em !important;
240
+ border-bottom: 1px solid rgba(10, 24, 40, 0.08) !important;
241
+ transition: all 0.3s ease;
242
+ }
243
+
244
+ .dataframe tbody tr {
245
+ transition: all 0.3s ease;
246
+ }
247
+
248
+ .dataframe tbody tr:hover {
249
+ background: rgba(212, 175, 55, 0.08) !important;
250
+ transform: scale(1.01);
251
+ }
252
+
253
+ /* Input Styling */
254
+ .gr-box,
255
+ input,
256
+ textarea,
257
+ select {
258
+ background: rgba(248, 246, 240, 0.95) !important;
259
+ border: 1px solid rgba(212, 175, 55, 0.3) !important;
260
+ color: var(--navy) !important;
261
+ border-radius: 8px !important;
262
+ transition: all 0.3s ease !important;
263
+ font-family: 'Montserrat', sans-serif !important;
264
+ }
265
+
266
+ .gr-box:focus,
267
+ input:focus,
268
+ textarea:focus,
269
+ select:focus {
270
+ border-color: var(--gold) !important;
271
+ box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.15) !important;
272
+ outline: none !important;
273
+ }
274
+
275
+ label {
276
+ color: var(--cream) !important;
277
+ font-weight: 500 !important;
278
+ text-transform: uppercase !important;
279
+ letter-spacing: 1px !important;
280
+ font-size: 0.85em !important;
281
+ margin-bottom: 8px !important;
282
+ }
283
+
284
+ /* Radio Buttons */
285
+ .gr-radio {
286
+ background: transparent !important;
287
+ }
288
+
289
+ .gr-radio label {
290
+ color: var(--cream) !important;
291
+ padding: 12px 20px !important;
292
+ border: 1px solid rgba(212, 175, 55, 0.3) !important;
293
+ border-radius: 8px !important;
294
+ margin: 5px !important;
295
+ transition: all 0.3s ease !important;
296
+ cursor: pointer !important;
297
+ text-transform: none !important;
298
+ letter-spacing: 0.5px !important;
299
+ }
300
+
301
+ .gr-radio label:hover {
302
+ background: rgba(212, 175, 55, 0.1) !important;
303
+ border-color: var(--gold) !important;
304
+ }
305
+
306
+ .gr-radio input:checked + label {
307
+ background: var(--gold) !important;
308
+ color: var(--navy) !important;
309
+ border-color: var(--gold) !important;
310
+ font-weight: 600 !important;
311
+ }
312
+
313
+ /* Buttons */
314
+ .gr-button {
315
+ background: linear-gradient(135deg, var(--gold-dark) 0%, var(--gold) 100%) !important;
316
+ color: var(--navy) !important;
317
+ border: none !important;
318
+ padding: 15px 35px !important;
319
+ font-weight: 600 !important;
320
+ text-transform: uppercase !important;
321
+ letter-spacing: 2px !important;
322
+ border-radius: 8px !important;
323
+ transition: all 0.4s ease !important;
324
+ box-shadow: 0 5px 20px rgba(212, 175, 55, 0.3) !important;
325
+ font-size: 0.9em !important;
326
+ position: relative !important;
327
+ overflow: hidden !important;
328
+ }
329
+
330
+ .gr-button::before {
331
+ content: '';
332
+ position: absolute;
333
+ top: 50%;
334
+ left: 50%;
335
+ width: 0;
336
+ height: 0;
337
+ border-radius: 50%;
338
+ background: rgba(255, 255, 255, 0.3);
339
+ transform: translate(-50%, -50%);
340
+ transition: width 0.6s, height 0.6s;
341
+ }
342
+
343
+ .gr-button:hover::before {
344
+ width: 300px;
345
+ height: 300px;
346
+ }
347
+
348
+ .gr-button:hover {
349
+ transform: translateY(-2px) !important;
350
+ box-shadow: 0 8px 30px rgba(212, 175, 55, 0.5) !important;
351
+ }
352
+
353
+ .gr-button-secondary {
354
+ background: transparent !important;
355
+ border: 2px solid var(--gold) !important;
356
+ color: var(--gold) !important;
357
+ }
358
+
359
+ .gr-button-secondary:hover {
360
+ background: var(--gold) !important;
361
+ color: var(--navy) !important;
362
+ }
363
+
364
+ /* Info Box */
365
+ .info-box {
366
+ background: rgba(212, 175, 55, 0.1);
367
+ border-left: 4px solid var(--gold);
368
+ padding: 20px 25px;
369
+ border-radius: 0 8px 8px 0;
370
+ color: var(--cream);
371
+ margin: 20px 0;
372
+ font-size: 0.95em;
373
+ line-height: 1.6;
374
+ backdrop-filter: blur(5px);
375
+ }
376
+
377
+ .info-box strong {
378
+ color: var(--gold);
379
+ font-weight: 600;
380
+ }
381
+
382
+ /* Dashboard Cards */
383
+ .dashboard-section {
384
+ background: rgba(248, 246, 240, 0.05);
385
+ border: 1px solid rgba(212, 175, 55, 0.2);
386
+ border-radius: 15px;
387
+ padding: 30px;
388
+ margin: 20px 0;
389
+ backdrop-filter: blur(10px);
390
+ }
391
+
392
+ .dashboard-title {
393
+ font-family: 'Cormorant Garamond', serif;
394
+ font-size: 1.8em;
395
+ color: var(--gold);
396
+ margin-bottom: 10px;
397
+ font-weight: 600;
398
+ letter-spacing: 1px;
399
+ }
400
+
401
+ .dashboard-subtitle {
402
+ color: var(--cream);
403
+ opacity: 0.8;
404
+ font-size: 0.9em;
405
+ letter-spacing: 1px;
406
+ text-transform: uppercase;
407
+ margin-bottom: 25px;
408
+ }
409
+
410
+ /* Footer */
411
+ .luxury-footer {
412
+ text-align: center;
413
+ margin-top: 80px;
414
+ padding: 40px 20px;
415
+ border-top: 1px solid rgba(212, 175, 55, 0.2);
416
+ color: var(--cream);
417
+ opacity: 0.6;
418
+ font-size: 0.85em;
419
+ letter-spacing: 1.5px;
420
+ text-transform: uppercase;
421
+ }
422
+
423
+ /* Status Badge */
424
+ .status-available {
425
+ color: #2ECC71;
426
+ font-weight: 600;
427
+ }
428
+
429
+ .status-occupied {
430
+ color: #E74C3C;
431
+ font-weight: 600;
432
+ }
433
+
434
+ /* Markdown Styling */
435
+ .markdown-text {
436
+ color: var(--cream) !important;
437
+ line-height: 1.8 !important;
438
+ }
439
+
440
+ .markdown-text h3 {
441
+ font-family: 'Cormorant Garamond', serif !important;
442
+ color: var(--gold) !important;
443
+ font-size: 1.6em !important;
444
+ margin-bottom: 15px !important;
445
+ font-weight: 600 !important;
446
+ }
447
+
448
+ .markdown-text p {
449
+ color: var(--cream) !important;
450
+ opacity: 0.9 !important;
451
+ }
452
+
453
+ /* Success Message */
454
+ .success-message {
455
+ background: linear-gradient(135deg, rgba(46, 204, 113, 0.2) 0%, rgba(46, 204, 113, 0.1) 100%);
456
+ border: 1px solid rgba(46, 204, 113, 0.5);
457
+ color: #2ECC71;
458
+ padding: 20px;
459
+ border-radius: 8px;
460
+ text-align: center;
461
+ font-weight: 600;
462
+ letter-spacing: 1px;
463
+ animation: slideInFromRight 0.5s ease-out;
464
+ }
465
+
466
+ @keyframes slideInFromRight {
467
+ from {
468
+ opacity: 0;
469
+ transform: translateX(30px);
470
+ }
471
+ to {
472
+ opacity: 1;
473
+ transform: translateX(0);
474
+ }
475
+ }
476
+
477
+ /* Responsive Design */
478
+ @media (max-width: 768px) {
479
+ .luxury-header h1 {
480
+ font-size: 2.5em !important;
481
+ }
482
+
483
+ .promo-banner .promo-message {
484
+ font-size: 1.4em;
485
+ }
486
+
487
+ .tabs button {
488
+ padding: 12px 20px !important;
489
+ font-size: 0.85em !important;
490
+ }
491
+ }
492
+
493
+ /* Scrollbar Styling */
494
+ ::-webkit-scrollbar {
495
+ width: 10px;
496
+ }
497
+
498
+ ::-webkit-scrollbar-track {
499
+ background: var(--navy);
500
+ }
501
+
502
+ ::-webkit-scrollbar-thumb {
503
+ background: var(--gold);
504
+ border-radius: 5px;
505
+ }
506
+
507
+ ::-webkit-scrollbar-thumb:hover {
508
+ background: var(--gold-dark);
509
+ }
510
+ """
511
+
512
+ # --- ENHANCED DATA ---
513
+ data = {
514
+ "🏨 Hotel Name": [
515
+ "Coral Reef View Resort",
516
+ "Male' Grand Stay Hotel",
517
+ "Villi Blue Inn",
518
+ "Azure Lagoon Suites",
519
+ "Paradise Isle Boutique"
520
+ ],
521
+ "πŸ“ Location": [
522
+ "Hulhumale Phase 1",
523
+ "Male' City Center",
524
+ "Villingili Island",
525
+ "Hulhumale Phase 2",
526
+ "Male' Waterfront"
527
+ ],
528
+ "πŸ’° Price/Night": [
529
+ "850 MVR",
530
+ "1,100 MVR",
531
+ "650 MVR",
532
+ "950 MVR",
533
+ "1,250 MVR"
534
+ ],
535
+ "⭐ Rating": [
536
+ "4.8/5.0",
537
+ "4.6/5.0",
538
+ "4.5/5.0",
539
+ "4.9/5.0",
540
+ "4.7/5.0"
541
+ ],
542
+ "βœ… Status": [
543
+ "Available",
544
+ "Available",
545
+ "Occupied",
546
+ "Available",
547
+ "Available"
548
+ ],
549
+ "πŸ“ž Direct Contact": [
550
+ "+960 777-1234",
551
+ "+960 778-5678",
552
+ "+960 779-9101",
553
+ "+960 780-2345",
554
+ "+960 781-6789"
555
+ ]
556
+ }
557
+ df = pd.DataFrame(data)
558
+
559
+ # --- FUNCTIONS ---
560
+ def filter_hotels(location):
561
+ if location == "All":
562
+ return df
563
+ elif location == "Male' City":
564
+ filtered = df[df["πŸ“ Location"].str.contains("Male'", case=False, na=False)]
565
+ elif location == "Hulhumale":
566
+ filtered = df[df["πŸ“ Location"].str.contains("Hulhumale", case=False, na=False)]
567
+ elif location == "Villingili":
568
+ filtered = df[df["πŸ“ Location"].str.contains("Villingili", case=False, na=False)]
569
+ else:
570
+ filtered = df
571
+ return filtered
572
+
573
+ def refresh_data():
574
+ return df, "✨ Availability refreshed successfully!"
575
+
576
+ def send_promotion(hotel, message):
577
+ if not hotel or not message:
578
+ return gr.update(visible=False), "⚠️ Please select a hotel and enter a promotion message."
579
+
580
+ time_now = datetime.now().strftime("%I:%M %p")
581
+ date_now = datetime.now().strftime("%B %d, %Y")
582
+
583
+ promo_html = f"""
584
+ <div class='promo-banner'>
585
+ <div class='promo-title'>⚑ Exclusive Flash Offer</div>
586
+ <div class='promo-message'>{message}</div>
587
+ <div style='margin: 15px 0; font-size: 1.1em; font-weight: 600;'>πŸ“ {hotel}</div>
588
+ <div class='promo-time'>Posted {time_now} β€’ {date_now} β€’ Valid Tonight Only</div>
589
+ </div>
590
+ """
591
+
592
+ return gr.update(value=promo_html, visible=True), f"<div class='success-message'>βœ… Promotion successfully broadcast to all users!</div>"
593
+
594
+ def update_listing(hotel, status, price):
595
+ if not hotel:
596
+ return "⚠️ Please select your property first."
597
+
598
+ success_msg = f"""
599
+ <div class='success-message'>
600
+ βœ… <strong>{hotel}</strong> updated successfully!<br>
601
+ Status: {status} | Price: {price} MVR/night
602
+ </div>
603
+ """
604
+ return success_msg
605
+
606
+ # --- UI LAYOUT ---
607
+ with gr.Blocks(css=custom_css, title="Hulhumale Luxury Hotel Direct", theme=gr.themes.Base()) as demo:
608
+
609
+ # Luxury Header
610
+ gr.HTML("""
611
+ <div class='luxury-header'>
612
+ <h1>HULHUMALE DIRECT</h1>
613
+ <div class='divider'></div>
614
+ <div class='subtitle'>Luxury Accommodations β€’ Direct Booking β€’ Zero Commission</div>
615
+ </div>
616
+ """)
617
+
618
+ # Promotion Display (Hidden by default)
619
+ promo_display = gr.HTML(visible=False)
620
+
621
+ with gr.Tabs() as tabs:
622
+
623
+ # --- TRAVELER TAB ---
624
+ with gr.Tab("πŸ” Discover Your Stay"):
625
+
626
+ gr.HTML("<div class='dashboard-section'>")
627
+
628
+ with gr.Row():
629
+ filter_location = gr.Radio(
630
+ choices=["All", "Male' City", "Hulhumale", "Villingili"],
631
+ value="All",
632
+ label="Filter by Destination",
633
+ elem_classes="location-filter"
634
+ )
635
+
636
+ room_table = gr.DataFrame(
637
+ value=df,
638
+ interactive=False,
639
+ label="Available Properties",
640
+ wrap=True
641
+ )
642
+
643
+ gr.HTML("""
644
+ <div class='info-box'>
645
+ <strong>🌟 How to Book:</strong> Contact hotels directly using the numbers listed above.
646
+ Speak with owners personally, negotiate rates, and enjoy commission-free bookings.
647
+ All properties are verified and family-operated.
648
+ </div>
649
+ """)
650
+
651
+ with gr.Row():
652
+ refresh_btn = gr.Button("πŸ”„ Refresh Availability", variant="secondary", size="lg")
653
+ refresh_status = gr.Markdown("")
654
+
655
+ gr.HTML("</div>")
656
+
657
+ # --- OWNER TAB ---
658
+ with gr.Tab("βš™οΈ Property Management Portal"):
659
+
660
+ gr.HTML("""
661
+ <div class='dashboard-section'>
662
+ <div class='dashboard-title'>Welcome to Your Dashboard</div>
663
+ <div class='dashboard-subtitle'>Premium Client Portal β€’ 350 MVR per Month</div>
664
+ </div>
665
+ """)
666
+
667
+ with gr.Row():
668
+ with gr.Column(scale=1):
669
+ gr.HTML("<div class='dashboard-section'>")
670
+ gr.Markdown("### πŸ“Š Update Your Listing")
671
+
672
+ owner_hotel = gr.Dropdown(
673
+ choices=list(data["🏨 Hotel Name"]),
674
+ label="Select Your Property",
675
+ value=None
676
+ )
677
+
678
+ new_status = gr.Radio(
679
+ choices=["Available", "Occupied"],
680
+ label="Current Availability Status",
681
+ value="Available"
682
+ )
683
+
684
+ update_price = gr.Textbox(
685
+ label="Tonight's Rate (MVR)",
686
+ placeholder="e.g., 850",
687
+ value=""
688
+ )
689
+
690
+ update_btn = gr.Button("πŸ’Ύ Update Live Listing", variant="primary", size="lg")
691
+ update_status = gr.HTML("")
692
+
693
+ gr.HTML("</div>")
694
+
695
+ with gr.Column(scale=1):
696
+ gr.HTML("<div class='dashboard-section'>")
697
+ gr.Markdown("### πŸ“£ Marketing & Promotions")
698
+
699
+ gr.HTML("""
700
+ <div class='info-box'>
701
+ Broadcast instant promotions to all active users browsing the platform.
702
+ Perfect for last-minute deals, early bird specials, or flash sales.
703
+ </div>
704
+ """)
705
+
706
+ promo_hotel = gr.Dropdown(
707
+ choices=list(data["🏨 Hotel Name"]),
708
+ label="Property Name",
709
+ value=None
710
+ )
711
+
712
+ promo_text = gr.Textbox(
713
+ label="Promotion Message",
714
+ placeholder="e.g., 20% OFF for airport arrivals before midnight!",
715
+ lines=3
716
+ )
717
+
718
+ promo_btn = gr.Button("πŸš€ Broadcast Promotion", variant="primary", size="lg")
719
+ promo_status = gr.HTML("")
720
+
721
+ gr.HTML("</div>")
722
+
723
+ # --- EVENT HANDLERS ---
724
+ filter_location.change(
725
+ fn=filter_hotels,
726
+ inputs=[filter_location],
727
+ outputs=[room_table]
728
+ )
729
+
730
+ refresh_btn.click(
731
+ fn=refresh_data,
732
+ inputs=[],
733
+ outputs=[room_table, refresh_status]
734
+ )
735
+
736
+ update_btn.click(
737
+ fn=update_listing,
738
+ inputs=[owner_hotel, new_status, update_price],
739
+ outputs=[update_status]
740
+ )
741
+
742
+ promo_btn.click(
743
+ fn=send_promotion,
744
+ inputs=[promo_hotel, promo_text],
745
+ outputs=[promo_display, promo_status]
746
+ )
747
+
748
+ # Luxury Footer
749
+ gr.HTML("""
750
+ <div class='luxury-footer'>
751
+ <div style='margin-bottom: 10px;'>✦ ✦ ✦</div>
752
+ Hulhumale Direct β€’ Connecting Travelers With Local Hospitality Since 2024
753
+ <div style='margin-top: 10px; font-size: 0.75em; opacity: 0.5;'>
754
+ A Premium Local Marketplace
755
+ </div>
756
+ </div>
757
+ """)
758
+
759
+ # Launch the app
760
+ if __name__ == "__main__":
761
+ demo.launch(share=False)