Add reports page with sales analytics and UI components
Introduces comprehensive reports feature with interactive sales dashboard Includes dynamic data tables, charts, and filtering capabilities Enhances application with new statistics and reporting functionality
This commit is contained in:
parent
6ef001e35f
commit
405dabeb34
15 changed files with 1909 additions and 212 deletions
622
PlanTempus.Application/Features/Reports/Pages/Index.cshtml
Normal file
622
PlanTempus.Application/Features/Reports/Pages/Index.cshtml
Normal file
|
|
@ -0,0 +1,622 @@
|
|||
@page "/rapporter"
|
||||
@model PlanTempus.Application.Features.Reports.Pages.IndexModel
|
||||
@{
|
||||
ViewData["Title"] = "Statistik og Rapporter";
|
||||
}
|
||||
|
||||
<!-- Sticky Header with Tabs -->
|
||||
<swp-sticky-header>
|
||||
<swp-header-content>
|
||||
<swp-page-header>
|
||||
<swp-page-title>
|
||||
<i class="ph ph-chart-line-up"></i>
|
||||
<span>Statistik og Rapporter</span>
|
||||
</swp-page-title>
|
||||
<swp-page-actions>
|
||||
<swp-btn class="secondary" id="exportBtn">
|
||||
<i class="ph ph-export"></i>
|
||||
Eksporter
|
||||
</swp-btn>
|
||||
</swp-page-actions>
|
||||
</swp-page-header>
|
||||
</swp-header-content>
|
||||
|
||||
<!-- Tab Bar -->
|
||||
<swp-tab-bar>
|
||||
<swp-tab class="active" data-tab="sales">
|
||||
<i class="ph ph-receipt"></i>
|
||||
<span>Salgsrapport</span>
|
||||
</swp-tab>
|
||||
<swp-tab data-tab="hours">
|
||||
<i class="ph ph-clock"></i>
|
||||
<span>Timerapport</span>
|
||||
</swp-tab>
|
||||
</swp-tab-bar>
|
||||
</swp-sticky-header>
|
||||
|
||||
<!-- Tab Content: Salgsrapport -->
|
||||
<swp-tab-content data-tab="sales" class="active">
|
||||
<swp-page-container>
|
||||
<!-- Stats Bar -->
|
||||
<swp-stats-row class="cols-4">
|
||||
<swp-stat-card class="highlight">
|
||||
<swp-stat-value>12.450 kr</swp-stat-value>
|
||||
<swp-stat-label>Omsætning i dag</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card class="success">
|
||||
<swp-stat-value>187.230 kr</swp-stat-value>
|
||||
<swp-stat-label>Omsætning denne måned</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card>
|
||||
<swp-stat-value>18</swp-stat-value>
|
||||
<swp-stat-label>Antal salg i dag</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card>
|
||||
<swp-stat-value>692 kr</swp-stat-value>
|
||||
<swp-stat-label>Gns. ordreværdi</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
</swp-stats-row>
|
||||
|
||||
<!-- Charts Grid -->
|
||||
<swp-charts-grid>
|
||||
<swp-chart-card>
|
||||
<swp-chart-header>
|
||||
<swp-chart-title>Omsætning pr. måned</swp-chart-title>
|
||||
<swp-chart-hint>Sidste 12 måneder</swp-chart-hint>
|
||||
</swp-chart-header>
|
||||
<swp-chart-container id="revenueChart"></swp-chart-container>
|
||||
</swp-chart-card>
|
||||
<swp-chart-card>
|
||||
<swp-chart-header>
|
||||
<swp-chart-title>Betalingsmetoder</swp-chart-title>
|
||||
<swp-chart-hint>Fordeling</swp-chart-hint>
|
||||
</swp-chart-header>
|
||||
<swp-chart-container id="paymentChart"></swp-chart-container>
|
||||
</swp-chart-card>
|
||||
</swp-charts-grid>
|
||||
|
||||
<!-- Filter Bar -->
|
||||
<swp-filter-bar>
|
||||
<swp-search-input>
|
||||
<i class="ph ph-magnifying-glass"></i>
|
||||
<input type="search" id="searchInput" placeholder="Søg fakturanr, kunde, medarbejder..." />
|
||||
</swp-search-input>
|
||||
<swp-filter-group>
|
||||
<swp-filter-label>Fra</swp-filter-label>
|
||||
<input type="date" id="dateFrom" value="2025-01-01" />
|
||||
</swp-filter-group>
|
||||
<swp-filter-group>
|
||||
<swp-filter-label>Til</swp-filter-label>
|
||||
<input type="date" id="dateTo" value="2025-01-06" />
|
||||
</swp-filter-group>
|
||||
<swp-filter-group>
|
||||
<swp-filter-label>Status</swp-filter-label>
|
||||
<select id="statusFilter">
|
||||
<option value="">Alle</option>
|
||||
<option value="paid">Betalt</option>
|
||||
<option value="pending">Afventer</option>
|
||||
<option value="credited">Krediteret</option>
|
||||
</select>
|
||||
</swp-filter-group>
|
||||
<swp-filter-group>
|
||||
<swp-filter-label>Betaling</swp-filter-label>
|
||||
<select id="paymentFilter">
|
||||
<option value="">Alle</option>
|
||||
<option value="card">Kort</option>
|
||||
<option value="cash">Kontant</option>
|
||||
<option value="mobilepay">MobilePay</option>
|
||||
<option value="invoice">Faktura</option>
|
||||
<option value="giftcard">Fordelskort</option>
|
||||
</select>
|
||||
</swp-filter-group>
|
||||
</swp-filter-bar>
|
||||
|
||||
<!-- Sales Table -->
|
||||
<swp-card class="sales-table">
|
||||
<swp-data-table>
|
||||
<swp-data-table-header>
|
||||
<swp-data-table-cell>Faktura</swp-data-table-cell>
|
||||
<swp-data-table-cell>Dato/tid</swp-data-table-cell>
|
||||
<swp-data-table-cell>Kunde</swp-data-table-cell>
|
||||
<swp-data-table-cell>Medarbejder</swp-data-table-cell>
|
||||
<swp-data-table-cell>Ydelser</swp-data-table-cell>
|
||||
<swp-data-table-cell class="right">Beløb</swp-data-table-cell>
|
||||
<swp-data-table-cell>Betaling</swp-data-table-cell>
|
||||
<swp-data-table-cell>Status</swp-data-table-cell>
|
||||
<swp-data-table-cell></swp-data-table-cell>
|
||||
</swp-data-table-header>
|
||||
<!-- Row 1 -->
|
||||
<swp-data-table-row>
|
||||
<swp-data-table-cell>
|
||||
<swp-invoice-cell>#1847</swp-invoice-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-datetime-cell>
|
||||
<span class="date">6. jan 2025</span>
|
||||
<span class="time">14:32</span>
|
||||
</swp-datetime-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-customer-cell>
|
||||
<span class="name">Maria Hansen</span>
|
||||
<span class="phone">+45 23 45 67 89</span>
|
||||
</swp-customer-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell class="muted">Louise P.</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-services-cell>
|
||||
<span class="main">Dameklip, Farve</span>
|
||||
<span class="more">+ 1 produkt</span>
|
||||
</swp-services-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-amount-cell>1.450 kr</swp-amount-cell></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-payment-badge class="card"><i class="ph ph-credit-card"></i> Kort</swp-payment-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-status-badge class="paid">Betalt</swp-status-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-row-arrow><i class="ph ph-caret-right"></i></swp-row-arrow></swp-data-table-cell>
|
||||
</swp-data-table-row>
|
||||
<!-- Row 2 -->
|
||||
<swp-data-table-row>
|
||||
<swp-data-table-cell>
|
||||
<swp-invoice-cell>#1846</swp-invoice-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-datetime-cell>
|
||||
<span class="date">6. jan 2025</span>
|
||||
<span class="time">13:15</span>
|
||||
</swp-datetime-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-customer-cell>
|
||||
<span class="name">Peter Sørensen</span>
|
||||
<span class="phone">+45 30 12 34 56</span>
|
||||
</swp-customer-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell class="muted">Anna J.</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-services-cell>
|
||||
<span class="main">Herreklip</span>
|
||||
</swp-services-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-amount-cell>295 kr</swp-amount-cell></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-payment-badge class="mobilepay"><i class="ph ph-device-mobile"></i> MobilePay</swp-payment-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-status-badge class="paid">Betalt</swp-status-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-row-arrow><i class="ph ph-caret-right"></i></swp-row-arrow></swp-data-table-cell>
|
||||
</swp-data-table-row>
|
||||
<!-- Row 3 -->
|
||||
<swp-data-table-row>
|
||||
<swp-data-table-cell>
|
||||
<swp-invoice-cell>#1845</swp-invoice-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-datetime-cell>
|
||||
<span class="date">6. jan 2025</span>
|
||||
<span class="time">11:45</span>
|
||||
</swp-datetime-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-customer-cell>
|
||||
<span class="name">Lise Andersen</span>
|
||||
<span class="phone">+45 42 56 78 90</span>
|
||||
</swp-customer-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell class="muted">Louise P.</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-services-cell>
|
||||
<span class="main">Dameklip, Balayage</span>
|
||||
<span class="more">+ 2 produkter</span>
|
||||
</swp-services-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-amount-cell>2.350 kr</swp-amount-cell></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-payment-badge class="card"><i class="ph ph-credit-card"></i> Kort</swp-payment-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-status-badge class="paid">Betalt</swp-status-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-row-arrow><i class="ph ph-caret-right"></i></swp-row-arrow></swp-data-table-cell>
|
||||
</swp-data-table-row>
|
||||
<!-- Row 4 -->
|
||||
<swp-data-table-row>
|
||||
<swp-data-table-cell>
|
||||
<swp-invoice-cell>#1844</swp-invoice-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-datetime-cell>
|
||||
<span class="date">5. jan 2025</span>
|
||||
<span class="time">16:20</span>
|
||||
</swp-datetime-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-customer-cell>
|
||||
<span class="name">Thomas Nielsen</span>
|
||||
<span class="phone">+45 51 23 45 67</span>
|
||||
</swp-customer-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell class="muted">Mikkel H.</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-services-cell>
|
||||
<span class="main">Herreklip, Skægtrim</span>
|
||||
</swp-services-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-amount-cell>395 kr</swp-amount-cell></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-payment-badge class="cash"><i class="ph ph-money"></i> Kontant</swp-payment-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-status-badge class="paid">Betalt</swp-status-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-row-arrow><i class="ph ph-caret-right"></i></swp-row-arrow></swp-data-table-cell>
|
||||
</swp-data-table-row>
|
||||
<!-- Row 5 -->
|
||||
<swp-data-table-row>
|
||||
<swp-data-table-cell>
|
||||
<swp-invoice-cell>#1843</swp-invoice-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-datetime-cell>
|
||||
<span class="date">5. jan 2025</span>
|
||||
<span class="time">14:00</span>
|
||||
</swp-datetime-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-customer-cell>
|
||||
<span class="name">Sofia Madsen</span>
|
||||
<span class="phone">+45 60 78 90 12</span>
|
||||
</swp-customer-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell class="muted">Anna J.</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-services-cell>
|
||||
<span class="main">Extensions</span>
|
||||
<span class="more">+ 1 produkt</span>
|
||||
</swp-services-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-amount-cell>4.500 kr</swp-amount-cell></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-payment-badge class="invoice"><i class="ph ph-file-text"></i> Faktura</swp-payment-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-status-badge class="pending">Afventer</swp-status-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-row-arrow><i class="ph ph-caret-right"></i></swp-row-arrow></swp-data-table-cell>
|
||||
</swp-data-table-row>
|
||||
<!-- Row 6 -->
|
||||
<swp-data-table-row>
|
||||
<swp-data-table-cell>
|
||||
<swp-invoice-cell>#1842</swp-invoice-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-datetime-cell>
|
||||
<span class="date">5. jan 2025</span>
|
||||
<span class="time">11:30</span>
|
||||
</swp-datetime-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-customer-cell>
|
||||
<span class="name">Emma Jensen</span>
|
||||
<span class="phone">+45 71 23 45 67</span>
|
||||
</swp-customer-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell class="muted">Louise P.</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-services-cell>
|
||||
<span class="main">Dameklip</span>
|
||||
</swp-services-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-amount-cell>-450 kr</swp-amount-cell></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-payment-badge class="card"><i class="ph ph-credit-card"></i> Kort</swp-payment-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-status-badge class="credited">Krediteret</swp-status-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-row-arrow><i class="ph ph-caret-right"></i></swp-row-arrow></swp-data-table-cell>
|
||||
</swp-data-table-row>
|
||||
<!-- Row 7 -->
|
||||
<swp-data-table-row>
|
||||
<swp-data-table-cell>
|
||||
<swp-invoice-cell>#1841</swp-invoice-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-datetime-cell>
|
||||
<span class="date">4. jan 2025</span>
|
||||
<span class="time">15:45</span>
|
||||
</swp-datetime-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-customer-cell>
|
||||
<span class="name">Katrine Olsen</span>
|
||||
<span class="phone">+45 82 34 56 78</span>
|
||||
</swp-customer-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell class="muted">Mikkel H.</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-services-cell>
|
||||
<span class="main">Dameklip, Highlights</span>
|
||||
<span class="more">+ 3 produkter</span>
|
||||
</swp-services-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-amount-cell>1.895 kr</swp-amount-cell></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-payment-badge class="giftcard"><i class="ph ph-gift"></i> Fordelskort</swp-payment-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-status-badge class="paid">Betalt</swp-status-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-row-arrow><i class="ph ph-caret-right"></i></swp-row-arrow></swp-data-table-cell>
|
||||
</swp-data-table-row>
|
||||
<!-- Row 8 -->
|
||||
<swp-data-table-row>
|
||||
<swp-data-table-cell>
|
||||
<swp-invoice-cell>#1840</swp-invoice-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-datetime-cell>
|
||||
<span class="date">4. jan 2025</span>
|
||||
<span class="time">10:00</span>
|
||||
</swp-datetime-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-customer-cell>
|
||||
<span class="name">Mads Christensen</span>
|
||||
<span class="phone">+45 93 45 67 89</span>
|
||||
</swp-customer-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell class="muted">Anna J.</swp-data-table-cell>
|
||||
<swp-data-table-cell>
|
||||
<swp-services-cell>
|
||||
<span class="main">Herreklip</span>
|
||||
</swp-services-cell>
|
||||
</swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-amount-cell>275 kr</swp-amount-cell></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-payment-badge class="mobilepay"><i class="ph ph-device-mobile"></i> MobilePay</swp-payment-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-status-badge class="paid">Betalt</swp-status-badge></swp-data-table-cell>
|
||||
<swp-data-table-cell><swp-row-arrow><i class="ph ph-caret-right"></i></swp-row-arrow></swp-data-table-cell>
|
||||
</swp-data-table-row>
|
||||
</swp-data-table>
|
||||
<swp-table-footer>
|
||||
<span>Viser 1-8 af 1.847 fakturaer</span>
|
||||
<swp-pagination>
|
||||
<swp-page-btn><i class="ph ph-caret-left"></i></swp-page-btn>
|
||||
<swp-page-btn class="active">1</swp-page-btn>
|
||||
<swp-page-btn>2</swp-page-btn>
|
||||
<swp-page-btn>3</swp-page-btn>
|
||||
<swp-page-btn>...</swp-page-btn>
|
||||
<swp-page-btn>231</swp-page-btn>
|
||||
<swp-page-btn><i class="ph ph-caret-right"></i></swp-page-btn>
|
||||
</swp-pagination>
|
||||
</swp-table-footer>
|
||||
</swp-card>
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
<!-- Tab Content: Timerapport -->
|
||||
<swp-tab-content data-tab="hours">
|
||||
<swp-page-container>
|
||||
<swp-card>
|
||||
<swp-card-header>
|
||||
<swp-card-title>
|
||||
<i class="ph ph-clock"></i>
|
||||
<span>Timerapport</span>
|
||||
</swp-card-title>
|
||||
</swp-card-header>
|
||||
<swp-empty-state>
|
||||
<i class="ph ph-clock-counter-clockwise"></i>
|
||||
<span>Timerapport kommer snart</span>
|
||||
</swp-empty-state>
|
||||
</swp-card>
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
@section Scripts {
|
||||
<script type="module">
|
||||
import { createChart } from '/lib/swp-charting/dist/swp-charting.js';
|
||||
import Fuse from '/lib/fuse/fuse.mjs';
|
||||
|
||||
// === SEARCH FUNCTIONALITY ===
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const tableRows = document.querySelectorAll('swp-card.sales-table swp-data-table-row');
|
||||
|
||||
// Build searchable data from table rows
|
||||
const salesData = Array.from(tableRows).map((row, index) => {
|
||||
const cells = row.querySelectorAll('swp-data-table-cell');
|
||||
return {
|
||||
index,
|
||||
invoice: cells[0]?.textContent?.trim() || '',
|
||||
date: cells[1]?.textContent?.trim() || '',
|
||||
customer: cells[2]?.textContent?.trim() || '',
|
||||
employee: cells[3]?.textContent?.trim() || '',
|
||||
services: cells[4]?.textContent?.trim() || '',
|
||||
amount: cells[5]?.textContent?.trim() || '',
|
||||
payment: cells[6]?.textContent?.trim() || '',
|
||||
status: cells[7]?.textContent?.trim() || ''
|
||||
};
|
||||
});
|
||||
|
||||
// Initialize Fuse.js
|
||||
const fuse = new Fuse(salesData, {
|
||||
keys: ['invoice', 'customer', 'employee', 'services', 'amount', 'payment', 'status'],
|
||||
threshold: 0.3,
|
||||
ignoreLocation: true
|
||||
});
|
||||
|
||||
// Parse amount string "1.450 kr" -> 1450
|
||||
function parseAmountFromText(text) {
|
||||
const match = text.match(/-?([\d.]+)/);
|
||||
if (!match) return null;
|
||||
// Remove thousand separators (Danish format uses . for thousands)
|
||||
return parseFloat(match[1].replace(/\./g, ''));
|
||||
}
|
||||
|
||||
// Parse invoice number "#1847" -> 1847
|
||||
function parseInvoiceNumber(text) {
|
||||
const match = text.match(/#(\d+)/);
|
||||
return match ? parseInt(match[1], 10) : null;
|
||||
}
|
||||
|
||||
// Parse range operators from query (supports both amount and invoice ranges)
|
||||
function parseRangeQuery(query) {
|
||||
let textQuery = query;
|
||||
let minAmount = null;
|
||||
let maxAmount = null;
|
||||
let minInvoice = null;
|
||||
let maxInvoice = null;
|
||||
|
||||
// === INVOICE NUMBER RANGES (with # prefix) ===
|
||||
// Match #1840-1845
|
||||
let match = textQuery.match(/#(\d+)-(\d+)/);
|
||||
if (match) {
|
||||
minInvoice = parseInt(match[1], 10);
|
||||
maxInvoice = parseInt(match[2], 10);
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match #>=
|
||||
match = textQuery.match(/#>=\s*(\d+)/);
|
||||
if (match) {
|
||||
minInvoice = parseInt(match[1], 10);
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match #> (but not #>=)
|
||||
match = textQuery.match(/#>\s*(\d+)/);
|
||||
if (match) {
|
||||
minInvoice = parseInt(match[1], 10) + 1;
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match #<=
|
||||
match = textQuery.match(/#<=\s*(\d+)/);
|
||||
if (match) {
|
||||
maxInvoice = parseInt(match[1], 10);
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match #< (but not #<=)
|
||||
match = textQuery.match(/#<\s*(\d+)/);
|
||||
if (match) {
|
||||
maxInvoice = parseInt(match[1], 10) - 1;
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// === AMOUNT RANGES (no prefix) ===
|
||||
// Match range syntax: 400-1000
|
||||
match = textQuery.match(/(\d+)-(\d+)/);
|
||||
if (match) {
|
||||
minAmount = parseFloat(match[1]);
|
||||
maxAmount = parseFloat(match[2]);
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match >=
|
||||
match = textQuery.match(/>=\s*(\d+(?:\.\d+)?)/);
|
||||
if (match) {
|
||||
minAmount = parseFloat(match[1]);
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match > (but not >=)
|
||||
match = textQuery.match(/>\s*(\d+(?:\.\d+)?)/);
|
||||
if (match) {
|
||||
minAmount = parseFloat(match[1]) + 0.01;
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match <=
|
||||
match = textQuery.match(/<=\s*(\d+(?:\.\d+)?)/);
|
||||
if (match) {
|
||||
maxAmount = parseFloat(match[1]);
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
// Match < (but not <=)
|
||||
match = textQuery.match(/<\s*(\d+(?:\.\d+)?)/);
|
||||
if (match) {
|
||||
maxAmount = parseFloat(match[1]) - 0.01;
|
||||
textQuery = textQuery.replace(match[0], '').trim();
|
||||
}
|
||||
|
||||
return { textQuery, minAmount, maxAmount, minInvoice, maxInvoice };
|
||||
}
|
||||
|
||||
// Search handler with range support
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
const query = e.target.value.trim();
|
||||
|
||||
if (!query) {
|
||||
// Show all rows
|
||||
tableRows.forEach(row => row.style.display = '');
|
||||
return;
|
||||
}
|
||||
|
||||
const { textQuery, minAmount, maxAmount, minInvoice, maxInvoice } = parseRangeQuery(query);
|
||||
|
||||
// Start with all indices
|
||||
let matchedIndices = new Set(salesData.map((_, i) => i));
|
||||
|
||||
// Apply invoice number range filter
|
||||
if (minInvoice !== null || maxInvoice !== null) {
|
||||
matchedIndices = new Set(
|
||||
salesData
|
||||
.filter((item) => {
|
||||
const invoiceNum = parseInvoiceNumber(item.invoice);
|
||||
if (invoiceNum === null) return false;
|
||||
if (minInvoice !== null && invoiceNum < minInvoice) return false;
|
||||
if (maxInvoice !== null && invoiceNum > maxInvoice) return false;
|
||||
return true;
|
||||
})
|
||||
.map(item => item.index)
|
||||
);
|
||||
}
|
||||
|
||||
// Apply amount range filter
|
||||
if (minAmount !== null || maxAmount !== null) {
|
||||
matchedIndices = new Set(
|
||||
[...matchedIndices].filter(i => {
|
||||
const amount = parseAmountFromText(salesData[i].amount);
|
||||
if (amount === null) return false;
|
||||
if (minAmount !== null && amount < minAmount) return false;
|
||||
if (maxAmount !== null && amount > maxAmount) return false;
|
||||
return true;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Apply Fuse.js text search if there's remaining text
|
||||
if (textQuery) {
|
||||
const fuseResults = fuse.search(textQuery);
|
||||
const textMatches = new Set(fuseResults.map(r => r.item.index));
|
||||
matchedIndices = new Set([...matchedIndices].filter(i => textMatches.has(i)));
|
||||
}
|
||||
|
||||
tableRows.forEach((row, index) => {
|
||||
row.style.display = matchedIndices.has(index) ? '' : 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// Revenue bar chart
|
||||
createChart(document.getElementById('revenueChart'), {
|
||||
height: 240,
|
||||
xAxis: {
|
||||
categories: ['Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec', 'Jan']
|
||||
},
|
||||
yAxis: {
|
||||
format: (v) => `${Math.round(v / 1000)}k`
|
||||
},
|
||||
series: [{
|
||||
name: 'Omsætning',
|
||||
color: '#00897b',
|
||||
type: 'bar',
|
||||
unit: ' kr',
|
||||
data: [
|
||||
{ x: 'Feb', y: 142500 },
|
||||
{ x: 'Mar', y: 168200 },
|
||||
{ x: 'Apr', y: 155800 },
|
||||
{ x: 'Maj', y: 178400 },
|
||||
{ x: 'Jun', y: 145600 },
|
||||
{ x: 'Jul', y: 98200 },
|
||||
{ x: 'Aug', y: 134500 },
|
||||
{ x: 'Sep', y: 189300 },
|
||||
{ x: 'Okt', y: 201400 },
|
||||
{ x: 'Nov', y: 178900 },
|
||||
{ x: 'Dec', y: 245600 },
|
||||
{ x: 'Jan', y: 187230 }
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
// Payment methods pie chart
|
||||
createChart(document.getElementById('paymentChart'), {
|
||||
height: 240,
|
||||
series: [
|
||||
{ name: 'Kort', color: '#1976d2', type: 'pie', data: [{ x: '', y: 892400 }], unit: ' kr', pie: { innerRadius: 40, outerRadius: 90 } },
|
||||
{ name: 'MobilePay', color: '#5C6BC0', type: 'pie', data: [{ x: '', y: 445200 }], unit: ' kr', pie: { innerRadius: 40, outerRadius: 90 } },
|
||||
{ name: 'Kontant', color: '#43a047', type: 'pie', data: [{ x: '', y: 234800 }], unit: ' kr', pie: { innerRadius: 40, outerRadius: 90 } },
|
||||
{ name: 'Faktura', color: '#f59e0b', type: 'pie', data: [{ x: '', y: 178500 }], unit: ' kr', pie: { innerRadius: 40, outerRadius: 90 } },
|
||||
{ name: 'Fordelskort', color: '#8b5cf6', type: 'pie', data: [{ x: '', y: 74700 }], unit: ' kr', pie: { innerRadius: 40, outerRadius: 90 } }
|
||||
],
|
||||
tooltip: true,
|
||||
legend: { position: 'right', align: 'center' }
|
||||
});
|
||||
</script>
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace PlanTempus.Application.Features.Reports.Pages;
|
||||
|
||||
public class IndexModel : PageModel
|
||||
{
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue