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
|
|
@ -9,7 +9,8 @@
|
||||||
"Bash(npm run analyze-css:*)",
|
"Bash(npm run analyze-css:*)",
|
||||||
"Bash(node:*)",
|
"Bash(node:*)",
|
||||||
"Bash(npx esbuild:*)",
|
"Bash(npx esbuild:*)",
|
||||||
"mcp__ide__getDiagnostics"
|
"mcp__ide__getDiagnostics",
|
||||||
|
"Bash(grep:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -370,3 +370,5 @@ PlanTempus.Application/tmpclaude*
|
||||||
PlanTempus.Application/wwwroot/js/app.js
|
PlanTempus.Application/wwwroot/js/app.js
|
||||||
|
|
||||||
PlanTempus.Application/wwwroot/js/app.js.map
|
PlanTempus.Application/wwwroot/js/app.js.map
|
||||||
|
|
||||||
|
PlanTempus.Application/wwwroot/lib/*
|
||||||
|
|
|
||||||
|
|
@ -155,8 +155,8 @@ public class MockMenuService : IMenuService
|
||||||
{
|
{
|
||||||
Id = "reports",
|
Id = "reports",
|
||||||
Label = "Statistik & Rapporter",
|
Label = "Statistik & Rapporter",
|
||||||
Icon = "ph-chart-bar",
|
Icon = "ph-chart-line-up",
|
||||||
Url = "/reports",
|
Url = "/rapporter",
|
||||||
MinimumRole = UserRole.Manager,
|
MinimumRole = UserRole.Manager,
|
||||||
SortOrder = 1
|
SortOrder = 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
64
PlanTempus.Application/Features/Shared/_NewTodoDrawer.cshtml
Normal file
64
PlanTempus.Application/Features/Shared/_NewTodoDrawer.cshtml
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<swp-new-todo-drawer id="newTodoDrawer">
|
||||||
|
<swp-drawer-header>
|
||||||
|
<swp-drawer-back id="newTodoDrawerBack">
|
||||||
|
<i class="ph ph-caret-left"></i>
|
||||||
|
</swp-drawer-back>
|
||||||
|
<swp-drawer-title>Ny opgave</swp-drawer-title>
|
||||||
|
</swp-drawer-header>
|
||||||
|
|
||||||
|
<swp-drawer-content>
|
||||||
|
<!-- Titel -->
|
||||||
|
<swp-form-field>
|
||||||
|
<swp-section-label>Opgave</swp-section-label>
|
||||||
|
<input type="text" placeholder="Hvad skal du huske?" id="newTodoTitle">
|
||||||
|
</swp-form-field>
|
||||||
|
|
||||||
|
<!-- Dato & Tid -->
|
||||||
|
<swp-form-row>
|
||||||
|
<swp-form-field>
|
||||||
|
<swp-section-label>Dato</swp-section-label>
|
||||||
|
<input type="date" id="newTodoDate">
|
||||||
|
</swp-form-field>
|
||||||
|
<swp-form-field>
|
||||||
|
<swp-section-label>Tid</swp-section-label>
|
||||||
|
<input type="time" id="newTodoTime">
|
||||||
|
</swp-form-field>
|
||||||
|
</swp-form-row>
|
||||||
|
|
||||||
|
<!-- Prioritet -->
|
||||||
|
<swp-form-field>
|
||||||
|
<swp-section-label>Prioritet</swp-section-label>
|
||||||
|
<select id="newTodoPriority">
|
||||||
|
<option value="normal">Normal</option>
|
||||||
|
<option value="high">Høj prioritet</option>
|
||||||
|
<option value="low">Lav prioritet</option>
|
||||||
|
</select>
|
||||||
|
</swp-form-field>
|
||||||
|
|
||||||
|
<!-- Synlighed -->
|
||||||
|
<swp-form-field>
|
||||||
|
<swp-section-label>Synlighed</swp-section-label>
|
||||||
|
<swp-visibility-toggle>
|
||||||
|
<swp-visibility-option class="active" data-value="personal">
|
||||||
|
<i class="ph ph-user"></i>
|
||||||
|
Kun mig
|
||||||
|
</swp-visibility-option>
|
||||||
|
<swp-visibility-option data-value="shared">
|
||||||
|
<i class="ph ph-users"></i>
|
||||||
|
Alle
|
||||||
|
</swp-visibility-option>
|
||||||
|
</swp-visibility-toggle>
|
||||||
|
</swp-form-field>
|
||||||
|
|
||||||
|
<!-- Noter -->
|
||||||
|
<swp-form-field>
|
||||||
|
<swp-section-label>Noter</swp-section-label>
|
||||||
|
<textarea placeholder="Tilføj noter..." id="newTodoNotes"></textarea>
|
||||||
|
</swp-form-field>
|
||||||
|
</swp-drawer-content>
|
||||||
|
|
||||||
|
<swp-drawer-footer>
|
||||||
|
<swp-btn class="secondary" id="cancelNewTodo">Annuller</swp-btn>
|
||||||
|
<swp-btn class="primary" id="saveNewTodo">Gem opgave</swp-btn>
|
||||||
|
</swp-drawer-footer>
|
||||||
|
</swp-new-todo-drawer>
|
||||||
|
|
@ -1,49 +1,74 @@
|
||||||
<swp-profile-drawer id="profileDrawer">
|
<swp-profile-drawer id="profileDrawer">
|
||||||
<swp-drawer-header>
|
<swp-drawer-header>
|
||||||
<swp-drawer-title>Profil</swp-drawer-title>
|
<swp-drawer-title localize="profile.title">Min profil</swp-drawer-title>
|
||||||
<swp-drawer-close id="closeProfileDrawer">
|
<swp-drawer-close id="drawerClose">
|
||||||
<i class="ph ph-x"></i>
|
<i class="ph ph-x"></i>
|
||||||
</swp-drawer-close>
|
</swp-drawer-close>
|
||||||
</swp-drawer-header>
|
</swp-drawer-header>
|
||||||
|
|
||||||
<swp-drawer-content>
|
<swp-drawer-content>
|
||||||
<swp-profile-section>
|
<swp-drawer-profile>
|
||||||
<swp-profile-avatar-large>MJ</swp-profile-avatar-large>
|
<swp-drawer-avatar>MJ</swp-drawer-avatar>
|
||||||
<swp-profile-name-large>Maria Jensen</swp-profile-name-large>
|
<swp-drawer-name>Maria Jensen</swp-drawer-name>
|
||||||
<swp-profile-email>maria@salon.dk</swp-profile-email>
|
<swp-drawer-role>Administrator</swp-drawer-role>
|
||||||
</swp-profile-section>
|
<swp-drawer-email>maria@salon.dk</swp-drawer-email>
|
||||||
|
</swp-drawer-profile>
|
||||||
|
|
||||||
<swp-drawer-divider></swp-drawer-divider>
|
<swp-drawer-section>
|
||||||
|
<swp-drawer-section-label localize="profile.account">Konto</swp-drawer-section-label>
|
||||||
|
<swp-drawer-item>
|
||||||
|
<i class="ph ph-user-circle"></i>
|
||||||
|
<swp-drawer-item-text localize="profile.editProfile">Rediger profil</swp-drawer-item-text>
|
||||||
|
<i class="ph ph-caret-right"></i>
|
||||||
|
</swp-drawer-item>
|
||||||
|
<swp-drawer-item>
|
||||||
|
<i class="ph ph-key"></i>
|
||||||
|
<swp-drawer-item-text localize="profile.changePassword">Skift adgangskode</swp-drawer-item-text>
|
||||||
|
<i class="ph ph-caret-right"></i>
|
||||||
|
</swp-drawer-item>
|
||||||
|
<swp-drawer-item id="openNotificationSettings">
|
||||||
|
<i class="ph ph-bell"></i>
|
||||||
|
<swp-drawer-item-text localize="profile.notifications">Notifikationer</swp-drawer-item-text>
|
||||||
|
<swp-drawer-item-hint>3 ulæste</swp-drawer-item-hint>
|
||||||
|
</swp-drawer-item>
|
||||||
|
<swp-drawer-item id="openTodoDrawer">
|
||||||
|
<i class="ph ph-check-square"></i>
|
||||||
|
<swp-drawer-item-text localize="profile.myTasks">Mine opgaver</swp-drawer-item-text>
|
||||||
|
<swp-drawer-item-hint>2 i dag</swp-drawer-item-hint>
|
||||||
|
</swp-drawer-item>
|
||||||
|
</swp-drawer-section>
|
||||||
|
|
||||||
<swp-drawer-menu>
|
<swp-drawer-section>
|
||||||
<swp-drawer-menu-item>
|
<swp-drawer-section-label localize="profile.appearance">Udseende</swp-drawer-section-label>
|
||||||
<i class="ph ph-user"></i>
|
<swp-theme-toggle id="themeToggleDrawer">
|
||||||
<span>Min profil</span>
|
<swp-theme-option data-theme="light" title="Lyst tema">
|
||||||
</swp-drawer-menu-item>
|
<i class="ph ph-sun"></i>
|
||||||
<swp-drawer-menu-item>
|
</swp-theme-option>
|
||||||
<i class="ph ph-gear"></i>
|
<swp-theme-option data-theme="dark" class="active" title="Mørkt tema">
|
||||||
<span>Indstillinger</span>
|
<i class="ph ph-moon"></i>
|
||||||
</swp-drawer-menu-item>
|
</swp-theme-option>
|
||||||
</swp-drawer-menu>
|
</swp-theme-toggle>
|
||||||
|
</swp-drawer-section>
|
||||||
|
|
||||||
<swp-drawer-divider></swp-drawer-divider>
|
<swp-drawer-section>
|
||||||
|
<swp-drawer-section-label localize="profile.support">Support</swp-drawer-section-label>
|
||||||
<swp-theme-toggle>
|
<swp-drawer-item>
|
||||||
<swp-theme-label>
|
<i class="ph ph-question"></i>
|
||||||
<i class="ph ph-moon"></i>
|
<swp-drawer-item-text localize="profile.helpSupport">Hjælp & Support</swp-drawer-item-text>
|
||||||
<span>Mørk tilstand</span>
|
<i class="ph ph-caret-right"></i>
|
||||||
</swp-theme-label>
|
</swp-drawer-item>
|
||||||
<swp-toggle-switch id="themeToggle">
|
<swp-drawer-item>
|
||||||
<input type="checkbox" id="themeCheckbox">
|
<i class="ph ph-info"></i>
|
||||||
<swp-toggle-track></swp-toggle-track>
|
<swp-drawer-item-text localize="profile.about">Om PlanTempus</swp-drawer-item-text>
|
||||||
</swp-toggle-switch>
|
<swp-drawer-item-hint>v2.1.0</swp-drawer-item-hint>
|
||||||
</swp-theme-toggle>
|
</swp-drawer-item>
|
||||||
|
</swp-drawer-section>
|
||||||
</swp-drawer-content>
|
</swp-drawer-content>
|
||||||
|
|
||||||
<swp-drawer-footer>
|
<swp-drawer-footer>
|
||||||
<swp-drawer-action class="logout" id="logoutBtn">
|
<swp-drawer-action class="logout" id="logoutBtn">
|
||||||
<i class="ph ph-sign-out"></i>
|
<i class="ph ph-sign-out"></i>
|
||||||
<span>Log ud</span>
|
<span localize="profile.logout">Log ud</span>
|
||||||
</swp-drawer-action>
|
</swp-drawer-action>
|
||||||
</swp-drawer-footer>
|
</swp-drawer-footer>
|
||||||
</swp-profile-drawer>
|
</swp-profile-drawer>
|
||||||
|
|
|
||||||
141
PlanTempus.Application/Features/Shared/_TodoDrawer.cshtml
Normal file
141
PlanTempus.Application/Features/Shared/_TodoDrawer.cshtml
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
<swp-todo-drawer id="todoDrawer">
|
||||||
|
<swp-drawer-header>
|
||||||
|
<swp-drawer-back id="todoDrawerBack">
|
||||||
|
<i class="ph ph-caret-left"></i>
|
||||||
|
</swp-drawer-back>
|
||||||
|
<swp-drawer-title>Mine opgaver</swp-drawer-title>
|
||||||
|
<swp-drawer-header-actions>
|
||||||
|
<swp-btn class="primary small" id="addTodoBtn">
|
||||||
|
<i class="ph ph-plus"></i>
|
||||||
|
Ny opgave
|
||||||
|
</swp-btn>
|
||||||
|
</swp-drawer-header-actions>
|
||||||
|
</swp-drawer-header>
|
||||||
|
|
||||||
|
<swp-drawer-content>
|
||||||
|
<!-- I dag -->
|
||||||
|
<swp-todo-section>
|
||||||
|
<swp-todo-section-header>
|
||||||
|
<i class="ph ph-caret-down"></i>
|
||||||
|
<swp-todo-section-title>I dag</swp-todo-section-title>
|
||||||
|
<swp-todo-section-count>3</swp-todo-section-count>
|
||||||
|
</swp-todo-section-header>
|
||||||
|
<swp-todo-items>
|
||||||
|
<swp-todo-item>
|
||||||
|
<swp-todo-checkbox>
|
||||||
|
<i class="ph ph-check"></i>
|
||||||
|
</swp-todo-checkbox>
|
||||||
|
<swp-todo-content>
|
||||||
|
<swp-todo-title>Ring til leverandør om ordre</swp-todo-title>
|
||||||
|
<swp-todo-meta>
|
||||||
|
<swp-todo-time>
|
||||||
|
<i class="ph ph-clock"></i>
|
||||||
|
10:00
|
||||||
|
</swp-todo-time>
|
||||||
|
</swp-todo-meta>
|
||||||
|
</swp-todo-content>
|
||||||
|
</swp-todo-item>
|
||||||
|
<swp-todo-item data-completed="true">
|
||||||
|
<swp-todo-checkbox>
|
||||||
|
<i class="ph ph-check"></i>
|
||||||
|
</swp-todo-checkbox>
|
||||||
|
<swp-todo-content>
|
||||||
|
<swp-todo-title>Bestil shampoo fra Wella</swp-todo-title>
|
||||||
|
</swp-todo-content>
|
||||||
|
</swp-todo-item>
|
||||||
|
<swp-todo-item>
|
||||||
|
<swp-todo-checkbox>
|
||||||
|
<i class="ph ph-check"></i>
|
||||||
|
</swp-todo-checkbox>
|
||||||
|
<swp-todo-content>
|
||||||
|
<swp-todo-title>Opdater prisliste for 2025</swp-todo-title>
|
||||||
|
<swp-todo-meta>
|
||||||
|
<swp-todo-priority class="high">
|
||||||
|
<i class="ph ph-flag"></i>
|
||||||
|
Høj
|
||||||
|
</swp-todo-priority>
|
||||||
|
</swp-todo-meta>
|
||||||
|
</swp-todo-content>
|
||||||
|
</swp-todo-item>
|
||||||
|
</swp-todo-items>
|
||||||
|
</swp-todo-section>
|
||||||
|
|
||||||
|
<!-- Denne uge -->
|
||||||
|
<swp-todo-section>
|
||||||
|
<swp-todo-section-header>
|
||||||
|
<i class="ph ph-caret-down"></i>
|
||||||
|
<swp-todo-section-title>Denne uge</swp-todo-section-title>
|
||||||
|
<swp-todo-section-count>2</swp-todo-section-count>
|
||||||
|
</swp-todo-section-header>
|
||||||
|
<swp-todo-items>
|
||||||
|
<swp-todo-item>
|
||||||
|
<swp-todo-checkbox>
|
||||||
|
<i class="ph ph-check"></i>
|
||||||
|
</swp-todo-checkbox>
|
||||||
|
<swp-todo-content>
|
||||||
|
<swp-todo-title>Rengør og vedligehold udstyr</swp-todo-title>
|
||||||
|
<swp-todo-meta>
|
||||||
|
<swp-todo-date>
|
||||||
|
<i class="ph ph-calendar"></i>
|
||||||
|
Onsdag
|
||||||
|
</swp-todo-date>
|
||||||
|
</swp-todo-meta>
|
||||||
|
</swp-todo-content>
|
||||||
|
</swp-todo-item>
|
||||||
|
<swp-todo-item>
|
||||||
|
<swp-todo-checkbox>
|
||||||
|
<i class="ph ph-check"></i>
|
||||||
|
</swp-todo-checkbox>
|
||||||
|
<swp-todo-content>
|
||||||
|
<swp-todo-title>Medarbejdersamtale med Jonas</swp-todo-title>
|
||||||
|
<swp-todo-meta>
|
||||||
|
<swp-todo-date>
|
||||||
|
<i class="ph ph-calendar"></i>
|
||||||
|
Fredag
|
||||||
|
</swp-todo-date>
|
||||||
|
<swp-todo-time>
|
||||||
|
<i class="ph ph-clock"></i>
|
||||||
|
14:00
|
||||||
|
</swp-todo-time>
|
||||||
|
</swp-todo-meta>
|
||||||
|
</swp-todo-content>
|
||||||
|
</swp-todo-item>
|
||||||
|
</swp-todo-items>
|
||||||
|
</swp-todo-section>
|
||||||
|
|
||||||
|
<!-- Udført -->
|
||||||
|
<swp-todo-section class="collapsed">
|
||||||
|
<swp-todo-section-header>
|
||||||
|
<i class="ph ph-caret-down"></i>
|
||||||
|
<swp-todo-section-title>Udført</swp-todo-section-title>
|
||||||
|
<swp-todo-section-count>3</swp-todo-section-count>
|
||||||
|
</swp-todo-section-header>
|
||||||
|
<swp-todo-items>
|
||||||
|
<swp-todo-item data-completed="true">
|
||||||
|
<swp-todo-checkbox>
|
||||||
|
<i class="ph ph-check"></i>
|
||||||
|
</swp-todo-checkbox>
|
||||||
|
<swp-todo-content>
|
||||||
|
<swp-todo-title>Send faktura til kunde</swp-todo-title>
|
||||||
|
</swp-todo-content>
|
||||||
|
</swp-todo-item>
|
||||||
|
<swp-todo-item data-completed="true">
|
||||||
|
<swp-todo-checkbox>
|
||||||
|
<i class="ph ph-check"></i>
|
||||||
|
</swp-todo-checkbox>
|
||||||
|
<swp-todo-content>
|
||||||
|
<swp-todo-title>Opdater åbningstider på Google</swp-todo-title>
|
||||||
|
</swp-todo-content>
|
||||||
|
</swp-todo-item>
|
||||||
|
<swp-todo-item data-completed="true">
|
||||||
|
<swp-todo-checkbox>
|
||||||
|
<i class="ph ph-check"></i>
|
||||||
|
</swp-todo-checkbox>
|
||||||
|
<swp-todo-content>
|
||||||
|
<swp-todo-title>Bestil nye håndklæder</swp-todo-title>
|
||||||
|
</swp-todo-content>
|
||||||
|
</swp-todo-item>
|
||||||
|
</swp-todo-items>
|
||||||
|
</swp-todo-section>
|
||||||
|
</swp-drawer-content>
|
||||||
|
</swp-todo-drawer>
|
||||||
|
|
@ -35,6 +35,7 @@
|
||||||
<link rel="stylesheet" href="~/css/services.css">
|
<link rel="stylesheet" href="~/css/services.css">
|
||||||
<link rel="stylesheet" href="~/css/customers.css">
|
<link rel="stylesheet" href="~/css/customers.css">
|
||||||
<link rel="stylesheet" href="~/css/settings.css">
|
<link rel="stylesheet" href="~/css/settings.css">
|
||||||
|
<link rel="stylesheet" href="~/css/reports.css">
|
||||||
@await RenderSectionAsync("Styles", required: false)
|
@await RenderSectionAsync("Styles", required: false)
|
||||||
</head>
|
</head>
|
||||||
<body class="has-demo-banner">
|
<body class="has-demo-banner">
|
||||||
|
|
@ -62,6 +63,8 @@
|
||||||
</swp-app-layout>
|
</swp-app-layout>
|
||||||
|
|
||||||
<partial name="_ProfileDrawer" />
|
<partial name="_ProfileDrawer" />
|
||||||
|
<partial name="_TodoDrawer" />
|
||||||
|
<partial name="_NewTodoDrawer" />
|
||||||
<swp-drawer-overlay id="drawerOverlay"></swp-drawer-overlay>
|
<swp-drawer-overlay id="drawerOverlay"></swp-drawer-overlay>
|
||||||
|
|
||||||
<script type="module" src="~/js/app.js"></script>
|
<script type="module" src="~/js/app.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
<swp-profile-drawer id="profileDrawer">
|
|
||||||
<swp-drawer-header>
|
|
||||||
<swp-drawer-title localize="profile.title">Profil</swp-drawer-title>
|
|
||||||
<swp-drawer-close id="closeProfileDrawer">
|
|
||||||
<i class="ph ph-x"></i>
|
|
||||||
</swp-drawer-close>
|
|
||||||
</swp-drawer-header>
|
|
||||||
|
|
||||||
<swp-drawer-content>
|
|
||||||
<swp-profile-section>
|
|
||||||
<swp-profile-avatar-large>MJ</swp-profile-avatar-large>
|
|
||||||
<swp-profile-name-large>Maria Jensen</swp-profile-name-large>
|
|
||||||
<swp-profile-email>maria@salon.dk</swp-profile-email>
|
|
||||||
</swp-profile-section>
|
|
||||||
|
|
||||||
<swp-drawer-divider></swp-drawer-divider>
|
|
||||||
|
|
||||||
<swp-drawer-menu>
|
|
||||||
<swp-drawer-menu-item>
|
|
||||||
<i class="ph ph-user"></i>
|
|
||||||
<span localize="profile.myProfile">Min profil</span>
|
|
||||||
</swp-drawer-menu-item>
|
|
||||||
<swp-drawer-menu-item>
|
|
||||||
<i class="ph ph-gear"></i>
|
|
||||||
<span localize="profile.settings">Indstillinger</span>
|
|
||||||
</swp-drawer-menu-item>
|
|
||||||
</swp-drawer-menu>
|
|
||||||
|
|
||||||
<swp-drawer-divider></swp-drawer-divider>
|
|
||||||
|
|
||||||
<swp-theme-toggle>
|
|
||||||
<swp-theme-label>
|
|
||||||
<i class="ph ph-moon"></i>
|
|
||||||
<span localize="profile.darkMode">Mørk tilstand</span>
|
|
||||||
</swp-theme-label>
|
|
||||||
<swp-toggle-switch id="themeToggle">
|
|
||||||
<input type="checkbox" id="themeCheckbox">
|
|
||||||
<swp-toggle-track></swp-toggle-track>
|
|
||||||
</swp-toggle-switch>
|
|
||||||
</swp-theme-toggle>
|
|
||||||
</swp-drawer-content>
|
|
||||||
|
|
||||||
<swp-drawer-footer>
|
|
||||||
<swp-drawer-action class="logout" id="logoutBtn">
|
|
||||||
<i class="ph ph-sign-out"></i>
|
|
||||||
<span localize="profile.logout">Log ud</span>
|
|
||||||
</swp-drawer-action>
|
|
||||||
</swp-drawer-footer>
|
|
||||||
</swp-profile-drawer>
|
|
||||||
7
PlanTempus.Application/package-lock.json
generated
7
PlanTempus.Application/package-lock.json
generated
|
|
@ -5,6 +5,7 @@
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@sevenweirdpeople/swp-charting": "^0.2.2",
|
||||||
"fuse.js": "^7.1.0"
|
"fuse.js": "^7.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -483,6 +484,12 @@
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@sevenweirdpeople/swp-charting": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sevenweirdpeople/swp-charting/-/swp-charting-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-q9p7TOSMAq6I0t6jGEWpmjR7l2H8q8G0TnXbIpDutCz5a2JEqMDFe0NGBGcCwze2rvvRnRvCz8P2zGMQlHmphw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ansi-regex": {
|
"node_modules/ansi-regex": {
|
||||||
"version": "6.2.2",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
"analyze-css": "node analyze-css.js"
|
"analyze-css": "node analyze-css.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@sevenweirdpeople/swp-charting": "^0.2.2",
|
||||||
"fuse.js": "^7.1.0"
|
"fuse.js": "^7.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,18 +33,5 @@ swp-main-content {
|
||||||
|
|
||||||
/* ===========================================
|
/* ===========================================
|
||||||
DRAWER OVERLAY
|
DRAWER OVERLAY
|
||||||
|
Styles moved to drawers.css for consistency with calpoc
|
||||||
=========================================== */
|
=========================================== */
|
||||||
swp-drawer-overlay {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
z-index: var(--z-overlay);
|
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
transition: opacity var(--transition-normal), visibility var(--transition-normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
swp-drawer-overlay.active {
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,27 @@
|
||||||
* Drawers - Slide-in Panels
|
* Drawers - Slide-in Panels
|
||||||
*
|
*
|
||||||
* Profile drawer, notifications drawer, etc.
|
* Profile drawer, notifications drawer, etc.
|
||||||
|
* Matches calpoc pattern from Calendar/wwwroot/poc-layout.html
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
DRAWER OVERLAY
|
||||||
|
=========================================== */
|
||||||
|
swp-drawer-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 200ms ease, visibility 200ms ease;
|
||||||
|
z-index: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-drawer-overlay.active {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
/* ===========================================
|
/* ===========================================
|
||||||
BASE DRAWER (Generic)
|
BASE DRAWER (Generic)
|
||||||
=========================================== */
|
=========================================== */
|
||||||
|
|
@ -15,12 +34,12 @@
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
border-left: 1px solid var(--color-border);
|
border-left: 1px solid var(--color-border);
|
||||||
box-shadow: var(--shadow-lg);
|
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
|
||||||
z-index: var(--z-drawer);
|
z-index: var(--z-drawer);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
transition: transform var(--transition-normal);
|
transition: transform 200ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-drawer].active,
|
[data-drawer].active,
|
||||||
|
|
@ -46,12 +65,12 @@ swp-todo-drawer {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
border-left: 1px solid var(--color-border);
|
border-left: 1px solid var(--color-border);
|
||||||
box-shadow: var(--shadow-lg);
|
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
|
||||||
z-index: var(--z-drawer);
|
z-index: 1000;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
transition: transform var(--transition-normal);
|
transition: transform 200ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-profile-drawer.active,
|
swp-profile-drawer.active,
|
||||||
|
|
@ -67,17 +86,15 @@ swp-drawer-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: var(--spacing-10) var(--spacing-12);
|
padding: 14px 16px;
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
background: var(--color-background-alt);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-drawer-title {
|
swp-drawer-title {
|
||||||
display: flex;
|
font-size: 14px;
|
||||||
align-items: center;
|
font-weight: 600;
|
||||||
gap: var(--spacing-2);
|
|
||||||
font-size: var(--font-size-lg);
|
|
||||||
font-weight: var(--font-weight-semibold);
|
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,11 +111,11 @@ swp-drawer-close {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: none;
|
border: none;
|
||||||
background: var(--color-background-alt);
|
background: transparent;
|
||||||
border-radius: var(--radius-md);
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
transition: all var(--transition-fast);
|
transition: all 150ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-drawer-close:hover {
|
swp-drawer-close:hover {
|
||||||
|
|
@ -110,6 +127,12 @@ swp-drawer-close i {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
swp-drawer-header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-3);
|
||||||
|
}
|
||||||
|
|
||||||
/* ===========================================
|
/* ===========================================
|
||||||
DRAWER CONTENT / BODY
|
DRAWER CONTENT / BODY
|
||||||
=========================================== */
|
=========================================== */
|
||||||
|
|
@ -117,10 +140,10 @@ swp-drawer-content,
|
||||||
swp-drawer-body {
|
swp-drawer-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: var(--spacing-8);
|
padding: 20px 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--spacing-5);
|
gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-drawer-divider {
|
swp-drawer-divider {
|
||||||
|
|
@ -130,134 +153,142 @@ swp-drawer-divider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===========================================
|
/* ===========================================
|
||||||
PROFILE SECTION
|
PROFILE SECTION (Centered in drawer)
|
||||||
=========================================== */
|
=========================================== */
|
||||||
swp-profile-section {
|
swp-drawer-profile {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: var(--spacing-4) 0;
|
padding-bottom: 20px;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-profile-avatar-large {
|
swp-drawer-avatar {
|
||||||
width: 64px;
|
width: 72px;
|
||||||
height: 64px;
|
height: 72px;
|
||||||
border-radius: var(--radius-full);
|
border-radius: 50%;
|
||||||
background: var(--color-teal);
|
background: var(--color-purple);
|
||||||
color: white;
|
color: white;
|
||||||
font-size: var(--font-size-3xl);
|
|
||||||
font-weight: var(--font-weight-semibold);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: var(--spacing-3);
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-profile-name-large {
|
swp-drawer-name {
|
||||||
font-size: var(--font-size-lg);
|
font-size: 18px;
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: 600;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
margin-bottom: var(--spacing-1);
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-profile-email {
|
swp-drawer-role {
|
||||||
font-size: var(--font-size-sm);
|
font-size: 13px;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-drawer-email {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-teal);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===========================================
|
/* ===========================================
|
||||||
DRAWER MENU
|
DRAWER SECTIONS
|
||||||
=========================================== */
|
=========================================== */
|
||||||
swp-drawer-menu {
|
swp-drawer-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--spacing-1);
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-drawer-menu-item {
|
swp-drawer-section-label {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
DRAWER ITEMS (Menu items)
|
||||||
|
=========================================== */
|
||||||
|
swp-drawer-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-3);
|
gap: 12px;
|
||||||
padding: var(--spacing-3) var(--spacing-3);
|
padding: 12px;
|
||||||
border-radius: var(--border-radius);
|
background: var(--color-background-alt);
|
||||||
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background var(--transition-fast);
|
transition: background 150ms ease;
|
||||||
color: var(--color-text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-drawer-menu-item:hover {
|
swp-drawer-item:hover {
|
||||||
background: var(--color-background-hover);
|
background: var(--color-background-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-drawer-menu-item i {
|
swp-drawer-item i {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
swp-drawer-item-text {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-drawer-item-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
/* ===========================================
|
/* ===========================================
|
||||||
THEME TOGGLE
|
THEME TOGGLE (Two icon buttons)
|
||||||
=========================================== */
|
=========================================== */
|
||||||
swp-theme-toggle {
|
swp-theme-toggle {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
border: 1px solid var(--color-border);
|
||||||
justify-content: space-between;
|
border-radius: 8px;
|
||||||
padding: var(--spacing-3);
|
overflow: hidden;
|
||||||
border-radius: var(--border-radius);
|
|
||||||
background: var(--color-background);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-theme-label {
|
swp-theme-option {
|
||||||
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-3);
|
justify-content: center;
|
||||||
color: var(--color-text);
|
padding: 12px;
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 150ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-theme-label i {
|
swp-theme-option:first-child {
|
||||||
|
border-right: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-theme-option i {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
|
transition: color 150ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-toggle-switch {
|
swp-theme-option:hover {
|
||||||
position: relative;
|
background: var(--color-background-hover);
|
||||||
width: 44px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-toggle-switch input {
|
swp-theme-option.active {
|
||||||
opacity: 0;
|
background: color-mix(in srgb, var(--color-teal) 12%, transparent);
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-toggle-track {
|
swp-theme-option.active i {
|
||||||
position: absolute;
|
color: var(--color-teal);
|
||||||
cursor: pointer;
|
|
||||||
inset: 0;
|
|
||||||
background: var(--color-border);
|
|
||||||
border-radius: 12px;
|
|
||||||
transition: background var(--transition-fast);
|
|
||||||
}
|
|
||||||
|
|
||||||
swp-toggle-track::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
left: 3px;
|
|
||||||
bottom: 3px;
|
|
||||||
background: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: transform var(--transition-fast);
|
|
||||||
}
|
|
||||||
|
|
||||||
swp-toggle-switch input:checked + swp-toggle-track {
|
|
||||||
background: var(--color-teal);
|
|
||||||
}
|
|
||||||
|
|
||||||
swp-toggle-switch input:checked + swp-toggle-track::before {
|
|
||||||
transform: translateX(20px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===========================================
|
/* ===========================================
|
||||||
|
|
@ -265,9 +296,9 @@ swp-toggle-switch input:checked + swp-toggle-track::before {
|
||||||
=========================================== */
|
=========================================== */
|
||||||
swp-drawer-footer {
|
swp-drawer-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
flex-direction: column;
|
||||||
gap: var(--spacing-3);
|
gap: 8px;
|
||||||
padding: var(--spacing-4) var(--spacing-5);
|
padding: 16px;
|
||||||
border-top: 1px solid var(--color-border);
|
border-top: 1px solid var(--color-border);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
@ -276,28 +307,485 @@ swp-drawer-action {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: var(--spacing-2);
|
gap: 8px;
|
||||||
width: 100%;
|
padding: 12px;
|
||||||
padding: var(--spacing-3);
|
font-size: 14px;
|
||||||
font-size: var(--font-size-base);
|
|
||||||
font-family: var(--font-family);
|
font-family: var(--font-family);
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: var(--border-radius);
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all var(--transition-fast);
|
transition: all 150ms ease;
|
||||||
|
}
|
||||||
& i {
|
|
||||||
font-size: 18px;
|
swp-drawer-action i {
|
||||||
}
|
font-size: 18px;
|
||||||
|
}
|
||||||
&:hover {
|
|
||||||
background: var(--color-background-hover);
|
swp-drawer-action:hover {
|
||||||
}
|
background: var(--color-background-hover);
|
||||||
|
}
|
||||||
&.logout:hover {
|
|
||||||
color: var(--color-red);
|
swp-drawer-action.logout:hover {
|
||||||
border-color: var(--color-red);
|
color: var(--color-red);
|
||||||
}
|
border-color: var(--color-red);
|
||||||
|
background: var(--bg-red-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
MARK ALL READ BUTTON (Notification drawer)
|
||||||
|
=========================================== */
|
||||||
|
swp-mark-read-btn {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-teal);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-mark-read-btn:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
TODO DRAWER (slides out to the left of profile drawer)
|
||||||
|
=========================================== */
|
||||||
|
swp-todo-drawer {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 320px; /* Position to the left of profile drawer (320px wide) */
|
||||||
|
bottom: 0;
|
||||||
|
width: 380px;
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
opacity 200ms ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 999; /* Below profile drawer */
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-drawer.active {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-drawer swp-drawer-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-drawer swp-drawer-title {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-drawer-back {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
transition: all 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-drawer-back:hover {
|
||||||
|
background: var(--color-background-hover);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-drawer-back i {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header button with text */
|
||||||
|
swp-todo-drawer swp-btn.primary.small {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: var(--font-family);
|
||||||
|
font-weight: 500;
|
||||||
|
background: var(--color-teal);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-drawer swp-btn.primary.small:hover {
|
||||||
|
background: color-mix(in srgb, var(--color-teal) 85%, black);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-drawer swp-btn.primary.small i {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-drawer swp-drawer-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 4px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-section-header i {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
transition: transform 200ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-section.collapsed swp-todo-section-header i {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-section.collapsed swp-todo-items {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-section-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-section-count {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
background: var(--color-background);
|
||||||
|
border-radius: 10px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-items {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-item:hover {
|
||||||
|
background: var(--color-background-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-item[data-completed="true"] {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-item[data-completed="true"] swp-todo-title {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-checkbox {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid var(--color-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 1px;
|
||||||
|
transition: all 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-checkbox i {
|
||||||
|
font-size: 14px;
|
||||||
|
color: white;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-item[data-completed="true"] swp-todo-checkbox {
|
||||||
|
background: var(--color-teal);
|
||||||
|
border-color: var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-item[data-completed="true"] swp-todo-checkbox i {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-meta i {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-time {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-priority {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-priority.high {
|
||||||
|
color: var(--color-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-priority.low {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-todo-date {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
NEW TODO DRAWER (slides out to the left of todo drawer)
|
||||||
|
=========================================== */
|
||||||
|
swp-new-todo-drawer {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 700px; /* 320px (profile) + 380px (todo) */
|
||||||
|
bottom: 0;
|
||||||
|
width: 340px;
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
opacity 200ms ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 998; /* Below todo drawer */
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer.active {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer swp-drawer-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer swp-drawer-title {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer swp-drawer-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer swp-drawer-footer {
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Elements */
|
||||||
|
swp-new-todo-drawer swp-section-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer swp-form-field {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer swp-form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer swp-form-row swp-form-field {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer input[type="text"],
|
||||||
|
swp-new-todo-drawer input[type="date"],
|
||||||
|
swp-new-todo-drawer input[type="time"],
|
||||||
|
swp-new-todo-drawer select,
|
||||||
|
swp-new-todo-drawer textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: var(--font-family);
|
||||||
|
color: var(--color-text);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer input:focus,
|
||||||
|
swp-new-todo-drawer select:focus,
|
||||||
|
swp-new-todo-drawer textarea:focus {
|
||||||
|
border-color: var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer input::placeholder,
|
||||||
|
swp-new-todo-drawer textarea::placeholder {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer select {
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 256 256'%3E%3Cpath fill='%23666' d='M213.66 101.66l-80 80a8 8 0 0 1-11.32 0l-80-80a8 8 0 0 1 11.32-11.32L128 164.69l74.34-74.35a8 8 0 0 1 11.32 11.32Z'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 10px center;
|
||||||
|
padding-right: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer textarea {
|
||||||
|
resize: none;
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Visibility toggle */
|
||||||
|
swp-visibility-toggle {
|
||||||
|
display: flex;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-visibility-option {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 150ms ease;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-visibility-option:first-child {
|
||||||
|
border-right: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-visibility-option i {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-visibility-option:hover {
|
||||||
|
background: var(--color-background-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-visibility-option.active {
|
||||||
|
background: color-mix(in srgb, var(--color-teal) 12%, transparent);
|
||||||
|
color: var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer buttons */
|
||||||
|
swp-new-todo-drawer swp-btn {
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: var(--font-family);
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer swp-btn.secondary {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer swp-btn.secondary:hover {
|
||||||
|
background: var(--color-background-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer swp-btn.primary {
|
||||||
|
background: var(--color-teal);
|
||||||
|
border: 1px solid var(--color-teal);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-new-todo-drawer swp-btn.primary:hover {
|
||||||
|
background: color-mix(in srgb, var(--color-teal) 85%, black);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
395
PlanTempus.Application/wwwroot/css/reports.css
Normal file
395
PlanTempus.Application/wwwroot/css/reports.css
Normal file
|
|
@ -0,0 +1,395 @@
|
||||||
|
/**
|
||||||
|
* Reports - Statistik og Rapporter
|
||||||
|
*
|
||||||
|
* Feature-specific styling for reports pages.
|
||||||
|
* Reuses: swp-stats-row (stats.css), swp-stat-card (stats.css),
|
||||||
|
* swp-tab-bar (tabs.css), swp-data-table (components.css)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
CHARTS GRID (2-column layout)
|
||||||
|
=========================================== */
|
||||||
|
swp-charts-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 1fr;
|
||||||
|
gap: var(--card-gap);
|
||||||
|
margin-bottom: var(--section-gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
swp-charts-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
CHART CARD
|
||||||
|
=========================================== */
|
||||||
|
swp-chart-card {
|
||||||
|
display: block;
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-chart-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--spacing-6) var(--card-padding);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-chart-title {
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-chart-hint {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-chart-container {
|
||||||
|
display: block;
|
||||||
|
height: 240px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
FILTER BAR
|
||||||
|
=========================================== */
|
||||||
|
swp-filter-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-6);
|
||||||
|
padding: var(--spacing-6) var(--card-padding);
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
margin-bottom: var(--section-gap);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-search-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-3);
|
||||||
|
padding: var(--spacing-3) var(--spacing-4);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--color-surface);
|
||||||
|
flex: 1;
|
||||||
|
max-width: 350px;
|
||||||
|
|
||||||
|
& i {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& input {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
font-family: var(--font-family);
|
||||||
|
width: 100%;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--color-text);
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-filter-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-filter-label {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-filter-bar select,
|
||||||
|
swp-filter-bar input[type="date"] {
|
||||||
|
padding: var(--spacing-3) var(--spacing-4);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
font-family: var(--font-family);
|
||||||
|
background: var(--color-surface);
|
||||||
|
color: var(--color-text);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-filter-bar input[type="date"] {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
SALES TABLE - Grid columns
|
||||||
|
=========================================== */
|
||||||
|
swp-card.sales-table {
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-card.sales-table swp-data-table {
|
||||||
|
grid-template-columns: 100px 140px minmax(120px, 1fr) 120px minmax(140px, 1.2fr) 100px 120px 100px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-card.sales-table swp-data-table-header {
|
||||||
|
padding: var(--spacing-4) var(--card-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-card.sales-table swp-data-table-row {
|
||||||
|
padding: var(--spacing-5) var(--card-padding);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
INVOICE CELL
|
||||||
|
=========================================== */
|
||||||
|
swp-invoice-cell {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
color: var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
DATETIME CELL
|
||||||
|
=========================================== */
|
||||||
|
swp-datetime-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
|
& .date {
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .time {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
CUSTOMER CELL
|
||||||
|
=========================================== */
|
||||||
|
swp-customer-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
|
& .name {
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .phone {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
SERVICES CELL
|
||||||
|
=========================================== */
|
||||||
|
swp-services-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
|
& .main {
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .more {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
AMOUNT CELL
|
||||||
|
=========================================== */
|
||||||
|
swp-amount-cell {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
text-align: right;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
PAYMENT BADGE
|
||||||
|
=========================================== */
|
||||||
|
swp-payment-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-2);
|
||||||
|
padding: var(--spacing-2) var(--spacing-4);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
|
||||||
|
& i {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.card {
|
||||||
|
background: color-mix(in srgb, var(--color-blue) 12%, transparent);
|
||||||
|
color: var(--color-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.cash {
|
||||||
|
background: color-mix(in srgb, var(--color-green) 12%, transparent);
|
||||||
|
color: var(--color-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mobilepay {
|
||||||
|
background: color-mix(in srgb, var(--color-blue) 12%, transparent);
|
||||||
|
color: var(--color-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.invoice {
|
||||||
|
background: color-mix(in srgb, var(--color-amber) 12%, transparent);
|
||||||
|
color: var(--color-amber);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.giftcard {
|
||||||
|
background: color-mix(in srgb, var(--color-purple) 12%, transparent);
|
||||||
|
color: var(--color-purple);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
STATUS BADGE ADDITIONS (paid, credited)
|
||||||
|
=========================================== */
|
||||||
|
swp-status-badge.paid {
|
||||||
|
background: var(--bg-green-strong);
|
||||||
|
color: var(--color-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-status-badge.credited {
|
||||||
|
background: var(--bg-purple-strong);
|
||||||
|
color: var(--color-purple);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
ROW ARROW
|
||||||
|
=========================================== */
|
||||||
|
swp-row-arrow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
& i {
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
transition: transform var(--transition-fast), color var(--transition-fast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-data-table-row:hover swp-row-arrow i {
|
||||||
|
transform: translateX(4px);
|
||||||
|
color: var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
TABLE FOOTER + PAGINATION
|
||||||
|
=========================================== */
|
||||||
|
swp-table-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--spacing-5) var(--card-padding);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-pagination {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-page-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-background-hover);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: var(--color-teal);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
& i {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
RESPONSIVE
|
||||||
|
=========================================== */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
swp-card.sales-table swp-data-table {
|
||||||
|
grid-template-columns: 100px 130px 1fr 100px 100px 100px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide employee and services columns */
|
||||||
|
swp-card.sales-table swp-data-table-cell:nth-child(4),
|
||||||
|
swp-card.sales-table swp-data-table-cell:nth-child(5) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
swp-card.sales-table swp-data-table {
|
||||||
|
grid-template-columns: 100px 1fr 100px 100px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide customer column */
|
||||||
|
swp-card.sales-table swp-data-table-cell:nth-child(3) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-filter-bar {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-search-input {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue