769 lines
44 KiB
Plaintext
769 lines
44 KiB
Plaintext
@page "/vas"
|
|
@rendermode InteractiveServer
|
|
|
|
@using System.Collections.Generic
|
|
@using System.Linq
|
|
@using System.Threading.Tasks
|
|
|
|
<div class="bg-[#f4f7fb] min-h-screen text-[#1f2a3a] font-sans antialiased p-6 md:p-8">
|
|
|
|
@* Page Header Area *@
|
|
<div class="flex flex-col sm:flex-row sm:items-end justify-between mb-6 gap-4">
|
|
<div class="text-left">
|
|
<h2 class="text-2xl font-bold tracking-tight text-[#003b71]">Value-Added Services (VAS)</h2>
|
|
<p class="text-xs text-[#5a6577] mt-1">Global destination intelligence, live travel risk feeds, and premium VIP traveller concierge support</p>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<button @onclick="OpenVipBookingModal"
|
|
class="inline-flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-semibold bg-[#0066b3] text-white hover:bg-[#003b71] transition-colors shadow-sm cursor-pointer">
|
|
<!-- Sparkles Star SVG -->
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.98 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" />
|
|
</svg>
|
|
Book VIP Lounge & Concierge
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
@* Alert / Feedback Notification Banner *@
|
|
@if (!string.IsNullOrEmpty(AlertFeedback))
|
|
{
|
|
<div class="mb-6 p-4 bg-[#effaf3] border-l-4 border-[#22a06b] rounded-r-lg text-sm text-[#1b8054] flex items-center gap-2.5 shadow-sm transition-all text-left">
|
|
<!-- Alert Icon SVG -->
|
|
<svg class="w-5 h-5 shrink-0 text-[#22a06b]" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.742 0 01-1.043 5.636 3.741 3.741 0 01-5.076.63 3.75 3.75 0 01-3.758-3.848c-.021-.013-.042-.027-.063-.04A4.5 4.5 0 113.6 8.775 5.25 5.25 0 0113.8 6.44a3 3 0 013.758 3.848A3.713 3.752 0 0118 12.75" />
|
|
</svg>
|
|
<span class="font-medium">@AlertFeedback</span>
|
|
</div>
|
|
}
|
|
|
|
@* Quick intelligence overview widgets *@
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
|
|
@foreach (var dest in IntelligentDestinations)
|
|
{
|
|
<div class="bg-white border border-[#e4eaf2] rounded-xl p-5 shadow-sm text-left flex flex-col justify-between transition-all hover:shadow-md relative overflow-hidden before:content-[''] before:absolute before:top-0 before:left-0 before:w-1 before:h-full @(dest.RiskLevel == "High" ? "before:bg-[#dc2626]" : dest.RiskLevel == "Moderate" ? "before:bg-[#f4a300]" : "before:bg-[#22a06b]")">
|
|
<div>
|
|
<div class="flex justify-between items-start mb-3">
|
|
<div class="text-left">
|
|
<h3 class="text-[16px] font-bold text-[#1f2a3a]">@dest.City, @dest.CountryCode</h3>
|
|
<p class="text-[11px] text-[#8a96a8] mt-0.5">Traveller: @dest.Traveller</p>
|
|
</div>
|
|
<span class="text-3xl">@dest.WeatherEmoji</span>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-3 mb-4">
|
|
<div class="text-2xl font-bold tracking-tight text-[#003b71]">@dest.Temperature°C</div>
|
|
<div class="text-xs text-[#5a6577]">
|
|
<span class="font-semibold block text-gray-800">@dest.WeatherCondition</span>
|
|
<span>Local time: @dest.LocalTime</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-2 border-t border-[#e4eaf2] pt-3 text-xs">
|
|
<div class="flex justify-between items-center py-1">
|
|
<span class="text-[#8a96a8]">Visa Requirement:</span>
|
|
<span class="px-2 py-0.5 rounded text-[10px] font-bold @(dest.VisaStatus == "eVisa Required" ? "bg-[#fff4dd] text-[#b06b00]" : dest.VisaStatus == "Standard Visa" ? "bg-[#fde5e8] text-[#dc2626]" : "bg-[#e3f6ed] text-[#22a06b]")">
|
|
@dest.VisaStatus
|
|
</span>
|
|
</div>
|
|
<div class="flex justify-between items-center py-1">
|
|
<span class="text-[#8a96a8]">Health Warning:</span>
|
|
<span class="font-medium text-[#1f2a3a]">@dest.HealthWarning</span>
|
|
</div>
|
|
<div class="flex justify-between items-center py-1">
|
|
<span class="text-[#8a96a8]">Global Safety Risk:</span>
|
|
<span class="font-bold uppercase text-[11px] @(dest.RiskLevel == "High" ? "text-[#dc2626]" : dest.RiskLevel == "Moderate" ? "text-[#b06b00]" : "text-[#22a06b]")">
|
|
@dest.RiskLevel
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4 pt-3 border-t border-[#e4eaf2] flex gap-2">
|
|
<button @onclick="() => ExploreDestinationInDepth(dest)"
|
|
class="w-full text-center py-1.5 bg-[#f4f7fb] hover:bg-[#e2eefb] text-[#0066b3] text-xs font-bold rounded transition">
|
|
Explore Destination Guide
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@* Risk Advisories and Services Splitting grid *@
|
|
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6 mb-6">
|
|
|
|
@* Live Travel Advisories Feed - Left Column (lg:col-span-7) *@
|
|
<div class="lg:col-span-7 bg-white border border-[#e4eaf2] rounded-[10px] p-5 shadow-sm text-left flex flex-col justify-between">
|
|
<div>
|
|
<div class="flex items-center justify-between mb-4 pb-2 border-b border-[#e4eaf2]">
|
|
<div>
|
|
<h3 class="text-base font-bold text-[#003b71]">Global Safety & Risk Feed</h3>
|
|
<p class="text-xs text-[#8a96a8] mt-0.5">Real-time alerts synced through TMT global risk metrics and WMO meteorology bulletins</p>
|
|
</div>
|
|
<div class="flex gap-1">
|
|
<button @onclick='() => SetAlertFilter("All")'
|
|
class="px-2 py-0.5 rounded text-[10px] font-bold transition @(SelectedAlertFilter == "All" ? "bg-[#0066b3] text-white" : "bg-[#f4f7fb] text-gray-500 hover:bg-gray-200")">
|
|
All
|
|
</button>
|
|
<button @onclick='() => SetAlertFilter("Critical")'
|
|
class="px-2 py-0.5 rounded text-[10px] font-bold transition @(SelectedAlertFilter == "Critical" ? "bg-[#dc2626] text-white" : "bg-[#f4f7fb] text-gray-500 hover:bg-gray-200")">
|
|
High Risk
|
|
</button>
|
|
<button @onclick='() => SetAlertFilter("Info")'
|
|
class="px-2 py-0.5 rounded text-[10px] font-bold transition @(SelectedAlertFilter == "Info" ? "bg-[#00a3a1] text-white" : "bg-[#f4f7fb] text-gray-500 hover:bg-gray-200")">
|
|
Advisory
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-3.5 max-h-[360px] overflow-y-auto pr-1">
|
|
@foreach (var alert in FilteredRiskAlerts)
|
|
{
|
|
<div class="p-3 border rounded-xl transition-all hover:bg-[#fafcfe] flex items-start gap-3 @(alert.Severity == "Critical" ? "bg-[#fdeef0] border-[#f8b4b4]" : alert.Severity == "Warning" ? "bg-[#fff9ec] border-[#fbd897]" : "bg-[#f3f8fd] border-[#b4d3f5]")">
|
|
<div class="shrink-0 mt-1">
|
|
@if (alert.Severity == "Critical")
|
|
{
|
|
<!-- Critical Red Alert SVG -->
|
|
<svg class="w-5 h-5 text-[#dc2626]" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
|
|
</svg>
|
|
}
|
|
else if (alert.Severity == "Warning")
|
|
{
|
|
<!-- Warning Orange SVG -->
|
|
<svg class="w-5 h-5 text-[#f4a300]" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
}
|
|
else
|
|
{
|
|
<!-- Info Blue SVG -->
|
|
<svg class="w-5 h-5 text-[#0066b3]" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 111.063.852l-.708 2.836a.75.75 0 001.063.852l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" />
|
|
</svg>
|
|
}
|
|
</div>
|
|
|
|
<div class="flex-1 text-xs">
|
|
<div class="flex justify-between items-baseline mb-0.5">
|
|
<h4 class="font-bold text-[#101827]">@alert.Title</h4>
|
|
<span class="text-[9px] font-mono text-gray-500">@alert.PublishedTime</span>
|
|
</div>
|
|
<p class="text-[#5a6577] mt-1">@alert.Description</p>
|
|
<div class="mt-2 flex items-center justify-between border-t border-gray-200/50 pt-2 text-[10px]">
|
|
<span class="font-bold text-[#003b71]">Category: @alert.Category</span>
|
|
<button @onclick="() => SimulateNotifyTravellers(alert)"
|
|
class="px-2 py-0.5 bg-white hover:bg-gray-100 border border-gray-300 text-gray-700 font-bold rounded shadow-xs">
|
|
Notify Affected Travellers
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4 pt-3.5 border-t border-[#e4eaf2] text-center text-xs text-gray-500">
|
|
<span>Alert definitions are updated dynamically via real-time international GDS PNR telemetry streams.</span>
|
|
</div>
|
|
</div>
|
|
|
|
@* Value-added Services At a Glance - Right Column (lg:col-span-5) *@
|
|
<div class="lg:col-span-5 space-y-6">
|
|
|
|
<div class="bg-white border border-[#e4eaf2] rounded-[10px] p-5 shadow-sm text-left">
|
|
<div class="mb-4">
|
|
<h3 class="text-base font-bold text-[#003b71]">Always-On Concierge Services</h3>
|
|
<p class="text-xs text-[#8a96a8] mt-0.5">Select a capability to simulate corporate booking and guidelines</p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3.5">
|
|
|
|
@* Tile 1: Airport VIP Lounge *@
|
|
<div @onclick="OpenVipBookingModal"
|
|
class="p-3 border border-[#e4eaf2] rounded-xl hover:border-[#0066b3] hover:bg-[#f4f8fc]/40 transition-all cursor-pointer group">
|
|
<div class="h-8 w-8 rounded-lg bg-[#e2eefb] text-[#0066b3] flex items-center justify-center mb-2.5 group-hover:bg-[#0066b3] group-hover:text-white transition-colors">
|
|
<!-- Star SVG -->
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.98 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" />
|
|
</svg>
|
|
</div>
|
|
<h4 class="text-xs font-bold text-[#1f2a3a] group-hover:text-[#0066b3] transition-colors">Airport Lounges</h4>
|
|
<p class="text-[10px] text-[#8a96a8] mt-0.5">Pre-book SLOW & international Bidvest airport lounges.</p>
|
|
</div>
|
|
|
|
@* Tile 2: Shuttle Transfer Calcs *@
|
|
<div @onclick="OpenShuttleModal"
|
|
class="p-3 border border-[#e4eaf2] rounded-xl hover:border-[#00a3a1] hover:bg-[#f2faf9]/50 transition-all cursor-pointer group">
|
|
<div class="h-8 w-8 rounded-lg bg-[#e0f7f6] text-[#00a3a1] flex items-center justify-center mb-2.5 group-hover:bg-[#00a3a1] group-hover:text-white transition-colors">
|
|
<!-- Truck/Car SVG -->
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 18.75a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h6m-9 0H3.375a1.125 1.125 0 01-1.125-1.125V14.25m17.25 4.5a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h1.125c.621 0 1.129-.504 1.129-1.125V11.25M12 9V5.25a2.25 2.25 0 00-2.25-2.25H6.75A2.25 2.25 0 004.5 5.25v2.25m9-1.5h.008v.008H13.5V6zm2.25 2.25h.008v.008H15.75V8.25zm2.25 2.25h.008v.008H18V10.5z" />
|
|
</svg>
|
|
</div>
|
|
<h4 class="text-xs font-bold text-[#1f2a3a] group-hover:text-[#00a3a1] transition-colors">Shuttle Transfers</h4>
|
|
<p class="text-[10px] text-[#8a96a8] mt-0.5">Calculate airport transfers, shuttles & express trains.</p>
|
|
</div>
|
|
|
|
@* Tile 3: Places of Interest *@
|
|
<div @onclick="OpenCultureModal"
|
|
class="p-3 border border-[#e4eaf2] rounded-xl hover:border-[#22a06b] hover:bg-[#edfbf4]/50 transition-all cursor-pointer group">
|
|
<div class="h-8 w-8 rounded-lg bg-[#e3f6ed] text-[#22a06b] flex items-center justify-center mb-2.5 group-hover:bg-[#22a06b] group-hover:text-white transition-colors">
|
|
<!-- Compass/Globe SVG -->
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<circle cx="12" cy="12" r="10" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 2.25v2.25m0 15v2.25M4.5 12h2.25M17.25 12H21" />
|
|
</svg>
|
|
</div>
|
|
<h4 class="text-xs font-bold text-[#1f2a3a] group-hover:text-[#22a06b] transition-colors">Culture Guides</h4>
|
|
<p class="text-[10px] text-[#8a96a8] mt-0.5">Corporate travel orientations & localized city guides.</p>
|
|
</div>
|
|
|
|
@* Tile 4: Health Warnings Tracker *@
|
|
<div @onclick="OpenHealthTrackerModal"
|
|
class="p-3 border border-[#e4eaf2] rounded-xl hover:border-[#f4a300] hover:bg-[#fffbf2]/50 transition-all cursor-pointer group">
|
|
<div class="h-8 w-8 rounded-lg bg-[#fff4dd] text-[#f4a300] flex items-center justify-center mb-2.5 group-hover:bg-[#f4a300] group-hover:text-white transition-colors">
|
|
<!-- Shield Exclamation SVG -->
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.742 0 01-1.043 5.636 3.741 3.741 0 01-5.076.63 3.75 3.75 0 01-3.758-3.848c-.021-.013-.042-.027-.063-.04A4.5 4.5 0 113.6 8.775 5.25 5.25 0 0113.8 6.44a3 3 0 013.758 3.848A3.713 3.752 0 0118 12.75" />
|
|
</svg>
|
|
</div>
|
|
<h4 class="text-xs font-bold text-[#1f2a3a] group-hover:text-[#f4a300] transition-colors">Health & Vaccines</h4>
|
|
<p class="text-[10px] text-[#8a96a8] mt-0.5">WHO, CDC Yellow Fever certificate lookup checker.</p>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
@* Travel Help Desk Assistance Box *@
|
|
<div class="p-4 bg-gradient-to-br from-[#003b71] to-[#0066b3] text-white rounded-xl text-left space-y-3.5 shadow-lg relative overflow-hidden">
|
|
<div class="absolute -right-8 -bottom-8 w-24 h-24 bg-white/5 rounded-full"></div>
|
|
<div>
|
|
<span class="text-[9px] bg-white/20 text-[#00d4d1] px-2 py-0.5 rounded-full font-bold uppercase tracking-wider">Premium Support</span>
|
|
<h3 class="text-sm font-bold mt-1.5">24/7 Global Emergency Assistance</h3>
|
|
<p class="text-[11px] text-blue-100 mt-1">If a traveller gets stranded, requires flight changes, or encounters medical events, our emergency assistance room handles all fast-tracks instantly.</p>
|
|
</div>
|
|
<div class="flex items-center justify-between text-xs pt-1 border-t border-white/10">
|
|
<div>
|
|
<span class="block text-[9px] text-blue-200">Local Help-desk</span>
|
|
<span class="font-mono font-semibold">0800 TMT HELP</span>
|
|
</div>
|
|
<div>
|
|
<span class="block text-[9px] text-blue-200">International Hotline</span>
|
|
<span class="font-mono font-semibold">+27 82 555 4400</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
@* ===================== MODAL DIALOGS ===================== *@
|
|
|
|
@* 1. Book VIP Lounge & Concierge Modal *@
|
|
@if (ShowVipBookingModal)
|
|
{
|
|
<div class="fixed inset-0 bg-[#1f2a3a]/40 backdrop-blur-xs z-50 flex items-center justify-center p-4">
|
|
<div class="bg-white rounded-xl border border-[#e4eaf2] shadow-xl max-w-md w-full p-6 text-left">
|
|
<div class="flex items-center justify-between mb-4 pb-2 border-b border-[#e4eaf2]">
|
|
<div>
|
|
<h3 class="text-base font-bold text-[#003b71]">Book Premium VIP Services</h3>
|
|
<p class="text-xs text-[#8a96a8] mt-0.5">Fast-track security and airport lounge clearance for SAQA travellers</p>
|
|
</div>
|
|
<button @onclick="CloseVipBookingModal" class="text-[#8a96a8] hover:text-[#1f2a3a] cursor-pointer">
|
|
<!-- Close SVG -->
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="space-y-4 text-xs">
|
|
<div>
|
|
<label class="block font-bold text-gray-700 uppercase mb-1">Select Traveller</label>
|
|
<select @bind="VipSelectedTraveller" class="w-full px-3 py-2 border border-gray-300 rounded-lg bg-[#f4f7fb]">
|
|
<option value="J. Mokoena">J. Mokoena (Executive)</option>
|
|
<option value="T. Maluleke">T. Maluleke (Director)</option>
|
|
<option value="A. Dlamini">A. Dlamini (Senior Manager)</option>
|
|
<option value="S. Pillay">S. Pillay (Coordinator)</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="block font-bold text-gray-700 uppercase mb-1">Departure Airport</label>
|
|
<select @bind="VipSelectedAirport" class="w-full px-3 py-2 border border-gray-300 rounded-lg bg-[#f4f7fb]">
|
|
<option value="OR Tambo (JNB)">OR Tambo JNB</option>
|
|
<option value="Cape Town (CPT)">Cape Town CPT</option>
|
|
<option value="King Shaka (DUR)">King Shaka DUR</option>
|
|
<option value="Heathrow (LHR)">London Heathrow LHR</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block font-bold text-gray-700 uppercase mb-1">Service Level</label>
|
|
<select @bind="VipServiceLevel" class="w-full px-3 py-2 border border-gray-300 rounded-lg bg-[#f4f7fb]">
|
|
<option value="Airport Lounge Access">Airport Lounge Access Only</option>
|
|
<option value="Fast-track + Lounge">Fast-track + Lounge Clearance</option>
|
|
<option value="Full Concierge Meet & Greet">Full VIP Meet & Greet Meetup</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block font-bold text-gray-700 uppercase mb-1">Flight Departure Date</label>
|
|
<input type="date" @bind="VipFlightDate" class="w-full px-3 py-2 border border-gray-300 rounded-lg bg-[#f4f7fb]" />
|
|
</div>
|
|
|
|
<div class="p-3 bg-[#e2eefb] text-[#0066b3] rounded-lg">
|
|
<p class="font-bold flex items-center gap-1.5 mb-1">
|
|
<!-- Info SVG -->
|
|
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<circle cx="12" cy="12" r="10" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 16v-4m0-4h.01" />
|
|
</svg>
|
|
SAQA Travel Policy Check
|
|
</p>
|
|
<p>Airport lounge clearances are automatically covered for executive travellers. Other staff require line-manager travel authorization justification files linked to their PO.</p>
|
|
</div>
|
|
|
|
<div class="flex gap-2 justify-end pt-3 border-t border-[#e4eaf2]">
|
|
<button @onclick="CloseVipBookingModal" class="px-4 py-2 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 font-semibold text-gray-700">
|
|
Cancel
|
|
</button>
|
|
<button @onclick="ProcessVipBooking" class="px-4 py-2 bg-[#0066b3] hover:bg-[#003b71] text-white font-semibold rounded-lg shadow-sm">
|
|
Issue Concierge Pass
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@* 2. Public Transport & Shuttle Calculator Modal *@
|
|
@if (ShowShuttleModal)
|
|
{
|
|
<div class="fixed inset-0 bg-[#1f2a3a]/40 backdrop-blur-xs z-50 flex items-center justify-center p-4">
|
|
<div class="bg-white rounded-xl border border-[#e4eaf2] shadow-xl max-w-md w-full p-6 text-left">
|
|
<div class="flex items-center justify-between mb-4 pb-2 border-b border-[#e4eaf2]">
|
|
<div>
|
|
<h3 class="text-base font-bold text-[#003b71]">Airport Shuttle Transfer Calculator</h3>
|
|
<p class="text-xs text-[#8a96a8] mt-0.5">Calculate corporate transfers vs. public options at destination</p>
|
|
</div>
|
|
<button @onclick="CloseShuttleModal" class="text-[#8a96a8] hover:text-[#1f2a3a] cursor-pointer">
|
|
<!-- Close SVG -->
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="space-y-4 text-xs">
|
|
<div>
|
|
<label class="block font-bold text-gray-700 uppercase mb-1">Select Destination City</label>
|
|
<select @bind="SelectedCalcCity" class="w-full px-3 py-2 border border-gray-300 rounded-lg bg-[#f4f7fb]">
|
|
<option value="London">London (Heathrow LHR)</option>
|
|
<option value="Nairobi">Nairobi (Jomo Kenyatta NBO)</option>
|
|
<option value="Cape Town">Cape Town (CPT)</option>
|
|
</select>
|
|
</div>
|
|
|
|
@{
|
|
var options = GetTransferOptions(SelectedCalcCity);
|
|
}
|
|
|
|
<div class="space-y-2 border border-dashed border-[#e4eaf2] p-3 rounded-lg bg-[#fafcfe]">
|
|
<div class="font-bold text-[#003b71] mb-2">Cost Comparison Matrix (converted to ZAR)</div>
|
|
@foreach (var opt in options)
|
|
{
|
|
<div class="flex justify-between items-center py-1.5 border-b border-gray-100 last:border-none">
|
|
<div>
|
|
<span class="block font-semibold text-gray-800">@opt.Name</span>
|
|
<span class="text-[9px] text-[#8a96a8]">Time: @opt.Duration</span>
|
|
</div>
|
|
<div class="text-right">
|
|
<span class="block font-bold text-[#1f2a3a]">R @opt.ZarCost.ToString("N0")</span>
|
|
<span class="text-[9px] px-1 rounded bg-[#e2eefb] text-[#0066b3]">@opt.Type</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="p-3 bg-[#e2eefb] text-[#0066b3] rounded-lg">
|
|
<p class="font-semibold block">TMT Recommendations:</p>
|
|
<p class="mt-0.5">Our preferred corporate shuttle transfers provide meet & greet in the terminal arrivals hall, reducing delays and offering bill-back directly to the corporate travel card.</p>
|
|
</div>
|
|
|
|
<div class="flex gap-2 justify-end pt-3 border-t border-[#e4eaf2]">
|
|
<button @onclick="CloseShuttleModal" class="px-4 py-2 bg-[#0066b3] text-white hover:bg-[#003b71] font-semibold rounded-lg shadow-sm">
|
|
Close Calculator
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@* 3. Culture Guidelines Modal *@
|
|
@if (ShowCultureModal)
|
|
{
|
|
<div class="fixed inset-0 bg-[#1f2a3a]/40 backdrop-blur-xs z-50 flex items-center justify-center p-4">
|
|
<div class="bg-white rounded-xl border border-[#e4eaf2] shadow-xl max-w-md w-full p-6 text-left">
|
|
<div class="flex items-center justify-between mb-4 pb-2 border-b border-[#e4eaf2]">
|
|
<div>
|
|
<h3 class="text-base font-bold text-[#003b71]">Travel Orientation & Cultural Guidelines</h3>
|
|
<p class="text-xs text-[#8a96a8] mt-0.5">Empowering corporate travellers with destination intelligence and local advice</p>
|
|
</div>
|
|
<button @onclick="CloseCultureModal" class="text-[#8a96a8] hover:text-[#1f2a3a] cursor-pointer">
|
|
<!-- Close SVG -->
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="space-y-4 text-xs text-[#1f2a3a] overflow-y-auto max-h-[400px]">
|
|
|
|
<div class="space-y-3">
|
|
<div class="p-3.5 border border-[#e4eaf2] rounded-lg bg-[#fafcfe]">
|
|
<div class="font-bold text-[#003b71] text-xs uppercase mb-1">United Kingdom (London Orientation)</div>
|
|
<p class="text-xs text-[#5a6577]">Always keep to the left on tube escalators to avoid delays. Mobile contactless is widely accepted across all public transport. Standard tipping is 10-12.5% on meals if service charge is not already included. Safe and well-lit walking spaces throughout.</p>
|
|
</div>
|
|
|
|
<div class="p-3.5 border border-[#e4eaf2] rounded-lg bg-[#fafcfe]">
|
|
<div class="font-bold text-[#00a3a1] text-xs uppercase mb-1">Kenya (Nairobi Orientation)</div>
|
|
<p class="text-xs text-[#5a6577]">M-Pesa mobile money is universal; ask the help-desk on how to set it up prior to landing. Avoid carrying excess physical cash or wearing flashy items. Pre-arranged hotel transport or Uber is highly recommended for late-night airport transfers.</p>
|
|
</div>
|
|
|
|
<div class="p-3.5 border border-[#e4eaf2] rounded-lg bg-[#fafcfe]">
|
|
<div class="font-bold text-[#22a06b] text-xs uppercase mb-1">South Africa (Cape Town Orientation)</div>
|
|
<p class="text-xs text-[#5a6577]">Local microclimate weather shifts can occur quickly; packing lightweight warm layers is ideal. Use pre-arranged airport transfer shuttles rather than flagging roadside cabs. High alert on security awareness in localized central sectors after hours.</p>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="flex justify-end pt-4 border-t border-[#e4eaf2]">
|
|
<button @onclick="CloseCultureModal" class="px-4 py-2 bg-[#003b71] hover:bg-[#00264d] text-white font-semibold rounded-lg shadow-sm">
|
|
Close Orientation
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@* 4. Health Warnings Tracker & Vaccine Requirements *@
|
|
@if (ShowHealthTrackerModal)
|
|
{
|
|
<div class="fixed inset-0 bg-[#1f2a3a]/40 backdrop-blur-xs z-50 flex items-center justify-center p-4">
|
|
<div class="bg-white rounded-xl border border-[#e4eaf2] shadow-xl max-w-md w-full p-6 text-left">
|
|
<div class="flex items-center justify-between mb-4 pb-2 border-b border-[#e4eaf2]">
|
|
<div>
|
|
<h3 class="text-base font-bold text-[#003b71]">Health & Vaccine Checklist Portal</h3>
|
|
<p class="text-xs text-[#8a96a8] mt-0.5">Pre-travel health screening for SAQA corporate compliance</p>
|
|
</div>
|
|
<button @onclick="CloseHealthTrackerModal" class="text-[#8a96a8] hover:text-[#1f2a3a] cursor-pointer">
|
|
<!-- Close SVG -->
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="space-y-4 text-xs">
|
|
|
|
<div class="p-3 bg-[#effaf3] text-[#1b8054] rounded-lg flex gap-2">
|
|
<!-- ShieldCheck SVG -->
|
|
<svg class="w-5 h-5 shrink-0 text-[#22a06b]" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.742 0 01-1.043 5.636 3.741 3.741 0 01-5.076.63 3.75 3.75 0 01-3.758-3.848c-.021-.013-.042-.027-.063-.04A4.5 4.5 0 113.6 8.775 5.25 5.25 0 0113.8 6.44a3 3 0 013.758 3.848A3.713 3.752 0 0118 12.75" />
|
|
</svg>
|
|
<div>
|
|
<span class="font-bold block">International Yellow Card</span>
|
|
<p class="text-[11px] mt-0.5">SAQA travellers returning from East Africa, West Africa or South America require a physical Yellow Fever vaccine certificate logged on checkout.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-3">
|
|
<div class="p-3 border border-[#e4eaf2] rounded-lg">
|
|
<div class="font-bold text-[#dc2626] mb-1">Nairobi, Kenya</div>
|
|
<p class="text-gray-600">Yellow Fever vaccination certificate mandatory upon landing. Malaria prophylaxis is strongly advised for excursions outside urban Nairobi.</p>
|
|
</div>
|
|
<div class="p-3 border border-[#e4eaf2] rounded-lg">
|
|
<div class="font-bold text-[#0066b3] mb-1">United Kingdom & Schengen Area</div>
|
|
<p class="text-gray-600">No special vaccine cards or malaria restrictions required. General healthcare reciprocal frameworks apply for travel insurance files.</p>
|
|
</div>
|
|
<div class="p-3 border border-[#e4eaf2] rounded-lg">
|
|
<div class="font-bold text-[#f4a300] mb-1">United Arab Emirates (Dubai)</div>
|
|
<p class="text-gray-600">Ensure any special prescription medicine is logged on official SAQA Support channels beforehand, as UAE customs maintains strict medical framework checklists.</p>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="flex justify-end pt-4 border-t border-[#e4eaf2] mt-4">
|
|
<button @onclick="CloseHealthTrackerModal" class="px-4 py-2 bg-[#003b71] hover:bg-[#00264d] text-white font-semibold rounded-lg shadow-sm">
|
|
Close Tracker
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
|
|
@code {
|
|
// Model Definitions
|
|
public class DestinationGuide
|
|
{
|
|
public string City { get; set; } = string.Empty;
|
|
public string CountryCode { get; set; } = string.Empty;
|
|
public string Traveller { get; set; } = string.Empty;
|
|
public string WeatherEmoji { get; set; } = string.Empty;
|
|
public int Temperature { get; set; }
|
|
public string WeatherCondition { get; set; } = string.Empty;
|
|
public string LocalTime { get; set; } = string.Empty;
|
|
public string VisaStatus { get; set; } = string.Empty;
|
|
public string HealthWarning { get; set; } = string.Empty;
|
|
public string RiskLevel { get; set; } = string.Empty;
|
|
}
|
|
|
|
public class GlobalRiskAlert
|
|
{
|
|
public int Id { get; set; }
|
|
public string Title { get; set; } = string.Empty;
|
|
public string PublishedTime { get; set; } = string.Empty;
|
|
public string Severity { get; set; } = "Info"; // Critical, Warning, Info
|
|
public string Category { get; set; } = string.Empty;
|
|
public string Description { get; set; } = string.Empty;
|
|
}
|
|
|
|
public class AirportTransferOpt
|
|
{
|
|
public string Name { get; set; } = string.Empty;
|
|
public string Duration { get; set; } = string.Empty;
|
|
public decimal ZarCost { get; set; }
|
|
public string Type { get; set; } = string.Empty; // Train, Shuttle, Cab
|
|
}
|
|
|
|
// Core States
|
|
private string AlertFeedback { get; set; } = string.Empty;
|
|
private string SelectedAlertFilter { get; set; } = "All";
|
|
|
|
// Modals Control
|
|
private bool ShowVipBookingModal { get; set; } = false;
|
|
private bool ShowShuttleModal { get; set; } = false;
|
|
private bool ShowCultureModal { get; set; } = false;
|
|
private bool ShowHealthTrackerModal { get; set; } = false;
|
|
|
|
// Vip booking form states
|
|
private string VipSelectedTraveller { get; set; } = "J. Mokoena";
|
|
private string VipSelectedAirport { get; set; } = "OR Tambo (JNB)";
|
|
private string VipServiceLevel { get; set; } = "Fast-track + Lounge";
|
|
private DateTime VipFlightDate { get; set; } = new DateTime(2026, 6, 2);
|
|
|
|
// Shuttle calculator states
|
|
private string SelectedCalcCity { get; set; } = "London";
|
|
|
|
// Databases
|
|
private List<DestinationGuide> IntelligentDestinations { get; set; } = new();
|
|
private List<GlobalRiskAlert> SafetyAlerts { get; set; } = new();
|
|
|
|
// Filtered Risk Alerts derived properties
|
|
private IEnumerable<GlobalRiskAlert> FilteredRiskAlerts =>
|
|
SelectedAlertFilter == "All"
|
|
? SafetyAlerts
|
|
: SelectedAlertFilter == "Critical"
|
|
? SafetyAlerts.Where(a => a.Severity == "Critical")
|
|
: SafetyAlerts.Where(a => a.Severity == "Warning" || a.Severity == "Info");
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
// Populate standard intelligence trackers matching React seed database
|
|
IntelligentDestinations = new List<DestinationGuide>
|
|
{
|
|
new DestinationGuide
|
|
{
|
|
City = "London",
|
|
CountryCode = "UK",
|
|
Traveller = "T. Maluleke",
|
|
WeatherEmoji = "☁️",
|
|
Temperature = 14,
|
|
WeatherCondition = "Light drizzle, windy",
|
|
LocalTime = "10:15 AM BST",
|
|
VisaStatus = "Standard Visa",
|
|
HealthWarning = "None active",
|
|
RiskLevel = "Low"
|
|
},
|
|
new DestinationGuide
|
|
{
|
|
City = "Nairobi",
|
|
CountryCode = "KE",
|
|
Traveller = "A. Dlamini",
|
|
WeatherEmoji = "⛅",
|
|
Temperature = 22,
|
|
WeatherCondition = "Partly Cloudy",
|
|
LocalTime = "12:15 PM EAT",
|
|
VisaStatus = "eVisa Required",
|
|
HealthWarning = "Yellow Fever Mandatory",
|
|
RiskLevel = "Moderate"
|
|
},
|
|
new DestinationGuide
|
|
{
|
|
City = "Cape Town",
|
|
CountryCode = "ZA",
|
|
Traveller = "J. Mokoena",
|
|
WeatherEmoji = "🌧️",
|
|
Temperature = 12,
|
|
WeatherCondition = "Heavy rain, cold front",
|
|
LocalTime = "11:15 AM SAST",
|
|
VisaStatus = "Not Applicable",
|
|
HealthWarning = "None",
|
|
RiskLevel = "Low"
|
|
}
|
|
};
|
|
|
|
// Populate global advisories feed
|
|
SafetyAlerts = new List<GlobalRiskAlert>
|
|
{
|
|
new GlobalRiskAlert
|
|
{
|
|
Id = 1,
|
|
Title = "Planned Ground Crew Strike action (London Heathrow)",
|
|
PublishedTime = "22 May 2026, 09:14 AM",
|
|
Severity = "Critical",
|
|
Category = "Labor Dispute",
|
|
Description = "A 48-hour ground handler strike is notified for Heathrow Airport Terminal 2 & 5. Expect airline rescheduling, baggage delays, and boarding queues. SAQA travellers on the BA route have been contacted for pre-arranged lounge transfers."
|
|
},
|
|
new GlobalRiskAlert
|
|
{
|
|
Id = 2,
|
|
Title = "Tropical Cyclone advisory (Mozambique Channel)",
|
|
PublishedTime = "23 May 2026, 08:30 AM",
|
|
Severity = "Warning",
|
|
Category = "Severe Weather",
|
|
Description = "High-velocity tropical system tracking near Beira. Staff scheduled for Maputo or regional liaison should avoid non-essential regional flights until June 2."
|
|
},
|
|
new GlobalRiskAlert
|
|
{
|
|
Id = 3,
|
|
Title = "High Capacity Notice: SLOW Lounge OR Tambo JNB",
|
|
PublishedTime = "24 May 2026, 07:15 AM",
|
|
Severity = "Info",
|
|
Category = "Facility Advisory",
|
|
Description = "SLOW lounge is reporting high peak queues between 06:00 and 09:00 SAST daily. Executive travellers are advised to pre-book their slots using this companion app to guarantee priority entry."
|
|
}
|
|
};
|
|
}
|
|
|
|
// Interactive Filters
|
|
private void SetAlertFilter(string filter)
|
|
{
|
|
SelectedAlertFilter = filter;
|
|
}
|
|
|
|
// Destinations action guides
|
|
private void ExploreDestinationInDepth(DestinationGuide dest)
|
|
{
|
|
TriggerFeedback($"Loaded complete Orientation Portfolio for {dest.City} ({dest.CountryCode}). Sending localized safety briefs to {dest.Traveller}'s device.");
|
|
}
|
|
|
|
private void SimulateNotifyTravellers(GlobalRiskAlert alert)
|
|
{
|
|
TriggerFeedback($"Notification sent successfully! Pushed critical alert '{alert.Title}' as an SMS and inside the Companion Wallet of active SAQA travellers.");
|
|
}
|
|
|
|
// Vip booking processors
|
|
private void ProcessVipBooking()
|
|
{
|
|
TriggerFeedback($"VIP Service Request successfully processed! Authorized '{VipServiceLevel}' for {VipSelectedTraveller} departing {VipSelectedAirport} on {VipFlightDate.ToString("yyyy-MM-dd")}. Billed to SAQA Lodge Card.");
|
|
ShowVipBookingModal = false;
|
|
}
|
|
|
|
// Airport transfer calculator helper
|
|
private List<AirportTransferOpt> GetTransferOptions(string city)
|
|
{
|
|
if (city == "London")
|
|
{
|
|
return new List<AirportTransferOpt>
|
|
{
|
|
new AirportTransferOpt { Name = "Heathrow Express (Train)", Duration = "15 mins", ZarCost = 600M, Type = "Train" },
|
|
new AirportTransferOpt { Name = "Elizabeth Line Tube", Duration = "35 mins", ZarCost = 310M, Type = "Train" },
|
|
new AirportTransferOpt { Name = "Corporate Partner Shuttle", Duration = "45 mins", ZarCost = 1450M, Type = "Shuttle" }
|
|
};
|
|
}
|
|
else if (city == "Nairobi")
|
|
{
|
|
return new List<AirportTransferOpt>
|
|
{
|
|
new AirportTransferOpt { Name = "Pre-arranged Hotel Shuttle", Duration = "25 mins", ZarCost = 450M, Type = "Shuttle" },
|
|
new AirportTransferOpt { Name = "Local Rideshare App (Uber)", Duration = "30 mins", ZarCost = 280M, Type = "Cab" }
|
|
};
|
|
}
|
|
else
|
|
{
|
|
return new List<AirportTransferOpt>
|
|
{
|
|
new AirportTransferOpt { Name = "SA Express Shuttle Service", Duration = "20 mins", ZarCost = 380M, Type = "Shuttle" },
|
|
new AirportTransferOpt { Name = "MyCiTi Airport Bus 110", Duration = "30 mins", ZarCost = 120M, Type = "Shuttle" },
|
|
new AirportTransferOpt { Name = "Airport authorized Private Cab", Duration = "25 mins", ZarCost = 320M, Type = "Cab" }
|
|
};
|
|
}
|
|
}
|
|
|
|
// Modals visibility Helpers
|
|
private void OpenVipBookingModal()
|
|
{
|
|
ShowVipBookingModal = true;
|
|
}
|
|
|
|
private void CloseVipBookingModal()
|
|
{
|
|
ShowVipBookingModal = false;
|
|
}
|
|
|
|
private void OpenShuttleModal()
|
|
{
|
|
ShowShuttleModal = true;
|
|
}
|
|
|
|
private void CloseShuttleModal()
|
|
{
|
|
ShowShuttleModal = false;
|
|
}
|
|
|
|
private void OpenCultureModal()
|
|
{
|
|
ShowCultureModal = true;
|
|
}
|
|
|
|
private void CloseCultureModal()
|
|
{
|
|
ShowCultureModal = false;
|
|
}
|
|
|
|
private void OpenHealthTrackerModal()
|
|
{
|
|
ShowHealthTrackerModal = true;
|
|
}
|
|
|
|
private void CloseHealthTrackerModal()
|
|
{
|
|
ShowHealthTrackerModal = false;
|
|
}
|
|
|
|
// Toast triggered alert toaster
|
|
private void TriggerFeedback(string message)
|
|
{
|
|
AlertFeedback = message;
|
|
_ = DismissAlertAfterDelay();
|
|
}
|
|
|
|
private async Task DismissAlertAfterDelay()
|
|
{
|
|
await Task.Delay(5000);
|
|
AlertFeedback = string.Empty;
|
|
StateHasChanged();
|
|
}
|
|
}
|