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

386 lines
21 KiB
Plaintext

@page "/financial"
<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>
<h2 class="text-2xl font-bold tracking-tight text-[#003b71]">Financial Management</h2>
<p class="text-xs text-[#5a6577] mt-1">Bill-back consolidation, lodge card reconciliation, virtual credit cards</p>
</div>
<div class="flex gap-2">
<button 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">
Export to Excel
</button>
<button 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">
Generate invoice
</button>
</div>
</div>
<!-- KPI Grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<!-- Outstanding Invoices -->
<div class="bg-white border border-[#e4eaf2] rounded-xl 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-[#0066b3]">
<div class="text-[11px] text-[#5a6577] uppercase tracking-wider font-semibold mb-2">Outstanding Invoices</div>
<div class="text-2xl font-bold tracking-tight">R @OutstandingInvoices.ToString("N0")</div>
<div class="flex items-center gap-1 mt-2 text-xs text-[#8a96a8]">
<span>14 invoices · ageing 0-30 days</span>
</div>
</div>
<!-- Lodge Card Balance -->
<div class="bg-white border border-[#e4eaf2] rounded-xl 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-[#00a3a1]">
<div class="text-[11px] text-[#5a6577] uppercase tracking-wider font-semibold mb-2">Lodge Card Balance</div>
<div class="text-2xl font-bold tracking-tight">R @LodgeCardBalance.ToString("N0")</div>
<div class="flex items-center gap-1 mt-2 text-xs text-[#8a96a8]">
<span>Reconciled to 24 May</span>
</div>
</div>
<!-- YTD Savings -->
<div class="bg-white border border-[#e4eaf2] rounded-xl 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]">
<div class="text-[11px] text-[#5a6577] uppercase tracking-wider font-semibold mb-2">YTD Savings</div>
<div class="text-2xl font-bold tracking-tight">@YtdSavingsText</div>
<div class="flex items-center gap-1.5 mt-2 text-xs">
<span class="text-[#22a06b] font-semibold">▲ 18.2%</span>
<span class="text-[#8a96a8]">vs. baseline</span>
</div>
</div>
<!-- No-Show Fees -->
<div class="bg-white border border-[#e4eaf2] rounded-xl 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]">
<div class="text-[11px] text-[#5a6577] uppercase tracking-wider font-semibold mb-2">No-Show Fees</div>
<div class="text-2xl font-bold tracking-tight">R @NoShowFees.ToString("N0")</div>
<div class="flex items-center gap-1.5 mt-2 text-xs">
<span class="text-[#dc2626] font-semibold">▼ R 2,300</span>
<span class="text-[#8a96a8]">vs. last month</span>
</div>
</div>
</div>
<!-- First Data Row -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
<!-- Spend vs Savings Responsive Bar Chart Card -->
<div class="bg-white border border-[#e4eaf2] rounded-xl p-5 shadow-sm flex flex-col justify-between">
<div class="flex items-center justify-between mb-4">
<div>
<h3 class="text-[15px] font-bold">Spend vs. Savings</h3>
<p class="text-xs text-[#8a96a8] mt-0.5">Cost containment performance</p>
</div>
<span class="bg-[#e3f6ed] text-[#22a06b] px-2.5 py-0.5 rounded-full text-[11px] font-semibold">On track</span>
</div>
<!-- Responsive Rendered SVG Bar Chart Layout -->
<div class="relative w-full h-[220px] mt-2">
<svg viewBox="0 0 600 220" class="w-full h-full" xmlns="http://www.w3.org/2000/svg">
<!-- Y-Axis Gridlines & Ticks -->
<g stroke="#e4eaf2" stroke-width="1" stroke-dasharray="2,2">
<line x1="60" y1="20" x2="580" y2="20" />
<line x1="60" y1="60" x2="580" y2="60" />
<line x1="60" y1="100" x2="580" y2="100" />
<line x1="60" y1="140" x2="580" y2="140" />
<line x1="60" y1="180" x2="580" y2="180" />
</g>
<g font-family="Inter, sans-serif" font-size="11" fill="#8a96a8" text-anchor="end">
<text x="50" y="24">R2.5M</text>
<text x="50" y="64">R2.0M</text>
<text x="50" y="104">R1.5M</text>
<text x="50" y="144">R1.0M</text>
<text x="50" y="184">R500k</text>
</g>
<!-- Chart Bars: [Spend (Blue), Savings (Teal)] -->
<!-- Dec -->
<rect x="110" y="59" width="16" height="121" fill="#0066b3" rx="3" />
<rect x="130" y="162" width="16" height="18" fill="#00a3a1" rx="3" />
<!-- Jan -->
<rect x="190" y="47" width="16" height="133" fill="#0066b3" rx="3" />
<rect x="210" y="159" width="16" height="21" fill="#00a3a1" rx="3" />
<!-- Feb -->
<rect x="270" y="46" width="16" height="134" fill="#0066b3" rx="3" />
<rect x="290" y="157" width="16" height="23" fill="#00a3a1" rx="3" />
<!-- Mar -->
<rect x="350" y="28" width="16" height="152" fill="#0066b3" rx="3" />
<rect x="370" y="153" width="16" height="27" fill="#00a3a1" rx="3" />
<!-- Apr -->
<rect x="430" y="13" width="16" height="167" fill="#0066b3" rx="3" />
<rect x="450" y="149" width="16" height="31" fill="#00a3a1" rx="3" />
<!-- May -->
<rect x="510" y="4" width="16" height="176" fill="#0066b3" rx="3" />
<rect x="530" y="147" width="16" height="33" fill="#00a3a1" rx="3" />
<!-- X-Axis Labels -->
<g font-family="Inter, sans-serif" font-size="11" fill="#8a96a8" text-anchor="middle">
<text x="128" y="198">Dec</text>
<text x="208" y="198">Jan</text>
<text x="288" y="198">Feb</text>
<text x="368" y="198">Mar</text>
<text x="448" y="198">Apr</text>
<text x="528" y="198">May</text>
</g>
</svg>
</div>
<div class="flex justify-center gap-6 mt-4 text-xs font-semibold text-[#5a6577]">
<div class="flex items-center gap-1.5">
<span class="w-3 h-3 rounded-sm bg-[#0066b3]"></span> Total Spend
</div>
<div class="flex items-center gap-1.5">
<span class="w-3 h-3 rounded-sm bg-[#00a3a1]"></span> Savings Realised
</div>
</div>
</div>
<!-- Savings Breakdown Table Card -->
<div class="bg-white border border-[#e4eaf2] rounded-xl p-5 shadow-sm flex flex-col justify-between">
<div class="flex items-center justify-between mb-4">
<div>
<h3 class="text-[15px] font-bold">Savings Breakdown</h3>
<p class="text-xs text-[#8a96a8] mt-0.5">How May savings of R 412,580 were realised</p>
</div>
<span class="bg-[#e3f6ed] text-[#22a06b] px-2.5 py-0.5 rounded-full text-[11px] font-semibold">+R 412k</span>
</div>
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr class="border-b border-[#e4eaf2]">
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-3">Category</th>
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-3">Open Rate</th>
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-3">Negotiated</th>
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-3">Saved</th>
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-3">%</th>
</tr>
</thead>
<tbody class="text-sm">
@foreach (var item in SavingsData)
{
<tr class="border-b border-[#e4eaf2] hover:bg-[#fafcfe] transition-colors">
<td class="py-2.5 px-3">
<span class="inline-block px-2.5 py-0.5 rounded-full text-xs font-semibold @item.BadgeClass">
@item.Category
</span>
</td>
<td class="py-2.5 px-3">R @item.OpenRate.ToString("N0")</td>
<td class="py-2.5 px-3">R @item.NegotiatedRate.ToString("N0")</td>
<td class="py-2.5 px-3 text-[#22a06b] font-semibold">R @item.Saved.ToString("N0")</td>
<td class="py-2.5 px-3 text-[#5a6577]">@item.Percentage.ToString("F1")%</td>
</tr>
}
<tr class="bg-[#effaf3] font-bold">
<td colspan="3" class="py-3 px-3">Total saved this month</td>
<td class="py-3 px-3 text-[#22a06b]">R @TotalSavedThisMonth.ToString("N0")</td>
<td class="py-3 px-3">16.8%</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Second Data Row -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
<!-- Virtual Credit Card Card -->
<div class="bg-white border border-[#e4eaf2] rounded-xl p-5 shadow-sm flex flex-col justify-between">
<div class="mb-4">
<h3 class="text-[15px] font-bold">Virtual Credit Card</h3>
<p class="text-xs text-[#8a96a8] mt-0.5">Single-use, per-transaction</p>
</div>
<!-- Authentic Virtual Card Mock -->
<div class="bg-gradient-to-br from-[#003b71] via-[#0066b3] to-[#00a3a1] text-white rounded-2xl p-6 shadow-md relative overflow-hidden before:content-[''] before:absolute before:-right-10 before:-top-10 before:w-40 before:h-40 before:bg-gradient-to-br before:from-white/10 before:to-transparent before:rounded-full">
<div class="text-[11px] opacity-85 uppercase tracking-widest">SAQA · Single-use VCC</div>
<div class="font-mono text-lg tracking-[4px] my-5 font-semibold">4716 •••• •••• 8842</div>
<div class="flex justify-between items-end mt-3">
<div>
<span class="text-[10px] opacity-85 block uppercase tracking-wider">Cardholder</span>
<strong class="text-sm font-semibold">SAQA Travel Desk</strong>
</div>
<div>
<span class="text-[10px] opacity-85 block uppercase tracking-wider">Limit</span>
<strong class="text-sm font-semibold">R 3,500.00</strong>
</div>
<div>
<span class="text-[10px] opacity-85 block uppercase tracking-wider">Expires</span>
<strong class="text-sm font-semibold">02/06/26</strong>
</div>
</div>
</div>
<div class="flex gap-2 mt-5">
<button class="flex-1 py-2.5 px-4 rounded-lg text-sm font-semibold bg-[#0066b3] text-white hover:bg-[#003b71] transition-colors shadow-sm cursor-pointer text-center">
Issue new card
</button>
<button class="py-2.5 px-4 rounded-lg text-sm font-semibold bg-white border border-[#e4eaf2] text-[#1f2a3a] hover:bg-[#fafcfe] transition-colors shadow-sm cursor-pointer text-center">
View all (12)
</button>
</div>
</div>
<!-- Spend by Cost Centre Card -->
<div class="bg-white border border-[#e4eaf2] rounded-xl p-5 shadow-sm">
<div class="mb-4">
<h3 class="text-[15px] font-bold">Spend by Cost Centre</h3>
<p class="text-xs text-[#8a96a8] mt-0.5">Where the May spend went</p>
</div>
<div class="flex flex-col">
@foreach (var cc in CostCentreData)
{
<div class="flex items-center justify-between py-3 border-b border-[#e4eaf2] last:border-0 hover:bg-[#fafcfe] px-1 transition-colors rounded-lg">
<div>
<div class="text-[13px] font-medium">@cc.Name</div>
<div class="text-[11px] text-[#8a96a8]">@cc.Travellers travellers</div>
</div>
<strong class="text-sm font-bold">R @cc.Spend.ToString("N0")</strong>
</div>
}
</div>
</div>
</div>
<!-- Consolidated Invoices Card -->
<div class="bg-white border border-[#e4eaf2] rounded-xl p-5 shadow-sm">
<div class="flex items-center justify-between mb-4">
<div>
<h3 class="text-[15px] font-bold">Consolidated Invoices</h3>
<p class="text-xs text-[#8a96a8] mt-0.5">Bill-back from suppliers · linked to PO &amp; Travel Authorisation</p>
</div>
<span class="bg-[#e2eefb] text-[#0066b3] px-2.5 py-0.5 rounded-full text-[11px] font-semibold">May 2026</span>
</div>
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr class="border-b border-[#e4eaf2]">
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-3">Invoice #</th>
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-3">Supplier</th>
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-3">Linked PO</th>
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-3">Items</th>
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-3">Total</th>
<th class="text-left text-[11px] text-[#8a96a8] uppercase tracking-wider font-semibold py-2 px-3">Status</th>
</tr>
</thead>
<tbody class="text-sm">
@foreach (var invoice in InvoicesData)
{
<tr class="border-b border-[#e4eaf2] hover:bg-[#fafcfe] transition-colors">
<td class="py-3 px-3 font-semibold">@invoice.InvoiceNum</td>
<td class="py-3 px-3">@invoice.Supplier</td>
<td class="py-3 px-3 text-[#5a6577]">@invoice.LinkedPo</td>
<td class="py-3 px-3 text-[#5a6577]">@invoice.Items</td>
<td class="py-3 px-3 font-medium">R @invoice.Total.ToString("N0")</td>
<td class="py-3 px-3">
<span class="inline-block px-2.5 py-0.5 rounded-full text-[11px] font-semibold @invoice.StatusBadgeClass">
@invoice.Status
</span>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
@code {
// KPI Data Properties
private double OutstandingInvoices { get; set; } = 184920;
private double LodgeCardBalance { get; set; } = 312440;
private string YtdSavingsText { get; set; } = "R 1.94M";
private double NoShowFees { get; set; } = 8140;
private double TotalSavedThisMonth { get; set; } = 412580;
// Savings Breakdown Data Structure
private List<SavingsItem> SavingsData { get; set; } = new()
{
new SavingsItem("Air, domestic", 980400, 832340, 148060, 15.1, "bg-[#e2eefb] text-[#0066b3]"),
new SavingsItem("Air, international", 320200, 260920, 59280, 18.5, "bg-[#e2eefb] text-[#0066b3]"),
new SavingsItem("Accommodation", 845600, 722420, 123180, 14.6, "bg-[#f4f7fb] text-[#1f2a3a] border border-[#e4eaf2]"),
new SavingsItem("Car rental", 215800, 168180, 47620, 22.1, "bg-[#fff4dd] text-[#b06b00]"),
new SavingsItem("Shuttle and transfers", 92800, 75400, 17400, 18.8, "bg-[#f4f7fb] text-[#1f2a3a] border border-[#e4eaf2]"),
new SavingsItem("Service fees waived", 17040, 0, 17040, 100.0, "bg-[#e3f6ed] text-[#22a06b]")
};
// Cost Centre Data Structure
private List<CostCentreItem> CostCentreData { get; set; } = new()
{
new CostCentreItem("Office of the CEO", 12, 684200),
new CostCentreItem("Qualifications and Standards", 38, 542400),
new CostCentreItem("Verifications", 26, 418950),
new CostCentreItem("Research and Stats", 18, 312800),
new CostCentreItem("Corporate Services", 14, 248400),
new CostCentreItem("International Liaison", 6, 640560)
};
// Consolidated Invoices Data Structure
private List<InvoiceItem> InvoicesData { get; set; } = new()
{
new InvoiceItem("INV-2026-0541", "SAA", "PO-44219", "3 tickets", 12420, "Paid", "bg-[#e3f6ed] text-[#22a06b]"),
new InvoiceItem("INV-2026-0542", "Premier Hotels", "PO-44230", "5 nights", 9475, "Pending", "bg-[#fff4dd] text-[#b06b00]"),
new InvoiceItem("INV-2026-0543", "Avis", "PO-44245", "1 rental · 3 days", 1420, "Reconciled", "bg-[#e2eefb] text-[#0066b3]"),
new InvoiceItem("INV-2026-0544", "FlySafair", "PO-44250", "2 tickets", 3970, "Paid", "bg-[#e3f6ed] text-[#22a06b]"),
new InvoiceItem("INV-2026-0545", "British Airways", "PO-44261", "1 ticket (LHR)", 18940, "Disputed", "bg-[#fde5e8] text-[#dc2626]")
};
// Sub-classes for models
public class SavingsItem
{
public string Category { get; set; }
public double OpenRate { get; set; }
public double NegotiatedRate { get; set; }
public double Saved { get; set; }
public double Percentage { get; set; }
public string BadgeClass { get; set; }
public SavingsItem(string category, double openRate, double negotiatedRate, double saved, double percentage, string badgeClass)
{
Category = category;
OpenRate = openRate;
NegotiatedRate = negotiatedRate;
Saved = saved;
Percentage = percentage;
BadgeClass = badgeClass;
}
}
public class CostCentreItem
{
public string Name { get; set; }
public int Travellers { get; set; }
public double Spend { get; set; }
public CostCentreItem(string name, int travellers, double spend)
{
Name = name;
Travellers = travellers;
Spend = spend;
}
}
public class InvoiceItem
{
public string InvoiceNum { get; set; }
public string Supplier { get; set; }
public string LinkedPo { get; set; }
public string Items { get; set; }
public double Total { get; set; }
public string Status { get; set; }
public string StatusBadgeClass { get; set; }
public InvoiceItem(string invoiceNum, string supplier, string linkedPo, string items, double total, string status, string statusBadgeClass)
{
InvoiceNum = invoiceNum;
Supplier = supplier;
LinkedPo = linkedPo;
Items = items;
Total = total;
Status = status;
StatusBadgeClass = statusBadgeClass;
}
}
}