|
@@ -1,203 +1,167 @@
|
|
|
-@using Common
|
|
|
|
|
@model Kitty_Fall.Cms.Models.RevenueReportViewModel
|
|
@model Kitty_Fall.Cms.Models.RevenueReportViewModel
|
|
|
@{
|
|
@{
|
|
|
ViewData["Title"] = "Revenue Reports";
|
|
ViewData["Title"] = "Revenue Reports";
|
|
|
- var isDaily = Model.ReportType == CommonConstants.DAY_REPORT;
|
|
|
|
|
var fromDateValue = Model.FromDate?.ToString("yyyy-MM-dd");
|
|
var fromDateValue = Model.FromDate?.ToString("yyyy-MM-dd");
|
|
|
var toDateValue = Model.ToDate?.ToString("yyyy-MM-dd");
|
|
var toDateValue = Model.ToDate?.ToString("yyyy-MM-dd");
|
|
|
|
|
+ var isRevenue = Model.ActiveTab == "revenue";
|
|
|
|
|
|
|
|
- string BuildTypeUrl(string reportType)
|
|
|
|
|
|
|
+ string TabUrl(string tab)
|
|
|
{
|
|
{
|
|
|
- var query = new System.Collections.Generic.List<string> { $"reportType={System.Uri.EscapeDataString(reportType)}" };
|
|
|
|
|
- if (!string.IsNullOrWhiteSpace(fromDateValue))
|
|
|
|
|
- {
|
|
|
|
|
- query.Add($"fromDate={System.Uri.EscapeDataString(fromDateValue)}");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!string.IsNullOrWhiteSpace(toDateValue))
|
|
|
|
|
- {
|
|
|
|
|
- query.Add($"toDate={System.Uri.EscapeDataString(toDateValue)}");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!string.IsNullOrWhiteSpace(Model.ServiceFilter))
|
|
|
|
|
- {
|
|
|
|
|
- query.Add($"serviceFilter={System.Uri.EscapeDataString(Model.ServiceFilter)}");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+ var query = new List<string> { "tab=" + Uri.EscapeDataString(tab) };
|
|
|
|
|
+ if (!string.IsNullOrWhiteSpace(fromDateValue)) query.Add("fromDate=" + Uri.EscapeDataString(fromDateValue));
|
|
|
|
|
+ if (!string.IsNullOrWhiteSpace(toDateValue)) query.Add("toDate=" + Uri.EscapeDataString(toDateValue));
|
|
|
|
|
+ if (!string.IsNullOrWhiteSpace(Model.ServiceFilter)) query.Add("serviceFilter=" + Uri.EscapeDataString(Model.ServiceFilter));
|
|
|
return Url.Content("~/RevenueReport") + "?" + string.Join("&", query);
|
|
return Url.Content("~/RevenueReport") + "?" + string.Join("&", query);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- string BuildTrendUrl()
|
|
|
|
|
- {
|
|
|
|
|
- var query = new System.Collections.Generic.List<string>();
|
|
|
|
|
- if (!string.IsNullOrWhiteSpace(fromDateValue))
|
|
|
|
|
- {
|
|
|
|
|
- query.Add($"fromDate={System.Uri.EscapeDataString(fromDateValue)}");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!string.IsNullOrWhiteSpace(toDateValue))
|
|
|
|
|
- {
|
|
|
|
|
- query.Add($"toDate={System.Uri.EscapeDataString(toDateValue)}");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ string FormatValue(decimal value) => isRevenue ? value.ToString("#,##0.##") : value.ToString("#,##0");
|
|
|
|
|
|
|
|
- if (!string.IsNullOrWhiteSpace(Model.ServiceFilter))
|
|
|
|
|
|
|
+ string PageUrl(int page)
|
|
|
|
|
+ {
|
|
|
|
|
+ var query = new List<string>
|
|
|
{
|
|
{
|
|
|
- query.Add($"serviceFilter={System.Uri.EscapeDataString(Model.ServiceFilter)}");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return query.Count == 0
|
|
|
|
|
- ? Url.Content("~/RevenueReport/DailyTrend")
|
|
|
|
|
- : Url.Content("~/RevenueReport/DailyTrend") + "?" + string.Join("&", query);
|
|
|
|
|
|
|
+ "tab=" + Uri.EscapeDataString(Model.ActiveTab),
|
|
|
|
|
+ "page=" + page
|
|
|
|
|
+ };
|
|
|
|
|
+ if (!string.IsNullOrWhiteSpace(fromDateValue)) query.Add("fromDate=" + Uri.EscapeDataString(fromDateValue));
|
|
|
|
|
+ if (!string.IsNullOrWhiteSpace(toDateValue)) query.Add("toDate=" + Uri.EscapeDataString(toDateValue));
|
|
|
|
|
+ if (!string.IsNullOrWhiteSpace(Model.ServiceFilter)) query.Add("serviceFilter=" + Uri.EscapeDataString(Model.ServiceFilter));
|
|
|
|
|
+ return Url.Content("~/RevenueReport") + "?" + string.Join("&", query);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- string FormatRevenue(decimal value) => value.ToString("#,##0.##");
|
|
|
|
|
- string FormatCount(decimal value) => value.ToString("#,##0");
|
|
|
|
|
- string FormatTime(DateTime value) => isDaily ? value.ToString("dd/MM/yyyy") : value.ToString("dd/MM/yyyy HH:mm");
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-<div class="py-6 sm:py-8">
|
|
|
|
|
- <div class="rounded-3xl bg-white shadow-sm ring-1 ring-gray-100">
|
|
|
|
|
|
|
+<div class="min-w-0 max-w-full py-6 sm:py-8">
|
|
|
|
|
+ <div class="min-w-0 max-w-full overflow-hidden rounded-3xl bg-white shadow-sm ring-1 ring-gray-100">
|
|
|
<div class="border-b border-gray-100 px-5 py-5 sm:px-6">
|
|
<div class="border-b border-gray-100 px-5 py-5 sm:px-6">
|
|
|
- <div class="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
|
|
|
|
|
- <div class="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
|
|
|
|
- <a href="@BuildTypeUrl(CommonConstants.DAY_REPORT)"
|
|
|
|
|
- class="inline-flex items-center justify-center rounded-2xl px-4 py-3 text-sm font-semibold transition @(isDaily ? "bg-admin-active text-white shadow-lg shadow-purple-100" : "bg-gray-100 text-gray-700 hover:bg-gray-200")">
|
|
|
|
|
- REVENUE by day
|
|
|
|
|
- </a>
|
|
|
|
|
- <a href="@BuildTypeUrl(CommonConstants.HOUR_REPORT)"
|
|
|
|
|
- class="inline-flex items-center justify-center rounded-2xl px-4 py-3 text-sm font-semibold transition @(!isDaily ? "bg-admin-active text-white shadow-lg shadow-purple-100" : "bg-gray-100 text-gray-700 hover:bg-gray-200")">
|
|
|
|
|
- REVENUE by hour
|
|
|
|
|
- </a>
|
|
|
|
|
- @* <a href="@BuildTrendUrl()"
|
|
|
|
|
- class="inline-flex items-center justify-center rounded-2xl bg-gray-100 px-4 py-3 text-sm font-semibold text-gray-700 transition hover:bg-gray-200">
|
|
|
|
|
- REVENUE by date
|
|
|
|
|
- </a> *@
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <h1 class="text-2xl font-bold text-gray-900">Hourly Reports</h1>
|
|
|
|
|
+ <p class="mt-1 text-sm text-gray-500">Revenue and total subscriptions by hour.</p>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="mt-5 flex flex-wrap gap-2">
|
|
|
|
|
+ <a href="@TabUrl("revenue")" class="rounded-2xl px-5 py-3 text-sm font-semibold transition @(isRevenue ? "bg-admin-active text-white" : "bg-gray-100 text-gray-700 hover:bg-gray-200")">Revenue</a>
|
|
|
|
|
+ <a href="@TabUrl("subscriptions")" class="rounded-2xl px-5 py-3 text-sm font-semibold transition @(!isRevenue ? "bg-admin-active text-white" : "bg-gray-100 text-gray-700 hover:bg-gray-200")">Total subscriptions</a>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<form method="get" action="@Url.Content("~/RevenueReport")" class="mt-5 grid gap-3 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(0,1fr)_auto]">
|
|
<form method="get" action="@Url.Content("~/RevenueReport")" class="mt-5 grid gap-3 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(0,1fr)_auto]">
|
|
|
- <input type="hidden" name="reportType" value="@Model.ReportType" />
|
|
|
|
|
|
|
+ <input type="hidden" name="tab" value="@Model.ActiveTab" />
|
|
|
<label class="block">
|
|
<label class="block">
|
|
|
<span class="mb-2 block text-sm font-semibold text-gray-700">Service</span>
|
|
<span class="mb-2 block text-sm font-semibold text-gray-700">Service</span>
|
|
|
- <select name="serviceFilter" class="w-full rounded-2xl border border-gray-200 px-4 py-3 text-sm text-gray-700 outline-none transition focus:border-admin-active focus:ring-2 focus:ring-purple-100">
|
|
|
|
|
|
|
+ <select name="serviceFilter" class="w-full rounded-2xl border border-gray-200 px-4 py-3 text-sm outline-none focus:border-admin-active focus:ring-2 focus:ring-purple-100">
|
|
|
@foreach (var service in Model.Services)
|
|
@foreach (var service in Model.Services)
|
|
|
{
|
|
{
|
|
|
<option value="@service.Value" selected="@(string.Equals(Model.ServiceFilter, service.Value, StringComparison.OrdinalIgnoreCase) ? "selected" : null)">@service.Name</option>
|
|
<option value="@service.Value" selected="@(string.Equals(Model.ServiceFilter, service.Value, StringComparison.OrdinalIgnoreCase) ? "selected" : null)">@service.Name</option>
|
|
|
}
|
|
}
|
|
|
</select>
|
|
</select>
|
|
|
</label>
|
|
</label>
|
|
|
- <label class="block">
|
|
|
|
|
- <span class="mb-2 block text-sm font-semibold text-gray-700">From date</span>
|
|
|
|
|
- <input type="date" name="fromDate" value="@fromDateValue" class="w-full rounded-2xl border border-gray-200 px-4 py-3 text-sm text-gray-700 outline-none transition focus:border-admin-active focus:ring-2 focus:ring-purple-100" />
|
|
|
|
|
- </label>
|
|
|
|
|
- <label class="block">
|
|
|
|
|
- <span class="mb-2 block text-sm font-semibold text-gray-700">To date</span>
|
|
|
|
|
- <input type="date" name="toDate" value="@toDateValue" class="w-full rounded-2xl border border-gray-200 px-4 py-3 text-sm text-gray-700 outline-none transition focus:border-admin-active focus:ring-2 focus:ring-purple-100" />
|
|
|
|
|
- </label>
|
|
|
|
|
- <div class="flex items-end">
|
|
|
|
|
- <button type="submit" class="inline-flex w-full items-center justify-center rounded-2xl bg-admin-active px-4 py-3 text-sm font-semibold text-white transition hover:bg-purple-700 md:w-auto">
|
|
|
|
|
- Apply filter
|
|
|
|
|
- </button>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <label class="block"><span class="mb-2 block text-sm font-semibold text-gray-700">From date</span><input type="date" name="fromDate" value="@fromDateValue" class="w-full rounded-2xl border border-gray-200 px-4 py-3 text-sm outline-none focus:border-admin-active focus:ring-2 focus:ring-purple-100" /></label>
|
|
|
|
|
+ <label class="block"><span class="mb-2 block text-sm font-semibold text-gray-700">To date</span><input type="date" name="toDate" value="@toDateValue" class="w-full rounded-2xl border border-gray-200 px-4 py-3 text-sm outline-none focus:border-admin-active focus:ring-2 focus:ring-purple-100" /></label>
|
|
|
|
|
+ <div class="flex items-end"><button type="submit" class="w-full rounded-2xl bg-admin-active px-5 py-3 text-sm font-semibold text-white hover:bg-purple-700 md:w-auto">Apply filter</button></div>
|
|
|
</form>
|
|
</form>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- @if (Model.Items.Count == 0)
|
|
|
|
|
|
|
+ @if (Model.HourlyItems.Count == 0)
|
|
|
{
|
|
{
|
|
|
- <div class="px-5 pb-6 sm:px-6">
|
|
|
|
|
- <div class="rounded-3xl border border-dashed border-gray-200 bg-gray-50 px-6 py-10 text-center">
|
|
|
|
|
- <div class="text-base font-semibold text-gray-800">No report data found.</div>
|
|
|
|
|
- <div class="mt-2 text-sm text-gray-500">Adjust the report type or date range and try again.</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div class="px-5 py-10 text-center text-sm font-semibold text-gray-600 sm:px-6">No hourly report data found.</div>
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
- <div class="px-5 pb-5 sm:hidden">
|
|
|
|
|
- <div class="space-y-4">
|
|
|
|
|
- @foreach (var item in Model.Items)
|
|
|
|
|
- {
|
|
|
|
|
- <div class="rounded-3xl border border-gray-100 bg-gray-50 p-4 shadow-sm">
|
|
|
|
|
- <div class="flex items-start justify-between gap-3">
|
|
|
|
|
- <div>
|
|
|
|
|
- <div class="text-xs font-bold uppercase tracking-[0.18em] text-admin-active">@item.Type</div>
|
|
|
|
|
- <div class="mt-1 text-lg font-bold text-gray-900">@FormatTime(item.TimeReport)</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="rounded-2xl bg-white px-3 py-2 text-right shadow-sm">
|
|
|
|
|
- <div class="text-xs font-medium text-gray-500">Revenue</div>
|
|
|
|
|
- <div class="text-base font-bold text-gray-900">@FormatRevenue(item.Revenue)</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div class="mt-4 grid grid-cols-2 gap-3 text-sm">
|
|
|
|
|
- <div class="rounded-2xl bg-white px-3 py-2">
|
|
|
|
|
- <div class="text-gray-500">Register web</div>
|
|
|
|
|
- <div class="mt-1 font-semibold text-gray-900">@FormatCount(item.RegisterOnWeb)</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="rounded-2xl bg-white px-3 py-2">
|
|
|
|
|
- <div class="text-gray-500">Register MPS</div>
|
|
|
|
|
- <div class="mt-1 font-semibold text-gray-900">@FormatCount(item.RegisterOnMps)</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="rounded-2xl bg-white px-3 py-2">
|
|
|
|
|
- <div class="text-gray-500">Renew</div>
|
|
|
|
|
- <div class="mt-1 font-semibold text-gray-900">@FormatCount(item.Renew)</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="rounded-2xl bg-white px-3 py-2">
|
|
|
|
|
- <div class="text-gray-500">Buy web</div>
|
|
|
|
|
- <div class="mt-1 font-semibold text-gray-900">@FormatCount(item.BuyOnWeb)</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="rounded-2xl bg-white px-3 py-2">
|
|
|
|
|
- <div class="text-gray-500">Buy MPS</div>
|
|
|
|
|
- <div class="mt-1 font-semibold text-gray-900">@FormatCount(item.BuyOnMps)</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="rounded-2xl bg-white px-3 py-2">
|
|
|
|
|
- <div class="text-gray-500">Users active</div>
|
|
|
|
|
- <div class="mt-1 font-semibold text-gray-900">@FormatCount(item.UserActive)</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ <div class="min-w-0 max-w-full px-5 py-6 sm:px-6">
|
|
|
|
|
+ <div class="mb-4 flex items-center justify-between gap-3">
|
|
|
|
|
+ <div class="text-sm text-gray-500">@Model.TotalRows.ToString("#,##0") rows found</div>
|
|
|
|
|
+ <div class="text-xs font-semibold text-gray-400">Page @Model.Page / @Model.TotalPages</div>
|
|
|
</div>
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div class="hidden px-5 pb-6 sm:block sm:px-6">
|
|
|
|
|
- <div class="overflow-hidden rounded-3xl border border-gray-100">
|
|
|
|
|
- <div class="overflow-x-auto">
|
|
|
|
|
- <table class="min-w-full divide-y divide-gray-100">
|
|
|
|
|
|
|
+ <div class="min-w-0 max-w-full overflow-hidden rounded-3xl border border-gray-100">
|
|
|
|
|
+ <div id="hourlyScrollTop" class="overflow-x-auto overflow-y-hidden border-b border-gray-100">
|
|
|
|
|
+ <div id="hourlyScrollTopContent" class="h-4"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div id="hourlyScrollBottom" class="overflow-x-auto">
|
|
|
|
|
+ <table class="min-w-max divide-y divide-gray-100 text-sm">
|
|
|
<thead class="bg-gray-50">
|
|
<thead class="bg-gray-50">
|
|
|
- <tr class="text-left text-xs font-bold uppercase tracking-[0.14em] text-gray-500">
|
|
|
|
|
- <th class="px-4 py-3">Time</th>
|
|
|
|
|
- <th class="px-4 py-3">Revenue</th>
|
|
|
|
|
- <th class="px-4 py-3">Register web</th>
|
|
|
|
|
- <th class="px-4 py-3">Register MPS</th>
|
|
|
|
|
- <th class="px-4 py-3">Renew</th>
|
|
|
|
|
- <th class="px-4 py-3">Buy web</th>
|
|
|
|
|
- <th class="px-4 py-3">Buy MPS</th>
|
|
|
|
|
- <th class="px-4 py-3">Users active</th>
|
|
|
|
|
- <th class="px-4 py-3">Users inactive</th>
|
|
|
|
|
|
|
+ <tr class="text-left text-xs font-bold uppercase tracking-wider text-gray-500">
|
|
|
|
|
+ <th class="sticky left-0 z-20 bg-gray-50 px-4 py-3">No.</th>
|
|
|
|
|
+ <th class="sticky left-[58px] z-20 bg-gray-50 px-4 py-3">Service</th>
|
|
|
|
|
+ <th class="px-4 py-3">Report date</th>
|
|
|
|
|
+ <th class="px-4 py-3">Total</th>
|
|
|
|
|
+ @for (var hour = 0; hour < 24; hour++)
|
|
|
|
|
+ {
|
|
|
|
|
+ <th class="px-4 py-3">@($"H{hour:00}")</th>
|
|
|
|
|
+ }
|
|
|
</tr>
|
|
</tr>
|
|
|
</thead>
|
|
</thead>
|
|
|
- <tbody class="divide-y divide-gray-100 bg-white text-sm text-gray-700">
|
|
|
|
|
- @foreach (var item in Model.Items)
|
|
|
|
|
|
|
+ <tbody class="divide-y divide-gray-100 bg-white text-gray-700">
|
|
|
|
|
+ @for (var index = 0; index < Model.HourlyItems.Count; index++)
|
|
|
{
|
|
{
|
|
|
- <tr class="align-top">
|
|
|
|
|
- <td class="whitespace-nowrap px-4 py-4 font-semibold text-gray-900">@FormatTime(item.TimeReport)</td>
|
|
|
|
|
- <td class="whitespace-nowrap px-4 py-4 font-semibold text-gray-900">@FormatRevenue(item.Revenue)</td>
|
|
|
|
|
- <td class="whitespace-nowrap px-4 py-4">@FormatCount(item.RegisterOnWeb)</td>
|
|
|
|
|
- <td class="whitespace-nowrap px-4 py-4">@FormatCount(item.RegisterOnMps)</td>
|
|
|
|
|
- <td class="whitespace-nowrap px-4 py-4">@FormatCount(item.Renew)</td>
|
|
|
|
|
- <td class="whitespace-nowrap px-4 py-4">@FormatCount(item.BuyOnWeb)</td>
|
|
|
|
|
- <td class="whitespace-nowrap px-4 py-4">@FormatCount(item.BuyOnMps)</td>
|
|
|
|
|
- <td class="whitespace-nowrap px-4 py-4">@FormatCount(item.UserActive)</td>
|
|
|
|
|
- <td class="whitespace-nowrap px-4 py-4">@FormatCount(item.UserInactive)</td>
|
|
|
|
|
|
|
+ var item = Model.HourlyItems[index];
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <td class="sticky left-0 bg-white px-4 py-4">@((Model.Page - 1) * Model.PageSize + index + 1)</td>
|
|
|
|
|
+ <td class="sticky left-[58px] whitespace-nowrap bg-white px-4 py-4 font-semibold text-gray-900">@item.ServiceName</td>
|
|
|
|
|
+ <td class="whitespace-nowrap px-4 py-4">@item.ReportDate.ToString("dd/MM/yyyy")</td>
|
|
|
|
|
+ <td class="whitespace-nowrap px-4 py-4 font-bold text-gray-900">@FormatValue(item.Total)</td>
|
|
|
|
|
+ @for (var hour = 0; hour < 24; hour++)
|
|
|
|
|
+ {
|
|
|
|
|
+ <td class="whitespace-nowrap px-4 py-4">@FormatValue(item.Hours[hour])</td>
|
|
|
|
|
+ }
|
|
|
</tr>
|
|
</tr>
|
|
|
}
|
|
}
|
|
|
</tbody>
|
|
</tbody>
|
|
|
</table>
|
|
</table>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ @if (Model.TotalPages > 1)
|
|
|
|
|
+ {
|
|
|
|
|
+ var start = Math.Max(1, Model.Page - 2);
|
|
|
|
|
+ var end = Math.Min(Model.TotalPages, start + 4);
|
|
|
|
|
+ start = Math.Max(1, end - 4);
|
|
|
|
|
+ <nav class="mt-5 flex flex-wrap items-center justify-between gap-3" aria-label="Hourly report pagination">
|
|
|
|
|
+ <div class="text-xs text-gray-400">Page <span class="font-bold text-gray-700">@Model.Page</span> / <span class="font-bold text-gray-700">@Model.TotalPages</span></div>
|
|
|
|
|
+ <div class="flex items-center gap-1">
|
|
|
|
|
+ <a href="@PageUrl(Math.Max(1, Model.Page - 1))" class="rounded-lg px-3 py-2 text-xs font-bold text-gray-500 hover:bg-gray-100"><i class="fas fa-angle-left"></i></a>
|
|
|
|
|
+ @for (var p = start; p <= end; p++)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (p == Model.Page)
|
|
|
|
|
+ {
|
|
|
|
|
+ <span class="min-w-[36px] rounded-lg bg-admin-active px-3 py-2 text-center text-xs font-bold text-white">@p</span>
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ <a href="@PageUrl(p)" class="min-w-[36px] rounded-lg px-3 py-2 text-center text-xs font-bold text-gray-600 hover:bg-gray-100">@p</a>
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ <a href="@PageUrl(Math.Min(Model.TotalPages, Model.Page + 1))" class="rounded-lg px-3 py-2 text-xs font-bold text-gray-500 hover:bg-gray-100"><i class="fas fa-angle-right"></i></a>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </nav>
|
|
|
|
|
+ }
|
|
|
</div>
|
|
</div>
|
|
|
}
|
|
}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+@section Scripts {
|
|
|
|
|
+ <script>
|
|
|
|
|
+ (() => {
|
|
|
|
|
+ const top = document.getElementById('hourlyScrollTop');
|
|
|
|
|
+ const topContent = document.getElementById('hourlyScrollTopContent');
|
|
|
|
|
+ const bottom = document.getElementById('hourlyScrollBottom');
|
|
|
|
|
+ if (!top || !topContent || !bottom) return;
|
|
|
|
|
+
|
|
|
|
|
+ const resizeTopScrollbar = () => {
|
|
|
|
|
+ topContent.style.width = `${bottom.scrollWidth}px`;
|
|
|
|
|
+ };
|
|
|
|
|
+ let syncing = false;
|
|
|
|
|
+ top.addEventListener('scroll', () => {
|
|
|
|
|
+ if (syncing) return;
|
|
|
|
|
+ syncing = true;
|
|
|
|
|
+ bottom.scrollLeft = top.scrollLeft;
|
|
|
|
|
+ syncing = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ bottom.addEventListener('scroll', () => {
|
|
|
|
|
+ if (syncing) return;
|
|
|
|
|
+ syncing = true;
|
|
|
|
|
+ top.scrollLeft = bottom.scrollLeft;
|
|
|
|
|
+ syncing = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ resizeTopScrollbar();
|
|
|
|
|
+ window.addEventListener('resize', resizeTopScrollbar);
|
|
|
|
|
+ })();
|
|
|
|
|
+ </script>
|
|
|
|
|
+}
|