Stable main pages
This commit is contained in:
@@ -1,7 +1,386 @@
|
||||
@page "/financial"
|
||||
|
||||
<h3>Financial</h3>
|
||||
<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 & 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user