Files
2026-07-09 21:41:26 +02:00

604 lines
36 KiB
Plaintext

@page "/gds-sync"
@using System.Collections.Generic
@using System.Linq
@using System.Threading.Tasks
@rendermode InteractiveServer
<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]">GDS Sync Console</h2>
<p class="text-xs text-[#5a6577] mt-1">Real-time Amadeus, Travelport, and Sabre telemetry synchronization gateway and GDS negotiated rates validator</p>
</div>
<div class="flex gap-2">
<button @onclick="OpenSyncLogsModal"
class="inline-flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-semibold bg-white border border-[#e4eaf2] text-[#1f2a3a] hover:bg-[#fafcfe] transition-colors shadow-sm cursor-pointer">
<!-- Terminal SVG -->
<svg class="w-4 h-4 text-[#003b71]" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5" />
</svg>
View Sync Logs
</button>
<button @onclick="TriggerForceResync"
disabled="@IsSyncing"
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 disabled:bg-gray-400 disabled:cursor-not-allowed">
<!-- Sync / Arrow Path SVG -->
<svg class="w-4 h-4 @(IsSyncing ? "animate-spin" : "")" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
</svg>
@(IsSyncing ? "Syncing GDS Nodes..." : "Force Resync Now")
</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">
<!-- Check Circle 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>
}
@* GDS Status Core KPI Cards *@
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div class="bg-white border border-[#e4eaf2] rounded-[10px] p-[18px] shadow-sm relative overflow-hidden before:content-[''] before:absolute before:top-0 before:left-0 before:w-1 before:h-full before:bg-[#22a06b] text-left">
<div class="text-[11px] text-[#5a6577] uppercase tracking-[0.08em] font-semibold mb-2">Amadeus Node</div>
<div class="flex items-baseline gap-2">
<div class="text-2xl font-bold tracking-tight">Active / Live</div>
<span class="text-xs text-[#22a06b] font-semibold">100% Up</span>
</div>
<div class="flex items-center gap-1 mt-2 text-xs text-[#8a96a8]">
<span>Last sync @AmadeusLastSyncs ago</span>
</div>
</div>
<div class="bg-white border border-[#e4eaf2] rounded-[10px] p-[18px] shadow-sm relative overflow-hidden before:content-[''] before:absolute before:top-0 before:left-0 before:w-1 before:h-full before:bg-[#22a06b] text-left">
<div class="text-[11px] text-[#5a6577] uppercase tracking-[0.08em] font-semibold mb-2">Travelport Node</div>
<div class="flex items-baseline gap-2">
<div class="text-2xl font-bold tracking-tight">Active / Live</div>
<span class="text-xs text-[#22a06b] font-semibold">100% Up</span>
</div>
<div class="flex items-center gap-1 mt-2 text-xs text-[#8a96a8]">
<span>Last sync @TravelportLastSyncs ago</span>
</div>
</div>
<div class="bg-white border border-[#e4eaf2] rounded-[10px] p-[18px] shadow-sm relative overflow-hidden before:content-[''] before:absolute before:top-0 before:left-0 before:w-1 before:h-full before:bg-[#22a06b] text-left">
<div class="text-[11px] text-[#5a6577] uppercase tracking-[0.08em] font-semibold mb-2">Sabre Node</div>
<div class="flex items-baseline gap-2">
<div class="text-2xl font-bold tracking-tight">Active / Live</div>
<span class="text-xs text-[#22a06b] font-semibold">100% Up</span>
</div>
<div class="flex items-center gap-1 mt-2 text-xs text-[#8a96a8]">
<span>Last sync @SabreLastSyncs ago</span>
</div>
</div>
<div class="bg-white border border-[#e4eaf2] rounded-[10px] p-[18px] shadow-sm relative overflow-hidden before:content-[''] before:absolute before:top-0 before:left-0 before:w-1 before:h-full before:bg-[#f4a300] text-left">
<div class="text-[11px] text-[#5a6577] uppercase tracking-[0.08em] font-semibold mb-2">In-Flight PNR Queue</div>
<div class="text-2xl font-bold tracking-tight text-[#f4a300]">@PnrQueueCount</div>
<div class="flex items-center gap-1 mt-2 text-xs text-[#8a96a8]">
<span>Pending ticket printing checks</span>
</div>
</div>
</div>
@* Spend vs Savings Visual Indicators & GDS Providers Metrics split *@
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6 mb-6">
@* Spend vs Savings Metrics Panel - 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 border-b border-[#e4eaf2] pb-3">
<div>
<h3 class="text-[15px] font-bold">Spend vs. Savings Telemetry</h3>
<p class="text-xs text-[#8a96a8] mt-0.5">Negotiated corporate GDS fares against open published consumer tickets for May 2026</p>
</div>
<span class="bg-[#e3f6ed] text-[#22a06b] px-2.5 py-0.5 rounded-full text-[11px] font-semibold">+18.2% YTD Savings</span>
</div>
<div class="space-y-4">
<p class="text-xs text-[#5a6577]">Below metrics reflect the total savings realized directly from GDS nodes through TMT projects pre-negotiated corporate rates.</p>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div class="p-3.5 bg-[#f4f7fb] rounded-lg border border-[#e4eaf2]">
<span class="block text-[10px] text-[#8a96a8] uppercase font-bold mb-1">Open Published Cost</span>
<span class="text-lg font-bold text-[#1f2a3a]">R 2,662,890</span>
<span class="block text-[9px] text-gray-500 mt-1">Consumer benchmark rates</span>
</div>
<div class="p-3.5 bg-[#e2eefb] rounded-lg border border-[#b4d3f5]">
<span class="block text-[10px] text-[#0066b3] uppercase font-bold mb-1">Negotiated Corporate Cost</span>
<span class="text-lg font-bold text-[#0066b3]">R 2,250,310</span>
<span class="block text-[9px] text-[#0066b3] mt-1">SAQA contracted limits</span>
</div>
<div class="p-3.5 bg-[#effaf3] rounded-lg border border-[#a1dfc2]">
<span class="block text-[10px] text-[#22a06b] uppercase font-bold mb-1">Direct Savings Realized</span>
<span class="text-lg font-bold text-[#22a06b]">R @TotalSavingsThisMonth.ToString("N0")</span>
<span class="block text-[9px] text-[#22a06b] mt-1">May 2026 reporting index</span>
</div>
</div>
@* Visual Simulated Bar stack *@
<div class="space-y-1.5 pt-3">
<div class="flex justify-between items-center text-xs font-semibold">
<span>May Spend Budget Saved Rate</span>
<span class="text-[#22a06b]">15.5% Saved</span>
</div>
<div class="w-full bg-[#fde5e8] h-3.5 rounded-lg flex overflow-hidden">
<div class="bg-[#0066b3] h-full" style="width: 84.5%;" title="Negotiated Spend (84.5%)"></div>
<div class="bg-[#22a06b] h-full" style="width: 15.5%;" title="Savings (15.5%)"></div>
</div>
<div class="flex gap-4 text-[10px] text-gray-500">
<div class="flex items-center gap-1">
<span class="w-2.5 h-2.5 rounded-full bg-[#0066b3]"></span>
<span>Negotiated (R 2,250,310)</span>
</div>
<div class="flex items-center gap-1">
<span class="w-2.5 h-2.5 rounded-full bg-[#22a06b]"></span>
<span>Savings Realized (R @TotalSavingsThisMonth.ToString("N0"))</span>
</div>
</div>
</div>
</div>
</div>
<div class="mt-4 pt-3.5 border-t border-[#e4eaf2] text-xs text-[#8a96a8] flex justify-between items-center">
<span>Database verified by ASATA / IATA standards</span>
<span class="font-semibold text-gray-700">Sync is operational</span>
</div>
</div>
@* Savings by GDS Provider progress list - Right Column (lg:col-span-5) *@
<div class="lg:col-span-5 bg-white border border-[#e4eaf2] rounded-[10px] p-5 shadow-sm text-left flex flex-col justify-between">
<div>
<div class="mb-4">
<h3 class="text-base font-bold text-[#003b71]">Savings by GDS Provider Node</h3>
<p class="text-xs text-[#8a96a8] mt-0.5">Contribution percentages realized across Amadeus, Travelport, and Sabre connection pools</p>
</div>
<div class="space-y-4">
@* Provider 1 *@
<div class="space-y-1">
<div class="flex justify-between items-center text-xs">
<div>
<span class="font-bold text-[#1f2a3a]">Amadeus Node</span>
<span class="text-[10px] text-[#8a96a8] block">Domestic &amp; regional flights / local lodging</span>
</div>
<div class="text-right">
<span class="font-bold text-[#22a06b]">R @AmadeusSavings.ToString("N0")</span>
<span class="text-[10px] text-[#8a96a8] block">52.9% of total</span>
</div>
</div>
<div class="w-full bg-[#f4f7fb] h-1.5 rounded-full overflow-hidden">
<div class="bg-[#00a3a1] h-1.5 rounded-full" style="width: 53%;"></div>
</div>
</div>
@* Provider 2 *@
<div class="space-y-1">
<div class="flex justify-between items-center text-xs">
<div>
<span class="font-bold text-[#1f2a3a]">Travelport Node</span>
<span class="text-[10px] text-[#8a96a8] block">International airlines / premium class cabins</span>
</div>
<div class="text-right">
<span class="font-bold text-[#22a06b]">R @TravelportSavings.ToString("N0")</span>
<span class="text-[10px] text-[#8a96a8] block">30.1% of total</span>
</div>
</div>
<div class="w-full bg-[#f4f7fb] h-1.5 rounded-full overflow-hidden">
<div class="bg-[#0066b3] h-1.5 rounded-full" style="width: 30%;"></div>
</div>
</div>
@* Provider 3 *@
<div class="space-y-1">
<div class="flex justify-between items-center text-xs">
<div>
<span class="font-bold text-[#1f2a3a]">Sabre Node</span>
<span class="text-[10px] text-[#8a96a8] block">Hotel aggregates &amp; car rental structures</span>
</div>
<div class="text-right">
<span class="font-bold text-[#22a06b]">R @SabreSavings.ToString("N0")</span>
<span class="text-[10px] text-[#8a96a8] block">17.0% of total</span>
</div>
</div>
<div class="w-full bg-[#f4f7fb] h-1.5 rounded-full overflow-hidden">
<div class="bg-[#f4a300] h-1.5 rounded-full" style="width: 17%;"></div>
</div>
</div>
@* Highlighted Box *@
<div class="p-3 bg-[#effaf3] rounded-lg border border-[#d2f1df] text-center">
<span class="block text-[10px] text-[#5a6577] uppercase font-bold">Total GDS Savings Verified</span>
<span class="text-xl font-black text-[#22a06b] mt-0.5 block">R @TotalSavingsThisMonth.ToString("N0")</span>
</div>
</div>
</div>
<div class="mt-4 text-[10px] text-[#8a96a8] leading-normal">
<span>Savings are calculated on GDS booking confirmation vs standard unrestricted booking baseline. Verified by continuous live audit checks.</span>
</div>
</div>
</div>
@* Live Supplier Feed Table & Sync Activity grid *@
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6 mb-6">
@* GDS Supplier Fares Feed - Left Column (lg:col-span-8) *@
<div class="lg:col-span-8 bg-white border border-[#e4eaf2] rounded-[10px] p-5 shadow-sm text-left flex flex-col justify-between">
<div>
<div class="flex flex-col sm:flex-row sm:items-center justify-between mb-4 gap-2 border-b border-[#e4eaf2] pb-3">
<div>
<h3 class="text-base font-bold text-[#003b71]">Live Supplier Feed via GDS</h3>
<p class="text-xs text-[#8a96a8] mt-0.5">Contracted tariffs fetched directly from active connection handshakes</p>
</div>
@* Search Filter *@
<div class="flex items-center gap-2">
<span class="text-xs text-[#8a96a8] shrink-0 font-medium">GDS Provider:</span>
<select @bind="SelectedGdsFilter"
class="px-2 py-1 bg-[#f4f7fb] text-[#1f2a3a] border border-[#c8d3e1] rounded-lg text-xs font-semibold focus:outline-none focus:border-[#0066b3]">
<option value="All">All Providers</option>
<option value="Amadeus">Amadeus</option>
<option value="Travelport">Travelport</option>
<option value="Sabre">Sabre</option>
</select>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full border-collapse text-left">
<thead>
<tr class="border-b border-[#e4eaf2]">
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-1">Supplier</th>
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-1">GDS Node</th>
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-1">Type</th>
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-1">Open Rate</th>
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-1">Negotiated</th>
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-1">ZAR Saved</th>
<th class="text-right text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-1">Status</th>
</tr>
</thead>
<tbody class="text-sm">
@foreach (var feed in FilteredSupplierFeed)
{
<tr class="border-b border-[#e4eaf2] last:border-0 hover:bg-[#fafcfe] transition-colors">
<td class="py-2.5 px-1 font-bold text-[#101827]">@feed.SupplierName</td>
<td class="py-2.5 px-1">
<span class="inline-block px-2 py-0.5 rounded text-[10px] font-bold @(feed.GdsNode == "Amadeus" ? "bg-[#e0f7f6] text-[#00a3a1]" : feed.GdsNode == "Travelport" ? "bg-[#e2eefb] text-[#0066b3]" : "bg-[#fff4dd] text-[#b06b00]")">
@feed.GdsNode
</span>
</td>
<td class="py-2.5 px-1 text-gray-500 text-xs">@feed.CategoryType</td>
<td class="py-2.5 px-1 text-gray-600 font-mono text-xs">R @feed.OpenPrice.ToString("N0")</td>
<td class="py-2.5 px-1 font-bold text-[#101827] font-mono text-xs">R @feed.NegotiatedPrice.ToString("N0")</td>
<td class="py-2.5 px-1 text-[#22a06b] font-semibold text-xs font-mono">
R @feed.SavedValue.ToString("N0") (@feed.SavedPercentage%)
</td>
<td class="py-2.5 px-1 text-right">
<span class="inline-block px-2 py-0.5 rounded text-[9px] font-bold @(feed.Status == "Live" ? "bg-[#e3f6ed] text-[#22a06b]" : "bg-[#e2eefb] text-[#0066b3]")">
@feed.Status
</span>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<div class="mt-4 pt-3.5 border-t border-[#e4eaf2] text-xs text-[#8a96a8]">
<span>* Rates are updated dynamically every 30 seconds. Supplier cache lifetimes are managed according to commercial service agreements.</span>
</div>
</div>
@* Sync Activity & Health status indicators - Right Column (lg:col-span-4) *@
<div class="lg:col-span-4 space-y-6 text-left">
<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]">Node Traffic Analysis</h3>
<p class="text-xs text-[#8a96a8] mt-0.5">Aggregated telemetry actions logged over last 24 hours</p>
</div>
<div class="space-y-3.5">
<div class="flex items-center justify-between py-2 border-b border-dashed border-[#e4eaf2] text-xs">
<div>
<span class="font-bold text-[#1f2a3a] block">PNRs Generated</span>
<span class="text-[10px] text-[#8a96a8]">Across all connection tunnels</span>
</div>
<strong class="font-mono text-sm text-[#101827]">142</strong>
</div>
<div class="flex items-center justify-between py-2 border-b border-dashed border-[#e4eaf2] text-xs">
<div>
<span class="font-bold text-[#1f2a3a] block">Fares &amp; Tariff Queries</span>
<span class="text-[10px] text-[#8a96a8]">Read operations processed</span>
</div>
<strong class="font-mono text-sm text-[#101827]">8,420</strong>
</div>
<div class="flex items-center justify-between py-2 border-b border-dashed border-[#e4eaf2] text-xs">
<div>
<span class="font-bold text-[#1f2a3a] block">Confirmed Air &amp; Hotel Bookings</span>
<span class="text-[10px] text-[#8a96a8]">Ticketed in line with SAQA policies</span>
</div>
<strong class="font-mono text-sm text-[#22a06b]">118</strong>
</div>
<div class="flex items-center justify-between py-2 border-b border-dashed border-[#e4eaf2] text-xs">
<div>
<span class="font-bold text-[#1f2a3a] block">Auto Refund / Voucher Queues</span>
<span class="text-[10px] text-[#8a96a8]">Cancellations processed smoothly</span>
</div>
<strong class="font-mono text-sm text-[#0066b3]">9</strong>
</div>
<div class="flex items-center justify-between py-2 border-b border-dashed border-[#e4eaf2] text-xs">
<div>
<span class="font-bold text-[#1f2a3a] block">GDS Connection Failure Rate</span>
<span class="text-[10px] text-[#8a96a8]">Auto-retry failover threshold</span>
</div>
<strong class="font-mono text-sm text-[#22a06b]">0.04% (Exceptional)</strong>
</div>
</div>
</div>
<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]">Telemetry Node Health</h3>
<p class="text-xs text-[#8a96a8] mt-0.5">Direct response times &amp; availability metrics</p>
</div>
<div class="space-y-4">
@* Bar 1 *@
<div class="space-y-1">
<div class="flex justify-between items-center text-xs">
<span class="font-semibold text-gray-700">Amadeus API latency</span>
<span class="font-mono text-[#22a06b]">88ms (Avg)</span>
</div>
<div class="w-full bg-[#f4f7fb] h-1.5 rounded-full overflow-hidden">
<div class="bg-[#22a06b] h-1.5 rounded-full" style="width: 88%;"></div>
</div>
</div>
@* Bar 2 *@
<div class="space-y-1">
<div class="flex justify-between items-center text-xs">
<span class="font-semibold text-gray-700">Travelport API latency</span>
<span class="font-mono text-[#22a06b]">82ms (Avg)</span>
</div>
<div class="w-full bg-[#f4f7fb] h-1.5 rounded-full overflow-hidden">
<div class="bg-[#22a06b] h-1.5 rounded-full" style="width: 82%;"></div>
</div>
</div>
@* Bar 3 *@
<div class="space-y-1">
<div class="flex justify-between items-center text-xs">
<span class="font-semibold text-gray-700">Sabre API latency</span>
<span class="font-mono text-[#f4a300]">124ms (Avg)</span>
</div>
<div class="w-full bg-[#f4f7fb] h-1.5 rounded-full overflow-hidden">
<div class="bg-[#f4a300] h-1.5 rounded-full" style="width: 74%;"></div>
</div>
</div>
<div class="p-3 bg-[#effaf3] border border-[#d2f1df] rounded-lg text-xs text-[#1b8054] flex gap-2">
<!-- Shield 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">GDS Node Uptime: 99.98%</span>
<p class="text-[10px] mt-0.5 text-[#2b7250]">Automatic node failover setup ensures queries bypass any downtime immediately by fetching rates from auxiliary tunnels.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@* ===================== MODAL DIALOGS ===================== *@
@* 1. GDS Sync Logs Modal *@
@if (ShowSyncLogsModal)
{
<div class="fixed inset-0 bg-[#1f2a3a]/40 backdrop-blur-xs z-50 flex items-center justify-center p-4">
<div class="bg-[#101827] rounded-xl border border-gray-800 shadow-2xl max-w-2xl w-full p-6 flex flex-col max-h-[90vh] text-left">
<div class="flex items-center justify-between mb-4 pb-2 border-b border-gray-800">
<div>
<h3 class="text-base font-bold text-white">Live GDS Telemetry Sync Logs</h3>
<p class="text-xs text-gray-400 mt-0.5">Handshake queries and active transaction parameters</p>
</div>
<button @onclick="CloseSyncLogsModal" class="text-gray-400 hover:text-white 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>
@* Console style panel *@
<div class="flex-1 overflow-y-auto bg-black/50 border border-gray-900 rounded-lg p-4 font-mono text-xs text-green-400 space-y-2 max-h-[400px]">
@foreach (var log in SyncLogs)
{
<div class="flex gap-2.5 items-start">
<span class="text-gray-600 shrink-0">@log.Timestamp</span>
<span class="font-semibold @(log.Type == "Error" ? "text-red-400" : log.Type == "Success" ? "text-green-400" : "text-blue-300")">
[@log.Type]
</span>
<span class="text-gray-300">@log.Message</span>
</div>
}
</div>
<div class="flex justify-between items-center pt-4 border-t border-gray-800 mt-4">
<button @onclick="ClearSyncLogs" class="px-3.5 py-1.5 rounded-lg border border-gray-800 hover:border-gray-700 font-semibold text-xs text-gray-400 hover:text-white transition">
Clear Terminal Logs
</button>
<button @onclick="CloseSyncLogsModal" class="px-4 py-2 rounded-lg text-xs font-semibold text-[#1f2a3a] bg-white hover:bg-gray-100 transition shadow-sm cursor-pointer">
Close Terminal
</button>
</div>
</div>
</div>
}
@code {
// Model Definitions
public class GdsSupplierFeed
{
public int Id { get; set; }
public string SupplierName { get; set; } = string.Empty;
public string GdsNode { get; set; } = string.Empty; // Amadeus, Travelport, Sabre
public string CategoryType { get; set; } = string.Empty;
public decimal OpenPrice { get; set; }
public decimal NegotiatedPrice { get; set; }
public decimal SavedValue => OpenPrice - NegotiatedPrice;
public int SavedPercentage => (int)System.Math.Round((SavedValue / OpenPrice) * 100);
public string Status { get; set; } = "Live";
}
public class GdsConsoleLog
{
public string Timestamp { get; set; } = string.Empty;
public string Type { get; set; } = "Info"; // Success, Warning, Error, Info
public string Message { get; set; } = string.Empty;
}
// Core States
private bool IsSyncing { get; set; } = false;
private string AlertFeedback { get; set; } = string.Empty;
private string SelectedGdsFilter { get; set; } = "All";
private int PnrQueueCount { get; set; } = 14;
// Last sync counters
private int AmadeusLastSyncs { get; set; } = 14;
private int TravelportLastSyncs { get; set; } = 41;
private int SabreLastSyncs { get; set; } = 22;
// Total aggregate calculations
private decimal TotalSavingsThisMonth => AmadeusSavings + TravelportSavings + SabreSavings;
private decimal AmadeusSavings { get; set; } = 218400M;
private decimal TravelportSavings { get; set; } = 124180M;
private decimal SabreSavings { get; set; } = 70000M;
// Modal Control
private bool ShowSyncLogsModal { get; set; } = false;
// Databases
private List<GdsSupplierFeed> SupplierFeeds { get; set; } = new();
private List<GdsConsoleLog> SyncLogs { get; set; } = new();
// Filtered Feed list based on selection
private IEnumerable<GdsSupplierFeed> FilteredSupplierFeed =>
SelectedGdsFilter == "All"
? SupplierFeeds
: SupplierFeeds.Where(f => f.GdsNode.Equals(SelectedGdsFilter, System.StringComparison.OrdinalIgnoreCase));
protected override void OnInitialized()
{
// Populate supplier contracted feeds matching dashboard data
SupplierFeeds = new List<GdsSupplierFeed>
{
new GdsSupplierFeed { Id = 1, SupplierName = "SAA (South African Airways)", GdsNode = "Amadeus", CategoryType = "Air, Domestic", OpenPrice = 3290M, NegotiatedPrice = 2890M, Status = "Live" },
new GdsSupplierFeed { Id = 2, SupplierName = "FlySafair Flights", GdsNode = "Amadeus", CategoryType = "Air, Low Cost", OpenPrice = 2180M, NegotiatedPrice = 1985M, Status = "Live" },
new GdsSupplierFeed { Id = 3, SupplierName = "British Airways Standard", GdsNode = "Travelport", CategoryType = "Air, International", OpenPrice = 21440M, NegotiatedPrice = 18940M, Status = "Live" },
new GdsSupplierFeed { Id = 4, SupplierName = "Emirates Corporate", GdsNode = "Travelport", CategoryType = "Air, International", OpenPrice = 28800M, NegotiatedPrice = 23100M, Status = "Live" },
new GdsSupplierFeed { Id = 5, SupplierName = "Premier Hotels Collection", GdsNode = "Sabre", CategoryType = "Accommodation", OpenPrice = 2200M, NegotiatedPrice = 1895M, Status = "Live" },
new GdsSupplierFeed { Id = 6, SupplierName = "City Lodge Hotels Corp", GdsNode = "Sabre", CategoryType = "Accommodation", OpenPrice = 1850M, NegotiatedPrice = 1520M, Status = "Live" },
new GdsSupplierFeed { Id = 7, SupplierName = "Avis Car Rental", GdsNode = "Sabre", CategoryType = "Car Rental", OpenPrice = 520M, NegotiatedPrice = 420M, Status = "Live" },
new GdsSupplierFeed { Id = 8, SupplierName = "Europcar SA Group", GdsNode = "Sabre", CategoryType = "Car Rental", OpenPrice = 580M, NegotiatedPrice = 460M, Status = "Cached, 2 min" }
};
// Populate initial sync terminal logger
SyncLogs = new List<GdsConsoleLog>
{
new GdsConsoleLog { Timestamp = "11:15:02 AM", Type = "Info", Message = "Opening communication socket with Amadeus JNB Node (196.34.12.8)..." },
new GdsConsoleLog { Timestamp = "11:15:03 AM", Type = "Success", Message = "Handshake authenticated successfully. Security key established." },
new GdsConsoleLog { Timestamp = "11:15:04 AM", Type = "Info", Message = "Fetching May 2026 PNR ticket confirmation logs. Parsing 142 records..." },
new GdsConsoleLog { Timestamp = "11:15:08 AM", Type = "Success", Message = "Amadeus sync process successfully closed. 14 open e-Tickets synced to active dashboard." },
new GdsConsoleLog { Timestamp = "11:15:10 AM", Type = "Info", Message = "Querying Sabre GDS hotel & transfer rates. Auto-refresh threshold enabled." },
new GdsConsoleLog { Timestamp = "11:15:12 AM", Type = "Warning", Message = "Europcar API socket returned lag index of 180ms. Auto-refresh fallback to cache enabled." }
};
}
// Force Resync Simulation Trigger
private async Task TriggerForceResync()
{
IsSyncing = true;
TriggerFeedback("Executing Force GDS Node Resync. Parsing active socket connections...");
// Simulating sync steps and logging parameters
SyncLogs.Add(new GdsConsoleLog { Timestamp = "Just Now", Type = "Info", Message = "USER SIGNAL: Initiating full cluster resynchronization..." });
StateHasChanged();
await Task.Delay(1200);
SyncLogs.Add(new GdsConsoleLog { Timestamp = "Just Now", Type = "Success", Message = "Amadeus connection flushed and pulled fresh corporate PNR sets." });
AmadeusLastSyncs = 1;
StateHasChanged();
await Task.Delay(800);
SyncLogs.Add(new GdsConsoleLog { Timestamp = "Just Now", Type = "Success", Message = "Travelport & Sabre nodes auto-recalibrated rates cache parameters." });
TravelportLastSyncs = 1;
SabreLastSyncs = 1;
PnrQueueCount = System.Math.Max(0, PnrQueueCount - 2);
IsSyncing = false;
TriggerFeedback("GDS Cluster Sync Complete! Telemetry matrices successfully mapped and negotiated rates parsed smoothly.");
StateHasChanged();
}
// Modal view triggers
private void OpenSyncLogsModal()
{
ShowSyncLogsModal = true;
}
private void CloseSyncLogsModal()
{
ShowSyncLogsModal = false;
}
private void ClearSyncLogs()
{
SyncLogs.Clear();
SyncLogs.Add(new GdsConsoleLog { Timestamp = "Terminal Purged", Type = "Info", Message = "Sync log interface successfully cleared by admin command." });
}
// Toast alarm toaster
private void TriggerFeedback(string message)
{
AlertFeedback = message;
_ = DismissAlertAfterDelay();
}
private async Task DismissAlertAfterDelay()
{
await Task.Delay(5000);
AlertFeedback = string.Empty;
StateHasChanged();
}
}