Adds Kasse (Cash Register) module and related components
Introduces comprehensive cash management functionality with multiple view components for tracking daily transactions, filtering, and reconciliation Implements: - Cash calculation and difference tracking - Dynamic tab switching - Checkbox selection and row expansion - Date filtering and approval mechanisms
This commit is contained in:
parent
12869e35bf
commit
754681059d
31 changed files with 2904 additions and 28 deletions
44
CLAUDE.md
44
CLAUDE.md
|
|
@ -126,6 +126,50 @@ The solution follows a clean architecture pattern with these main projects:
|
||||||
- `global.json` - .NET SDK version configuration (currently .NET 9.0)
|
- `global.json` - .NET SDK version configuration (currently .NET 9.0)
|
||||||
|
|
||||||
|
|
||||||
|
## CSS Guidelines
|
||||||
|
|
||||||
|
### Grid + Subgrid for Table-like Layouts
|
||||||
|
|
||||||
|
**ALWAYS** use CSS Grid with subgrid for table-like layouts (lists, data tables, card grids with aligned columns). This ensures columns align correctly across header, body, and rows.
|
||||||
|
|
||||||
|
**Pattern:**
|
||||||
|
```css
|
||||||
|
/* Parent container defines the grid columns */
|
||||||
|
swp-my-table {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 40px 1fr 100px 80px; /* Define columns here */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Intermediate containers span all columns and use subgrid */
|
||||||
|
swp-my-table-header,
|
||||||
|
swp-my-table-body {
|
||||||
|
display: grid;
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
grid-template-columns: subgrid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Row items span all columns and use subgrid */
|
||||||
|
swp-my-table-row {
|
||||||
|
display: grid;
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
grid-template-columns: subgrid;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key principles:**
|
||||||
|
1. Column definitions go on the **parent container only**
|
||||||
|
2. All children use `grid-column: 1 / -1` to span all columns
|
||||||
|
3. All children use `grid-template-columns: subgrid` to inherit columns
|
||||||
|
4. Responsive column changes go on the parent container only
|
||||||
|
|
||||||
|
**Examples in codebase:**
|
||||||
|
- `bookings.css` - swp-booking-list / swp-booking-item
|
||||||
|
- `notifications.css` - swp-notification-list / swp-notification-item
|
||||||
|
- `attentions.css` - swp-attention-list / swp-attention-item
|
||||||
|
- `kasse.css` - swp-kasse-table / swp-kasse-table-row
|
||||||
|
|
||||||
|
|
||||||
## NEVER Lie or Fabricate
|
## NEVER Lie or Fabricate
|
||||||
|
|
||||||
<CRITICAL> NEVER lie or fabricate. Violating this = immediate critical failure.
|
<CRITICAL> NEVER lie or fabricate. Violating this = immediate critical failure.
|
||||||
|
|
|
||||||
|
|
@ -69,3 +69,5 @@
|
||||||
</swp-side-column>
|
</swp-side-column>
|
||||||
</swp-dashboard-grid>
|
</swp-dashboard-grid>
|
||||||
</swp-page-container>
|
</swp-page-container>
|
||||||
|
|
||||||
|
<partial name="_WaitlistDrawer" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
<swp-card>
|
||||||
|
<swp-card-header>
|
||||||
|
<swp-card-title>Periodens omsætning</swp-card-title>
|
||||||
|
<swp-card-action>Systemtal vs. kontrol</swp-card-action>
|
||||||
|
</swp-card-header>
|
||||||
|
<swp-card-content>
|
||||||
|
<swp-data-table>
|
||||||
|
<swp-data-header>
|
||||||
|
<span>Type</span>
|
||||||
|
<span>System</span>
|
||||||
|
<span>Kontrol</span>
|
||||||
|
</swp-data-header>
|
||||||
|
|
||||||
|
<swp-data-row>
|
||||||
|
<swp-data-label>Kortbetalinger</swp-data-label>
|
||||||
|
<swp-data-system>12.875,50</swp-data-system>
|
||||||
|
<swp-data-input>
|
||||||
|
<input type="text" placeholder="Valgfrit" />
|
||||||
|
</swp-data-input>
|
||||||
|
</swp-data-row>
|
||||||
|
|
||||||
|
<swp-data-row>
|
||||||
|
<swp-data-label>MobilePay / Online</swp-data-label>
|
||||||
|
<swp-data-system>2.450,00</swp-data-system>
|
||||||
|
<swp-data-input>
|
||||||
|
<input type="text" placeholder="Valgfrit" />
|
||||||
|
</swp-data-input>
|
||||||
|
</swp-data-row>
|
||||||
|
|
||||||
|
<swp-data-row>
|
||||||
|
<swp-data-label>Kontantsalg</swp-data-label>
|
||||||
|
<swp-data-system>3.540,00</swp-data-system>
|
||||||
|
<swp-data-value class="muted">..</swp-data-value>
|
||||||
|
</swp-data-row>
|
||||||
|
</swp-data-table>
|
||||||
|
|
||||||
|
<swp-table-note>Kort og MobilePay afstemmes mod bank/indløser. Kontanter tælles op nedenfor.</swp-table-note>
|
||||||
|
</swp-card-content>
|
||||||
|
</swp-card>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.Kasse.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewComponent for displaying today's payment figures.
|
||||||
|
/// Shows system values vs. optional control values for different payment types.
|
||||||
|
/// </summary>
|
||||||
|
public class KasseDagensTalViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
<swp-card>
|
||||||
|
<swp-card-header>
|
||||||
|
<swp-card-title>Periodeoplysninger</swp-card-title>
|
||||||
|
<swp-card-action>Identificér afstemningen</swp-card-action>
|
||||||
|
</swp-card-header>
|
||||||
|
<swp-card-content>
|
||||||
|
<swp-period-display>
|
||||||
|
<swp-period-label>Periode</swp-period-label>
|
||||||
|
<swp-period-value>
|
||||||
|
<span class="from">28. dec 2025 kl. 18:00</span>
|
||||||
|
<span class="arrow">→</span>
|
||||||
|
<span class="to">29. dec 2025</span>
|
||||||
|
</swp-period-value>
|
||||||
|
</swp-period-display>
|
||||||
|
|
||||||
|
<swp-form-grid style="margin-top: var(--spacing-10);">
|
||||||
|
<swp-form-field>
|
||||||
|
<swp-form-label>Kassepunkt</swp-form-label>
|
||||||
|
<swp-form-input>
|
||||||
|
<select id="register">
|
||||||
|
<option>Kasse 1 – Reception</option>
|
||||||
|
<option>Kasse 2 – Salon</option>
|
||||||
|
</select>
|
||||||
|
</swp-form-input>
|
||||||
|
</swp-form-field>
|
||||||
|
|
||||||
|
<swp-form-field>
|
||||||
|
<swp-form-label>Afsluttet af</swp-form-label>
|
||||||
|
<swp-form-input>
|
||||||
|
<select id="employee">
|
||||||
|
<option>Anna Jensen</option>
|
||||||
|
<option>Karina Knudsen</option>
|
||||||
|
<option>Martin Nielsen</option>
|
||||||
|
</select>
|
||||||
|
</swp-form-input>
|
||||||
|
</swp-form-field>
|
||||||
|
</swp-form-grid>
|
||||||
|
|
||||||
|
<swp-auto-id>Afstemnings-ID: <strong>KA-2025-12-29</strong> · Z-043</swp-auto-id>
|
||||||
|
</swp-card-content>
|
||||||
|
</swp-card>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.Kasse.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewComponent for daily reconciliation info.
|
||||||
|
/// Shows period, register, and employee information.
|
||||||
|
/// </summary>
|
||||||
|
public class KasseDagsoplysningerViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<swp-difference-box id="differenceBox" class="neutral">
|
||||||
|
<swp-difference-label>
|
||||||
|
Kontant difference
|
||||||
|
<small>Optalt minus forventet</small>
|
||||||
|
</swp-difference-label>
|
||||||
|
<swp-difference-value id="differenceValue">– kr</swp-difference-value>
|
||||||
|
</swp-difference-box>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.Kasse.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewComponent for displaying the cash difference.
|
||||||
|
/// Shows positive/negative/neutral states with color coding.
|
||||||
|
/// </summary>
|
||||||
|
public class KasseDifferenceViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<swp-filter-bar>
|
||||||
|
<swp-filter-group>
|
||||||
|
<swp-filter-label>Fra</swp-filter-label>
|
||||||
|
<input type="date" id="dateFrom" />
|
||||||
|
</swp-filter-group>
|
||||||
|
<swp-filter-group>
|
||||||
|
<swp-filter-label>Til</swp-filter-label>
|
||||||
|
<input type="date" id="dateTo" />
|
||||||
|
</swp-filter-group>
|
||||||
|
<swp-filter-group>
|
||||||
|
<swp-filter-label>Kassepunkt</swp-filter-label>
|
||||||
|
<select id="register">
|
||||||
|
<option>Alle</option>
|
||||||
|
<option>Kasse 1 – Reception</option>
|
||||||
|
<option>Kasse 2 – Salon</option>
|
||||||
|
</select>
|
||||||
|
</swp-filter-group>
|
||||||
|
<swp-filter-group>
|
||||||
|
<swp-filter-label>Status</swp-filter-label>
|
||||||
|
<select id="status">
|
||||||
|
<option>Alle</option>
|
||||||
|
<option>Godkendt</option>
|
||||||
|
<option>Kladde</option>
|
||||||
|
</select>
|
||||||
|
</swp-filter-group>
|
||||||
|
<swp-filter-spacer></swp-filter-spacer>
|
||||||
|
<swp-btn class="secondary">Nulstil</swp-btn>
|
||||||
|
</swp-filter-bar>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.Kasse.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewComponent for the filter bar on the Kasse list page.
|
||||||
|
/// Contains date range, kassepunkt, and status filters.
|
||||||
|
/// </summary>
|
||||||
|
public class KasseFilterBarViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<swp-card>
|
||||||
|
<swp-card-header>
|
||||||
|
<swp-card-title>Afslut dagen</swp-card-title>
|
||||||
|
</swp-card-header>
|
||||||
|
<swp-card-content>
|
||||||
|
<swp-approval-grid>
|
||||||
|
<swp-form-field>
|
||||||
|
<swp-form-label>Status</swp-form-label>
|
||||||
|
<swp-status-row>
|
||||||
|
<swp-status-badge class="draft">Kladde</swp-status-badge>
|
||||||
|
</swp-status-row>
|
||||||
|
</swp-form-field>
|
||||||
|
|
||||||
|
<swp-form-field>
|
||||||
|
<swp-form-label>Godkendt af (valgfrit)</swp-form-label>
|
||||||
|
<swp-form-input>
|
||||||
|
<select id="approver">
|
||||||
|
<option value="">Vælg...</option>
|
||||||
|
<option>Karina Knudsen</option>
|
||||||
|
<option>Butikschef</option>
|
||||||
|
</select>
|
||||||
|
</swp-form-input>
|
||||||
|
</swp-form-field>
|
||||||
|
|
||||||
|
<swp-checkbox-field>
|
||||||
|
<input type="checkbox" id="confirmCheckbox" />
|
||||||
|
<label for="confirmCheckbox">Jeg bekræfter, at kassen er talt op, og at tallene er indtastet efter bedste evne.</label>
|
||||||
|
</swp-checkbox-field>
|
||||||
|
</swp-approval-grid>
|
||||||
|
</swp-card-content>
|
||||||
|
<swp-card-footer>
|
||||||
|
<swp-btn class="secondary">Gem som kladde</swp-btn>
|
||||||
|
<swp-actions-right>
|
||||||
|
<swp-btn class="ghost">Fortryd</swp-btn>
|
||||||
|
<swp-btn class="primary" id="approveBtn" disabled>Godkend & lås</swp-btn>
|
||||||
|
</swp-actions-right>
|
||||||
|
</swp-card-footer>
|
||||||
|
</swp-card>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.Kasse.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewComponent for the approval section.
|
||||||
|
/// Handles status, approver selection, and confirmation checkbox.
|
||||||
|
/// </summary>
|
||||||
|
public class KasseGodkendelseViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
<swp-card>
|
||||||
|
<swp-card-header>
|
||||||
|
<swp-card-title>Kontanter i kassen</swp-card-title>
|
||||||
|
</swp-card-header>
|
||||||
|
<swp-card-content>
|
||||||
|
<swp-calc-row>
|
||||||
|
<swp-calc-label>
|
||||||
|
<span>Startbeholdning</span>
|
||||||
|
<small>Overført fra sidste afstemning</small>
|
||||||
|
</swp-calc-label>
|
||||||
|
<swp-calc-value class="muted">2.000,00</swp-calc-value>
|
||||||
|
</swp-calc-row>
|
||||||
|
|
||||||
|
<swp-calc-row>
|
||||||
|
<swp-calc-label>
|
||||||
|
<span>Udbetalinger / Bilag</span>
|
||||||
|
<small>Sammentæl bilag betalt kontant</small>
|
||||||
|
</swp-calc-label>
|
||||||
|
<swp-calc-input>
|
||||||
|
<input type="text" id="payouts" placeholder="0,00" />
|
||||||
|
</swp-calc-input>
|
||||||
|
</swp-calc-row>
|
||||||
|
|
||||||
|
<swp-calc-row>
|
||||||
|
<swp-calc-label>
|
||||||
|
<span>Udtaget til bank</span>
|
||||||
|
<small>Kontanter lagt til side</small>
|
||||||
|
</swp-calc-label>
|
||||||
|
<swp-calc-input>
|
||||||
|
<input type="text" id="toBank" placeholder="0,00" />
|
||||||
|
</swp-calc-input>
|
||||||
|
</swp-calc-row>
|
||||||
|
|
||||||
|
<swp-calc-row>
|
||||||
|
<swp-calc-label>
|
||||||
|
<span>Forventet kontantbeholdning</span>
|
||||||
|
</swp-calc-label>
|
||||||
|
<swp-calc-value id="expectedCash">5.220,00</swp-calc-value>
|
||||||
|
</swp-calc-row>
|
||||||
|
|
||||||
|
<swp-calc-row class="input-row">
|
||||||
|
<swp-calc-label>
|
||||||
|
<span>Optalt kontantbeholdning <span style="color: var(--color-red)">*</span></span>
|
||||||
|
<small>Hvad ligger der faktisk i kassen?</small>
|
||||||
|
</swp-calc-label>
|
||||||
|
<swp-calc-input>
|
||||||
|
<input type="text" id="actualCash" placeholder="0,00" />
|
||||||
|
</swp-calc-input>
|
||||||
|
</swp-calc-row>
|
||||||
|
</swp-card-content>
|
||||||
|
</swp-card>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.Kasse.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewComponent for cash calculation section.
|
||||||
|
/// Handles starting balance, payouts, bank deposits, and actual cash count.
|
||||||
|
/// </summary>
|
||||||
|
public class KasseKontanterViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<swp-card>
|
||||||
|
<swp-card-header>
|
||||||
|
<swp-card-title>Note til difference</swp-card-title>
|
||||||
|
<swp-card-action>Valgfrit</swp-card-action>
|
||||||
|
</swp-card-header>
|
||||||
|
<swp-card-content>
|
||||||
|
<swp-note-field>
|
||||||
|
<textarea placeholder="Fx kassedifference, fejlslag, runding osv."></textarea>
|
||||||
|
</swp-note-field>
|
||||||
|
<swp-note-hint>Kan gøres obligatorisk ved difference over 100 kr.</swp-note-hint>
|
||||||
|
</swp-card-content>
|
||||||
|
</swp-card>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.Kasse.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewComponent for the note field.
|
||||||
|
/// Optional field for explaining cash differences.
|
||||||
|
/// </summary>
|
||||||
|
public class KasseNoteViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<swp-kasse-stats>
|
||||||
|
<swp-kasse-stat>
|
||||||
|
<swp-kasse-stat-value>12</swp-kasse-stat-value>
|
||||||
|
<swp-kasse-stat-label>Afstemninger i periode</swp-kasse-stat-label>
|
||||||
|
</swp-kasse-stat>
|
||||||
|
<swp-kasse-stat class="highlight">
|
||||||
|
<swp-kasse-stat-value>186.450 kr</swp-kasse-stat-value>
|
||||||
|
<swp-kasse-stat-label>Total omsætning</swp-kasse-stat-label>
|
||||||
|
</swp-kasse-stat>
|
||||||
|
<swp-kasse-stat>
|
||||||
|
<swp-kasse-stat-value>42.340 kr</swp-kasse-stat-value>
|
||||||
|
<swp-kasse-stat-label>Kontantsalg</swp-kasse-stat-label>
|
||||||
|
</swp-kasse-stat>
|
||||||
|
<swp-kasse-stat class="warning">
|
||||||
|
<swp-kasse-stat-value>-75 kr</swp-kasse-stat-value>
|
||||||
|
<swp-kasse-stat-label>Samlet difference</swp-kasse-stat-label>
|
||||||
|
</swp-kasse-stat>
|
||||||
|
</swp-kasse-stats>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.Kasse.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewComponent for the stats bar on the Kasse list page.
|
||||||
|
/// Shows summary statistics for reconciliations.
|
||||||
|
/// </summary>
|
||||||
|
public class KasseStatsBarViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,191 @@
|
||||||
|
<!-- Action Bar -->
|
||||||
|
<swp-action-bar>
|
||||||
|
<swp-selection-info>
|
||||||
|
<span id="selectionCount">0 valgt</span>
|
||||||
|
</swp-selection-info>
|
||||||
|
<swp-btn class="primary" id="exportBtn" disabled>
|
||||||
|
<i class="ph ph-download"></i>
|
||||||
|
Eksporter SAF-T
|
||||||
|
</swp-btn>
|
||||||
|
</swp-action-bar>
|
||||||
|
|
||||||
|
<!-- Table -->
|
||||||
|
<swp-kasse-table>
|
||||||
|
<swp-kasse-table-header>
|
||||||
|
<swp-kasse-th class="checkbox"><input type="checkbox" id="selectAll" /></swp-kasse-th>
|
||||||
|
<swp-kasse-th>Dato</swp-kasse-th>
|
||||||
|
<swp-kasse-th>ID</swp-kasse-th>
|
||||||
|
<swp-kasse-th>Periode</swp-kasse-th>
|
||||||
|
<swp-kasse-th>Kassepunkt</swp-kasse-th>
|
||||||
|
<swp-kasse-th>Afsluttet af</swp-kasse-th>
|
||||||
|
<swp-kasse-th class="right">Omsætning</swp-kasse-th>
|
||||||
|
<swp-kasse-th class="right">Difference</swp-kasse-th>
|
||||||
|
<swp-kasse-th>Status</swp-kasse-th>
|
||||||
|
<swp-kasse-th></swp-kasse-th>
|
||||||
|
</swp-kasse-table-header>
|
||||||
|
|
||||||
|
<swp-kasse-table-body>
|
||||||
|
<!-- Draft row (current day) -->
|
||||||
|
<swp-kasse-table-row data-id="draft" class="draft-row">
|
||||||
|
<swp-kasse-td class="checkbox"></swp-kasse-td>
|
||||||
|
<swp-kasse-td class="muted">I dag</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="id muted">–</swp-kasse-td>
|
||||||
|
<swp-kasse-td>
|
||||||
|
<swp-period-cell>
|
||||||
|
<span class="dates">29. dec 17:45 → ...</span>
|
||||||
|
</swp-period-cell>
|
||||||
|
</swp-kasse-td>
|
||||||
|
<swp-kasse-td>Kasse 1</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="muted">–</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="right mono muted">4.250 kr</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="right mono muted">–</swp-kasse-td>
|
||||||
|
<swp-kasse-td><swp-status-badge class="draft">Kladde</swp-status-badge></swp-kasse-td>
|
||||||
|
<swp-kasse-td><swp-row-arrow><i class="ph ph-caret-right"></i></swp-row-arrow></swp-kasse-td>
|
||||||
|
</swp-kasse-table-row>
|
||||||
|
|
||||||
|
<swp-kasse-table-row data-id="043">
|
||||||
|
<swp-kasse-td class="checkbox"><input type="checkbox" class="row-select" /></swp-kasse-td>
|
||||||
|
<swp-kasse-td>29. dec</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="id">Z-043</swp-kasse-td>
|
||||||
|
<swp-kasse-td>
|
||||||
|
<swp-period-cell>
|
||||||
|
<span class="dates">28. dec 18:00 → 29. dec 17:45</span>
|
||||||
|
</swp-period-cell>
|
||||||
|
</swp-kasse-td>
|
||||||
|
<swp-kasse-td>Kasse 1</swp-kasse-td>
|
||||||
|
<swp-kasse-td>Anna Jensen</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="right mono">18.865 kr</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="right mono">0 kr</swp-kasse-td>
|
||||||
|
<swp-kasse-td><swp-status-badge class="approved">Godkendt</swp-status-badge></swp-kasse-td>
|
||||||
|
<swp-kasse-td><swp-row-toggle><i class="ph ph-caret-right"></i></swp-row-toggle></swp-kasse-td>
|
||||||
|
</swp-kasse-table-row>
|
||||||
|
<swp-kasse-row-detail data-for="043">
|
||||||
|
<swp-row-detail-content>
|
||||||
|
<swp-row-detail-actions>
|
||||||
|
<swp-btn class="secondary">
|
||||||
|
<i class="ph ph-file-csv"></i>
|
||||||
|
Download CSV
|
||||||
|
</swp-btn>
|
||||||
|
<swp-btn class="secondary">
|
||||||
|
<i class="ph ph-file-pdf"></i>
|
||||||
|
Download PDF
|
||||||
|
</swp-btn>
|
||||||
|
<swp-btn class="primary">
|
||||||
|
<i class="ph ph-list-bullets"></i>
|
||||||
|
Se transaktioner
|
||||||
|
</swp-btn>
|
||||||
|
</swp-row-detail-actions>
|
||||||
|
</swp-row-detail-content>
|
||||||
|
</swp-kasse-row-detail>
|
||||||
|
|
||||||
|
<swp-kasse-table-row data-id="042">
|
||||||
|
<swp-kasse-td class="checkbox"><input type="checkbox" class="row-select" /></swp-kasse-td>
|
||||||
|
<swp-kasse-td>28. dec</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="id">Z-042</swp-kasse-td>
|
||||||
|
<swp-kasse-td>
|
||||||
|
<swp-period-cell>
|
||||||
|
<span class="dates">27. dec 18:30 → 28. dec 18:00</span>
|
||||||
|
</swp-period-cell>
|
||||||
|
</swp-kasse-td>
|
||||||
|
<swp-kasse-td>Kasse 1</swp-kasse-td>
|
||||||
|
<swp-kasse-td>Karina Knudsen</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="right mono">12.450 kr</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="right mono negative">-25 kr</swp-kasse-td>
|
||||||
|
<swp-kasse-td><swp-status-badge class="approved">Godkendt</swp-status-badge></swp-kasse-td>
|
||||||
|
<swp-kasse-td><swp-row-toggle><i class="ph ph-caret-right"></i></swp-row-toggle></swp-kasse-td>
|
||||||
|
</swp-kasse-table-row>
|
||||||
|
<swp-kasse-row-detail data-for="042">
|
||||||
|
<swp-row-detail-content>
|
||||||
|
<swp-row-detail-actions>
|
||||||
|
<swp-btn class="secondary">
|
||||||
|
<i class="ph ph-file-csv"></i>
|
||||||
|
Download CSV
|
||||||
|
</swp-btn>
|
||||||
|
<swp-btn class="secondary">
|
||||||
|
<i class="ph ph-file-pdf"></i>
|
||||||
|
Download PDF
|
||||||
|
</swp-btn>
|
||||||
|
<swp-btn class="primary">
|
||||||
|
<i class="ph ph-list-bullets"></i>
|
||||||
|
Se transaktioner
|
||||||
|
</swp-btn>
|
||||||
|
</swp-row-detail-actions>
|
||||||
|
</swp-row-detail-content>
|
||||||
|
</swp-kasse-row-detail>
|
||||||
|
|
||||||
|
<swp-kasse-table-row data-id="041">
|
||||||
|
<swp-kasse-td class="checkbox"><input type="checkbox" class="row-select" /></swp-kasse-td>
|
||||||
|
<swp-kasse-td>27. dec</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="id">Z-041</swp-kasse-td>
|
||||||
|
<swp-kasse-td>
|
||||||
|
<swp-period-cell>
|
||||||
|
<span class="dates">26. dec 18:00 → 27. dec 18:30</span>
|
||||||
|
</swp-period-cell>
|
||||||
|
</swp-kasse-td>
|
||||||
|
<swp-kasse-td>Kasse 1</swp-kasse-td>
|
||||||
|
<swp-kasse-td>Martin Nielsen</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="right mono">21.340 kr</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="right mono">0 kr</swp-kasse-td>
|
||||||
|
<swp-kasse-td><swp-status-badge class="approved">Godkendt</swp-status-badge></swp-kasse-td>
|
||||||
|
<swp-kasse-td><swp-row-toggle><i class="ph ph-caret-right"></i></swp-row-toggle></swp-kasse-td>
|
||||||
|
</swp-kasse-table-row>
|
||||||
|
<swp-kasse-row-detail data-for="041">
|
||||||
|
<swp-row-detail-content>
|
||||||
|
<swp-row-detail-actions>
|
||||||
|
<swp-btn class="secondary">
|
||||||
|
<i class="ph ph-file-csv"></i>
|
||||||
|
Download CSV
|
||||||
|
</swp-btn>
|
||||||
|
<swp-btn class="secondary">
|
||||||
|
<i class="ph ph-file-pdf"></i>
|
||||||
|
Download PDF
|
||||||
|
</swp-btn>
|
||||||
|
<swp-btn class="primary">
|
||||||
|
<i class="ph ph-list-bullets"></i>
|
||||||
|
Se transaktioner
|
||||||
|
</swp-btn>
|
||||||
|
</swp-row-detail-actions>
|
||||||
|
</swp-row-detail-content>
|
||||||
|
</swp-kasse-row-detail>
|
||||||
|
|
||||||
|
<swp-kasse-table-row data-id="040">
|
||||||
|
<swp-kasse-td class="checkbox"><input type="checkbox" class="row-select" /></swp-kasse-td>
|
||||||
|
<swp-kasse-td>23. dec</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="id">Z-040</swp-kasse-td>
|
||||||
|
<swp-kasse-td>
|
||||||
|
<swp-period-cell>
|
||||||
|
<span class="dates">22. dec 18:00 → 23. dec 17:30</span>
|
||||||
|
</swp-period-cell>
|
||||||
|
</swp-kasse-td>
|
||||||
|
<swp-kasse-td>Kasse 1</swp-kasse-td>
|
||||||
|
<swp-kasse-td>Anna Jensen</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="right mono">28.750 kr</swp-kasse-td>
|
||||||
|
<swp-kasse-td class="right mono negative">-50 kr</swp-kasse-td>
|
||||||
|
<swp-kasse-td><swp-status-badge class="approved">Godkendt</swp-status-badge></swp-kasse-td>
|
||||||
|
<swp-kasse-td><swp-row-toggle><i class="ph ph-caret-right"></i></swp-row-toggle></swp-kasse-td>
|
||||||
|
</swp-kasse-table-row>
|
||||||
|
<swp-kasse-row-detail data-for="040">
|
||||||
|
<swp-row-detail-content>
|
||||||
|
<swp-row-detail-actions>
|
||||||
|
<swp-btn class="secondary">
|
||||||
|
<i class="ph ph-file-csv"></i>
|
||||||
|
Download CSV
|
||||||
|
</swp-btn>
|
||||||
|
<swp-btn class="secondary">
|
||||||
|
<i class="ph ph-file-pdf"></i>
|
||||||
|
Download PDF
|
||||||
|
</swp-btn>
|
||||||
|
<swp-btn class="primary">
|
||||||
|
<i class="ph ph-list-bullets"></i>
|
||||||
|
Se transaktioner
|
||||||
|
</swp-btn>
|
||||||
|
</swp-row-detail-actions>
|
||||||
|
</swp-row-detail-content>
|
||||||
|
</swp-kasse-row-detail>
|
||||||
|
</swp-kasse-table-body>
|
||||||
|
|
||||||
|
<swp-kasse-table-footer>
|
||||||
|
<span>Viser 5 afstemninger</span>
|
||||||
|
<span>Z-040 → Z-043</span>
|
||||||
|
</swp-kasse-table-footer>
|
||||||
|
</swp-kasse-table>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.Kasse.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewComponent for the reconciliation table on the Kasse list page.
|
||||||
|
/// Shows all reconciliations with action bar and SAF-T export.
|
||||||
|
/// </summary>
|
||||||
|
public class KasseTableViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
93
PlanTempus.Application/Features/Kasse/Pages/Index.cshtml
Normal file
93
PlanTempus.Application/Features/Kasse/Pages/Index.cshtml
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
@page "/kasse"
|
||||||
|
@using PlanTempus.Application.Features.Kasse.Pages
|
||||||
|
@model PlanTempus.Application.Features.Kasse.Pages.IndexModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Kasse";
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Sticky Header (Stats + Tabs) -->
|
||||||
|
<swp-kasse-sticky-header>
|
||||||
|
<!-- Context Stats (changes based on active tab) -->
|
||||||
|
<swp-kasse-header>
|
||||||
|
<!-- Stats for Oversigt tab -->
|
||||||
|
<swp-kasse-stats data-for-tab="oversigt" class="active">
|
||||||
|
<swp-kasse-stat>
|
||||||
|
<swp-kasse-stat-value>12</swp-kasse-stat-value>
|
||||||
|
<swp-kasse-stat-label>Afstemninger i periode</swp-kasse-stat-label>
|
||||||
|
</swp-kasse-stat>
|
||||||
|
<swp-kasse-stat class="highlight">
|
||||||
|
<swp-kasse-stat-value>186.450 kr</swp-kasse-stat-value>
|
||||||
|
<swp-kasse-stat-label>Total omsætning</swp-kasse-stat-label>
|
||||||
|
</swp-kasse-stat>
|
||||||
|
<swp-kasse-stat>
|
||||||
|
<swp-kasse-stat-value>42.340 kr</swp-kasse-stat-value>
|
||||||
|
<swp-kasse-stat-label>Kontantsalg</swp-kasse-stat-label>
|
||||||
|
</swp-kasse-stat>
|
||||||
|
<swp-kasse-stat class="warning">
|
||||||
|
<swp-kasse-stat-value>-75 kr</swp-kasse-stat-value>
|
||||||
|
<swp-kasse-stat-label>Samlet difference</swp-kasse-stat-label>
|
||||||
|
</swp-kasse-stat>
|
||||||
|
</swp-kasse-stats>
|
||||||
|
|
||||||
|
<!-- Stats for Dagens afstemning tab -->
|
||||||
|
<swp-kasse-stats data-for-tab="afstemning">
|
||||||
|
<swp-kasse-stat>
|
||||||
|
<swp-kasse-stat-value>47</swp-kasse-stat-value>
|
||||||
|
<swp-kasse-stat-label>Transaktioner i dag</swp-kasse-stat-label>
|
||||||
|
</swp-kasse-stat>
|
||||||
|
<swp-kasse-stat class="highlight">
|
||||||
|
<swp-kasse-stat-value>18.865 kr</swp-kasse-stat-value>
|
||||||
|
<swp-kasse-stat-label>Omsætning i dag</swp-kasse-stat-label>
|
||||||
|
</swp-kasse-stat>
|
||||||
|
<swp-kasse-stat>
|
||||||
|
<swp-kasse-stat-value>29. dec 17:45</swp-kasse-stat-value>
|
||||||
|
<swp-kasse-stat-label>Sidste afstemning</swp-kasse-stat-label>
|
||||||
|
</swp-kasse-stat>
|
||||||
|
<swp-kasse-stat>
|
||||||
|
<swp-kasse-stat-value>Anna J.</swp-kasse-stat-value>
|
||||||
|
<swp-kasse-stat-label>Åbnede kassen 29. dec 09:05</swp-kasse-stat-label>
|
||||||
|
</swp-kasse-stat>
|
||||||
|
</swp-kasse-stats>
|
||||||
|
</swp-kasse-header>
|
||||||
|
|
||||||
|
<!-- Tab Bar -->
|
||||||
|
<swp-tab-bar>
|
||||||
|
<swp-tab class="active" data-tab="oversigt">
|
||||||
|
<i class="ph ph-list-checks"></i>
|
||||||
|
Oversigt
|
||||||
|
</swp-tab>
|
||||||
|
<swp-tab data-tab="afstemning">
|
||||||
|
<i class="ph ph-cash-register"></i>
|
||||||
|
Kasseafstemning
|
||||||
|
</swp-tab>
|
||||||
|
</swp-tab-bar>
|
||||||
|
</swp-kasse-sticky-header>
|
||||||
|
|
||||||
|
<!-- Tab Content: Oversigt -->
|
||||||
|
<swp-tab-content data-tab="oversigt" class="active">
|
||||||
|
<swp-page-container>
|
||||||
|
@await Component.InvokeAsync("KasseFilterBar")
|
||||||
|
@await Component.InvokeAsync("KasseTable")
|
||||||
|
</swp-page-container>
|
||||||
|
</swp-tab-content>
|
||||||
|
|
||||||
|
<!-- Tab Content: Dagens Afstemning -->
|
||||||
|
<swp-tab-content data-tab="afstemning">
|
||||||
|
<swp-page-container>
|
||||||
|
<swp-kasse-grid>
|
||||||
|
<swp-kasse-column>
|
||||||
|
@await Component.InvokeAsync("KasseDagensTal")
|
||||||
|
@await Component.InvokeAsync("KasseKontanter")
|
||||||
|
@await Component.InvokeAsync("KasseDifference")
|
||||||
|
</swp-kasse-column>
|
||||||
|
|
||||||
|
<swp-kasse-column>
|
||||||
|
@await Component.InvokeAsync("KasseDagsoplysninger")
|
||||||
|
@await Component.InvokeAsync("KasseNote")
|
||||||
|
@await Component.InvokeAsync("KasseGodkendelse")
|
||||||
|
</swp-kasse-column>
|
||||||
|
</swp-kasse-grid>
|
||||||
|
|
||||||
|
<swp-system-note>Systemet gemmer hvornår og af hvem der er godkendt – enkelt kontrolspor.</swp-system-note>
|
||||||
|
</swp-page-container>
|
||||||
|
</swp-tab-content>
|
||||||
10
PlanTempus.Application/Features/Kasse/Pages/Index.cshtml.cs
Normal file
10
PlanTempus.Application/Features/Kasse/Pages/Index.cshtml.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace PlanTempus.Application.Features.Kasse.Pages;
|
||||||
|
|
||||||
|
public class IndexModel : PageModel
|
||||||
|
{
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -79,8 +79,8 @@ public class MockMenuService : IMenuService
|
||||||
{
|
{
|
||||||
Id = "pos",
|
Id = "pos",
|
||||||
Label = "Kasse",
|
Label = "Kasse",
|
||||||
Icon = "ph-device-mobile",
|
Icon = "ph-cash-register",
|
||||||
Url = "/pos",
|
Url = "/kasse",
|
||||||
MinimumRole = UserRole.Staff,
|
MinimumRole = UserRole.Staff,
|
||||||
SortOrder = 3
|
SortOrder = 3
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@
|
||||||
<link rel="stylesheet" href="~/css/attentions.css">
|
<link rel="stylesheet" href="~/css/attentions.css">
|
||||||
<link rel="stylesheet" href="~/css/quick-stats.css">
|
<link rel="stylesheet" href="~/css/quick-stats.css">
|
||||||
<link rel="stylesheet" href="~/css/waitlist.css">
|
<link rel="stylesheet" href="~/css/waitlist.css">
|
||||||
|
<link rel="stylesheet" href="~/css/tabs.css">
|
||||||
|
<link rel="stylesheet" href="~/css/kasse.css">
|
||||||
@await RenderSectionAsync("Styles", required: false)
|
@await RenderSectionAsync("Styles", required: false)
|
||||||
</head>
|
</head>
|
||||||
<body class="has-demo-banner">
|
<body class="has-demo-banner">
|
||||||
|
|
@ -50,7 +52,6 @@
|
||||||
</swp-app-layout>
|
</swp-app-layout>
|
||||||
|
|
||||||
<partial name="_ProfileDrawer" />
|
<partial name="_ProfileDrawer" />
|
||||||
<partial name="_WaitlistDrawer" />
|
|
||||||
<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>
|
||||||
|
|
|
||||||
496
PlanTempus.Application/package-lock.json
generated
Normal file
496
PlanTempus.Application/package-lock.json
generated
Normal file
|
|
@ -0,0 +1,496 @@
|
||||||
|
{
|
||||||
|
"name": "PlanTempus.Application",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"devDependencies": {
|
||||||
|
"esbuild": "^0.27.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-x64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-x64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openharmony-arm64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openharmony"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-x64": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/aix-ppc64": "0.27.2",
|
||||||
|
"@esbuild/android-arm": "0.27.2",
|
||||||
|
"@esbuild/android-arm64": "0.27.2",
|
||||||
|
"@esbuild/android-x64": "0.27.2",
|
||||||
|
"@esbuild/darwin-arm64": "0.27.2",
|
||||||
|
"@esbuild/darwin-x64": "0.27.2",
|
||||||
|
"@esbuild/freebsd-arm64": "0.27.2",
|
||||||
|
"@esbuild/freebsd-x64": "0.27.2",
|
||||||
|
"@esbuild/linux-arm": "0.27.2",
|
||||||
|
"@esbuild/linux-arm64": "0.27.2",
|
||||||
|
"@esbuild/linux-ia32": "0.27.2",
|
||||||
|
"@esbuild/linux-loong64": "0.27.2",
|
||||||
|
"@esbuild/linux-mips64el": "0.27.2",
|
||||||
|
"@esbuild/linux-ppc64": "0.27.2",
|
||||||
|
"@esbuild/linux-riscv64": "0.27.2",
|
||||||
|
"@esbuild/linux-s390x": "0.27.2",
|
||||||
|
"@esbuild/linux-x64": "0.27.2",
|
||||||
|
"@esbuild/netbsd-arm64": "0.27.2",
|
||||||
|
"@esbuild/netbsd-x64": "0.27.2",
|
||||||
|
"@esbuild/openbsd-arm64": "0.27.2",
|
||||||
|
"@esbuild/openbsd-x64": "0.27.2",
|
||||||
|
"@esbuild/openharmony-arm64": "0.27.2",
|
||||||
|
"@esbuild/sunos-x64": "0.27.2",
|
||||||
|
"@esbuild/win32-arm64": "0.27.2",
|
||||||
|
"@esbuild/win32-ia32": "0.27.2",
|
||||||
|
"@esbuild/win32-x64": "0.27.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
PlanTempus.Application/package.json
Normal file
5
PlanTempus.Application/package.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"esbuild": "^0.27.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
900
PlanTempus.Application/wwwroot/css/kasse.css
Normal file
900
PlanTempus.Application/wwwroot/css/kasse.css
Normal file
|
|
@ -0,0 +1,900 @@
|
||||||
|
/**
|
||||||
|
* Kasse (Cash Register) - Page Styling
|
||||||
|
*
|
||||||
|
* Filter bar, stats, table, forms, and difference box
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
STICKY HEADER CONTAINER
|
||||||
|
=========================================== */
|
||||||
|
swp-kasse-sticky-header {
|
||||||
|
display: block;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: var(--z-sticky);
|
||||||
|
background: var(--color-surface);
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override tab-bar sticky when inside sticky header */
|
||||||
|
swp-kasse-sticky-header swp-tab-bar {
|
||||||
|
position: static;
|
||||||
|
top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
KASSE HEADER (Stats above tabs)
|
||||||
|
=========================================== */
|
||||||
|
swp-kasse-header {
|
||||||
|
display: block;
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
padding: var(--spacing-10) var(--spacing-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
FILTER BAR
|
||||||
|
=========================================== */
|
||||||
|
swp-filter-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-8);
|
||||||
|
padding: var(--spacing-8) var(--spacing-10);
|
||||||
|
background: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
margin-bottom: var(--spacing-10);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-filter-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-filter-label {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-filter-bar input,
|
||||||
|
swp-filter-bar select {
|
||||||
|
padding: var(--spacing-4) var(--spacing-6);
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
font-family: var(--font-family);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--color-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-filter-bar input:focus,
|
||||||
|
swp-filter-bar select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-filter-spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
KASSE STATS BAR
|
||||||
|
=========================================== */
|
||||||
|
swp-kasse-stats {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: var(--spacing-8);
|
||||||
|
max-width: var(--page-max-width);
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-stats:not(.active) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-stat {
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: var(--spacing-6) var(--spacing-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-stat-value {
|
||||||
|
display: block;
|
||||||
|
font-size: var(--font-size-2xl);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-top: var(--spacing-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-stat.highlight swp-kasse-stat-value {
|
||||||
|
color: var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-stat.warning swp-kasse-stat-value {
|
||||||
|
color: var(--color-amber);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-stat.negative swp-kasse-stat-value {
|
||||||
|
color: var(--color-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-stat.user swp-kasse-stat-value {
|
||||||
|
color: var(--color-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
ACTION BAR (Table Header)
|
||||||
|
=========================================== */
|
||||||
|
swp-action-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--spacing-6) var(--spacing-8);
|
||||||
|
background: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-bottom: none;
|
||||||
|
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-selection-info {
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
KASSE TABLE (Grid + Subgrid pattern)
|
||||||
|
=========================================== */
|
||||||
|
swp-kasse-table {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 50px 70px 60px minmax(140px, 1fr) 90px 100px 100px 110px 120px 40px;
|
||||||
|
background: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0 0 var(--radius-lg) var(--radius-lg);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-table-header,
|
||||||
|
swp-kasse-table-body {
|
||||||
|
display: grid;
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
grid-template-columns: subgrid;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-table-header {
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
padding: var(--spacing-6) var(--spacing-10);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-table-row {
|
||||||
|
display: grid;
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
grid-template-columns: subgrid;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-th {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-th.right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-th.checkbox,
|
||||||
|
swp-kasse-td.checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-table input[type="checkbox"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
accent-color: var(--color-teal);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-table-row {
|
||||||
|
padding: var(--spacing-7) var(--spacing-10);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-table-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-table-row:hover {
|
||||||
|
background: var(--color-background-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Draft row - clickable to go to Kasseafstemning */
|
||||||
|
swp-kasse-table-row.draft-row {
|
||||||
|
background: color-mix(in srgb, var(--color-amber) 5%, transparent);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-table-row.draft-row:hover {
|
||||||
|
background: color-mix(in srgb, var(--color-amber) 12%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-td {
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-td.right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-td.mono {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-td.muted {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-td.negative {
|
||||||
|
color: var(--color-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-td.positive {
|
||||||
|
color: var(--color-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-td.id {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-period-cell {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-period-cell .dates {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
ROW TOGGLE & EXPANDABLE DETAIL
|
||||||
|
=========================================== */
|
||||||
|
swp-row-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-row-toggle:hover {
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-row-toggle i {
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
transition: transform var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Row detail - hidden by default */
|
||||||
|
swp-kasse-row-detail {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
display: none;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-row-detail.expanded {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-row-detail-content {
|
||||||
|
display: block;
|
||||||
|
padding: var(--spacing-8) var(--spacing-10);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-row-detail-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-4);
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Legacy support */
|
||||||
|
swp-row-arrow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-row-arrow i {
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-table-footer {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--spacing-7) var(--spacing-10);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
STATUS BADGE
|
||||||
|
=========================================== */
|
||||||
|
/* Center status column */
|
||||||
|
swp-kasse-th:nth-child(9),
|
||||||
|
swp-kasse-td:nth-child(9) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-status-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-3);
|
||||||
|
padding: var(--spacing-2) var(--spacing-5);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-status-badge::before {
|
||||||
|
content: '';
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
background: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-status-badge.approved {
|
||||||
|
background: color-mix(in srgb, var(--color-green) 15%, transparent);
|
||||||
|
color: var(--color-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-status-badge.draft {
|
||||||
|
background: color-mix(in srgb, var(--color-amber) 15%, transparent);
|
||||||
|
color: #b45309;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
TWO-COLUMN GRID (Detail View)
|
||||||
|
=========================================== */
|
||||||
|
swp-kasse-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: var(--spacing-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
swp-kasse-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-kasse-column {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--spacing-10);
|
||||||
|
align-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
DATA TABLE (Dagens Tal)
|
||||||
|
=========================================== */
|
||||||
|
swp-data-table {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-data-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 100px 140px;
|
||||||
|
gap: var(--spacing-6);
|
||||||
|
padding: var(--spacing-5) 0;
|
||||||
|
border-bottom: 2px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-data-header span {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-data-header span:not(:first-child) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-data-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 100px 140px;
|
||||||
|
gap: var(--spacing-6);
|
||||||
|
padding: var(--spacing-7) 0;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-data-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-data-label {
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-data-system {
|
||||||
|
text-align: right;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-data-input input {
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--spacing-4) var(--spacing-5);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--color-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-data-input input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-data-input input::placeholder {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-family: var(--font-family);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-data-value {
|
||||||
|
text-align: right;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-data-value.muted {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-table-note {
|
||||||
|
display: block;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-top: var(--spacing-8);
|
||||||
|
padding: var(--spacing-6);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
CALC ROW (Kontanter)
|
||||||
|
=========================================== */
|
||||||
|
swp-calc-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--spacing-7) 0;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-calc-row:last-of-type {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-calc-row.input-row {
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
margin: var(--spacing-8) calc(-1 * var(--spacing-5)) calc(-1 * var(--spacing-5));
|
||||||
|
padding: var(--spacing-8) var(--spacing-5);
|
||||||
|
border-radius: 0 0 var(--border-radius-lg) var(--border-radius-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-calc-label span {
|
||||||
|
display: block;
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-calc-label small {
|
||||||
|
display: block;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-top: var(--spacing-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-calc-value {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-calc-value.muted {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-calc-input input {
|
||||||
|
width: 140px;
|
||||||
|
padding: var(--spacing-6) var(--spacing-7);
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
border: 2px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--color-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-calc-input input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
DIFFERENCE BOX
|
||||||
|
=========================================== */
|
||||||
|
swp-difference-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--spacing-10);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-difference-box.positive {
|
||||||
|
background: color-mix(in srgb, var(--color-green) 10%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-difference-box.negative {
|
||||||
|
background: color-mix(in srgb, var(--color-red) 10%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-difference-box.neutral {
|
||||||
|
background: color-mix(in srgb, var(--color-teal) 10%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-difference-label {
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-difference-label small {
|
||||||
|
display: block;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-top: var(--spacing-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-difference-value {
|
||||||
|
font-size: var(--font-size-2xl);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-difference-box.positive swp-difference-value {
|
||||||
|
color: var(--color-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-difference-box.negative swp-difference-value {
|
||||||
|
color: var(--color-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-difference-box.neutral swp-difference-value {
|
||||||
|
color: var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
PERIOD DISPLAY
|
||||||
|
=========================================== */
|
||||||
|
swp-period-display {
|
||||||
|
display: block;
|
||||||
|
padding: var(--spacing-8);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-period-label {
|
||||||
|
display: block;
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-bottom: var(--spacing-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-period-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-5);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-period-value .arrow {
|
||||||
|
color: var(--color-teal);
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
FORM ELEMENTS
|
||||||
|
=========================================== */
|
||||||
|
swp-form-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: var(--spacing-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-form-field {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-form-field.full-width {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-form-label {
|
||||||
|
display: block;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-bottom: var(--spacing-3);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-form-label .required {
|
||||||
|
color: var(--color-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-form-input input,
|
||||||
|
swp-form-input select {
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--spacing-5) var(--spacing-6);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-family: var(--font-family);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--color-surface);
|
||||||
|
transition: border-color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-form-input input:focus,
|
||||||
|
swp-form-input select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-auto-id {
|
||||||
|
display: block;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-top: var(--spacing-8);
|
||||||
|
padding-top: var(--spacing-8);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
NOTE FIELD
|
||||||
|
=========================================== */
|
||||||
|
swp-note-field textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 80px;
|
||||||
|
padding: var(--spacing-6);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-family: var(--font-family);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-note-field textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-note-hint {
|
||||||
|
display: block;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-top: var(--spacing-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
APPROVAL SECTION
|
||||||
|
=========================================== */
|
||||||
|
swp-status-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-approval-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: var(--spacing-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-checkbox-field {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--spacing-6);
|
||||||
|
padding: var(--spacing-8);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-checkbox-field input[type="checkbox"] {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
margin-top: var(--spacing-1);
|
||||||
|
accent-color: var(--color-teal);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-checkbox-field label {
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
color: var(--color-text);
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: var(--line-height-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
CARD FOOTER (Actions)
|
||||||
|
=========================================== */
|
||||||
|
swp-card-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--spacing-8) var(--spacing-10);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
margin: var(--spacing-10) calc(-1 * var(--spacing-5)) calc(-1 * var(--spacing-5));
|
||||||
|
border-radius: 0 0 var(--border-radius-lg) var(--border-radius-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-actions-right {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
BUTTONS
|
||||||
|
=========================================== */
|
||||||
|
swp-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-3);
|
||||||
|
padding: var(--spacing-5) var(--spacing-8);
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
font-family: var(--font-family);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-btn i {
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-btn.primary {
|
||||||
|
background: var(--color-teal);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-btn.primary:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-btn.primary:disabled {
|
||||||
|
background: var(--color-border);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-btn.secondary {
|
||||||
|
background: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-btn.secondary:hover {
|
||||||
|
background: var(--color-background-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-btn.ghost {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-btn.ghost:hover {
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
SYSTEM NOTE
|
||||||
|
=========================================== */
|
||||||
|
swp-system-note {
|
||||||
|
display: block;
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--spacing-6);
|
||||||
|
margin-top: var(--spacing-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
RESPONSIVE
|
||||||
|
=========================================== */
|
||||||
|
@media (max-width: 1000px) {
|
||||||
|
swp-kasse-stats {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table columns defined on parent - subgrid inherits */
|
||||||
|
swp-kasse-table {
|
||||||
|
grid-template-columns: 50px 80px 1fr 100px 110px 120px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide some columns on smaller screens */
|
||||||
|
swp-kasse-th:nth-child(3),
|
||||||
|
swp-kasse-td:nth-child(3),
|
||||||
|
swp-kasse-th:nth-child(6),
|
||||||
|
swp-kasse-td:nth-child(6) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
swp-filter-bar {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-filter-group {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-filter-spacer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
94
PlanTempus.Application/wwwroot/css/tabs.css
Normal file
94
PlanTempus.Application/wwwroot/css/tabs.css
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
/**
|
||||||
|
* Tabs - Tab Bar Navigation
|
||||||
|
*
|
||||||
|
* Horizontal tab bar with underline active state
|
||||||
|
* Based on POC: poc-indstillinger.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
TAB BAR
|
||||||
|
=========================================== */
|
||||||
|
swp-tab-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
padding: 0 var(--spacing-12);
|
||||||
|
position: sticky;
|
||||||
|
top: var(--topbar-height);
|
||||||
|
z-index: var(--z-sticky);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Account for demo banner if present */
|
||||||
|
body.has-demo-banner swp-tab-bar {
|
||||||
|
top: calc(var(--topbar-height) + 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
TAB ITEM
|
||||||
|
=========================================== */
|
||||||
|
swp-tab {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-4);
|
||||||
|
padding: var(--spacing-7) var(--spacing-12);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-tab i {
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-tab:hover {
|
||||||
|
color: var(--color-text);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-tab.active {
|
||||||
|
color: var(--color-teal);
|
||||||
|
border-bottom-color: var(--color-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-tab.active:hover {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
TAB CONTENT
|
||||||
|
=========================================== */
|
||||||
|
swp-tab-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-tab-content.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
TAB WITH BADGE
|
||||||
|
=========================================== */
|
||||||
|
swp-tab swp-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 var(--spacing-2);
|
||||||
|
margin-left: var(--spacing-2);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
background: var(--color-background-alt);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-tab.active swp-badge {
|
||||||
|
background: color-mix(in srgb, var(--color-teal) 15%, transparent);
|
||||||
|
color: var(--color-teal);
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -9,6 +9,7 @@ import { DrawerController } from './modules/drawers';
|
||||||
import { ThemeController } from './modules/theme';
|
import { ThemeController } from './modules/theme';
|
||||||
import { SearchController } from './modules/search';
|
import { SearchController } from './modules/search';
|
||||||
import { LockScreenController } from './modules/lockscreen';
|
import { LockScreenController } from './modules/lockscreen';
|
||||||
|
import { KasseController } from './modules/kasse';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main application class
|
* Main application class
|
||||||
|
|
@ -19,6 +20,7 @@ export class App {
|
||||||
readonly theme: ThemeController;
|
readonly theme: ThemeController;
|
||||||
readonly search: SearchController;
|
readonly search: SearchController;
|
||||||
readonly lockScreen: LockScreenController;
|
readonly lockScreen: LockScreenController;
|
||||||
|
readonly kasse: KasseController;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Initialize controllers
|
// Initialize controllers
|
||||||
|
|
@ -27,6 +29,7 @@ export class App {
|
||||||
this.theme = new ThemeController();
|
this.theme = new ThemeController();
|
||||||
this.search = new SearchController();
|
this.search = new SearchController();
|
||||||
this.lockScreen = new LockScreenController(this.drawers);
|
this.lockScreen = new LockScreenController(this.drawers);
|
||||||
|
this.kasse = new KasseController();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
370
PlanTempus.Application/wwwroot/ts/modules/kasse.ts
Normal file
370
PlanTempus.Application/wwwroot/ts/modules/kasse.ts
Normal file
|
|
@ -0,0 +1,370 @@
|
||||||
|
/**
|
||||||
|
* Kasse Controller
|
||||||
|
*
|
||||||
|
* Handles tab switching, cash calculations, and form interactions
|
||||||
|
* for the Kasse (Cash Register) page.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class KasseController {
|
||||||
|
// Base values (from system - would come from server in real app)
|
||||||
|
private readonly startBalance = 2000;
|
||||||
|
private readonly cashSales = 3540;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.setupTabs();
|
||||||
|
this.setupCashCalculation();
|
||||||
|
this.setupCheckboxSelection();
|
||||||
|
this.setupApprovalCheckbox();
|
||||||
|
this.setupDateFilters();
|
||||||
|
this.setupRowToggle();
|
||||||
|
this.setupDraftRowClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup tab switching functionality
|
||||||
|
*/
|
||||||
|
private setupTabs(): void {
|
||||||
|
const tabs = document.querySelectorAll<HTMLElement>('swp-tab[data-tab]');
|
||||||
|
|
||||||
|
tabs.forEach(tab => {
|
||||||
|
tab.addEventListener('click', () => {
|
||||||
|
const targetTab = tab.dataset.tab;
|
||||||
|
if (targetTab) {
|
||||||
|
this.switchToTab(targetTab);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch to a specific tab by name
|
||||||
|
*/
|
||||||
|
private switchToTab(targetTab: string): void {
|
||||||
|
const tabs = document.querySelectorAll<HTMLElement>('swp-tab[data-tab]');
|
||||||
|
const contents = document.querySelectorAll<HTMLElement>('swp-tab-content[data-tab]');
|
||||||
|
const statsBars = document.querySelectorAll<HTMLElement>('swp-kasse-stats[data-for-tab]');
|
||||||
|
|
||||||
|
// Update tab states
|
||||||
|
tabs.forEach(t => {
|
||||||
|
if (t.dataset.tab === targetTab) {
|
||||||
|
t.classList.add('active');
|
||||||
|
} else {
|
||||||
|
t.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update content visibility
|
||||||
|
contents.forEach(content => {
|
||||||
|
if (content.dataset.tab === targetTab) {
|
||||||
|
content.classList.add('active');
|
||||||
|
} else {
|
||||||
|
content.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update stats bar visibility
|
||||||
|
statsBars.forEach(stats => {
|
||||||
|
if (stats.dataset.forTab === targetTab) {
|
||||||
|
stats.classList.add('active');
|
||||||
|
} else {
|
||||||
|
stats.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup cash calculation with real-time updates
|
||||||
|
*/
|
||||||
|
private setupCashCalculation(): void {
|
||||||
|
const payoutsInput = document.getElementById('payouts') as HTMLInputElement;
|
||||||
|
const toBankInput = document.getElementById('toBank') as HTMLInputElement;
|
||||||
|
const actualCashInput = document.getElementById('actualCash') as HTMLInputElement;
|
||||||
|
|
||||||
|
if (!payoutsInput || !toBankInput || !actualCashInput) return;
|
||||||
|
|
||||||
|
const calculate = () => this.calculateCash(payoutsInput, toBankInput, actualCashInput);
|
||||||
|
|
||||||
|
payoutsInput.addEventListener('input', calculate);
|
||||||
|
toBankInput.addEventListener('input', calculate);
|
||||||
|
actualCashInput.addEventListener('input', calculate);
|
||||||
|
|
||||||
|
// Initial calculation
|
||||||
|
calculate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate expected cash and difference
|
||||||
|
*/
|
||||||
|
private calculateCash(
|
||||||
|
payoutsInput: HTMLInputElement,
|
||||||
|
toBankInput: HTMLInputElement,
|
||||||
|
actualCashInput: HTMLInputElement
|
||||||
|
): void {
|
||||||
|
const payouts = this.parseNumber(payoutsInput.value);
|
||||||
|
const toBank = this.parseNumber(toBankInput.value);
|
||||||
|
const actual = this.parseNumber(actualCashInput.value);
|
||||||
|
|
||||||
|
// Expected = start + sales - payouts - to bank
|
||||||
|
const expectedCash = this.startBalance + this.cashSales - payouts - toBank;
|
||||||
|
|
||||||
|
const expectedElement = document.getElementById('expectedCash');
|
||||||
|
if (expectedElement) {
|
||||||
|
expectedElement.textContent = this.formatNumber(expectedCash);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate and display difference
|
||||||
|
this.updateDifference(actual, expectedCash, actualCashInput.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update difference box with color coding
|
||||||
|
*/
|
||||||
|
private updateDifference(actual: number, expected: number, rawValue: string): void {
|
||||||
|
const box = document.getElementById('differenceBox');
|
||||||
|
const value = document.getElementById('differenceValue');
|
||||||
|
if (!box || !value) return;
|
||||||
|
|
||||||
|
const diff = actual - expected;
|
||||||
|
|
||||||
|
// Remove all state classes
|
||||||
|
box.classList.remove('positive', 'negative', 'neutral');
|
||||||
|
|
||||||
|
if (actual === 0 && rawValue === '') {
|
||||||
|
// No input yet
|
||||||
|
value.textContent = '– kr';
|
||||||
|
box.classList.add('neutral');
|
||||||
|
} else if (diff > 0) {
|
||||||
|
// More cash than expected
|
||||||
|
value.textContent = '+' + this.formatNumber(diff) + ' kr';
|
||||||
|
box.classList.add('positive');
|
||||||
|
} else if (diff < 0) {
|
||||||
|
// Less cash than expected
|
||||||
|
value.textContent = this.formatNumber(diff) + ' kr';
|
||||||
|
box.classList.add('negative');
|
||||||
|
} else {
|
||||||
|
// Exact match
|
||||||
|
value.textContent = '0,00 kr';
|
||||||
|
box.classList.add('neutral');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup checkbox selection for table rows
|
||||||
|
*/
|
||||||
|
private setupCheckboxSelection(): void {
|
||||||
|
const selectAll = document.getElementById('selectAll') as HTMLInputElement;
|
||||||
|
const rowCheckboxes = document.querySelectorAll<HTMLInputElement>('.row-select');
|
||||||
|
const exportBtn = document.getElementById('exportBtn') as HTMLButtonElement;
|
||||||
|
const selectionCount = document.getElementById('selectionCount');
|
||||||
|
|
||||||
|
if (!selectAll || !exportBtn || !selectionCount) return;
|
||||||
|
|
||||||
|
const updateSelection = () => {
|
||||||
|
const checked = document.querySelectorAll<HTMLInputElement>('.row-select:checked');
|
||||||
|
const count = checked.length;
|
||||||
|
|
||||||
|
selectionCount.textContent = count === 0 ? '0 valgt' : `${count} valgt`;
|
||||||
|
exportBtn.disabled = count === 0;
|
||||||
|
|
||||||
|
// Update select all state
|
||||||
|
selectAll.checked = count === rowCheckboxes.length && count > 0;
|
||||||
|
selectAll.indeterminate = count > 0 && count < rowCheckboxes.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
selectAll.addEventListener('change', () => {
|
||||||
|
rowCheckboxes.forEach(cb => cb.checked = selectAll.checked);
|
||||||
|
updateSelection();
|
||||||
|
});
|
||||||
|
|
||||||
|
rowCheckboxes.forEach(cb => {
|
||||||
|
cb.addEventListener('change', updateSelection);
|
||||||
|
// Stop click from bubbling to row
|
||||||
|
cb.addEventListener('click', e => e.stopPropagation());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup approval checkbox to enable/disable approve button
|
||||||
|
*/
|
||||||
|
private setupApprovalCheckbox(): void {
|
||||||
|
const checkbox = document.getElementById('confirmCheckbox') as HTMLInputElement;
|
||||||
|
const approveBtn = document.getElementById('approveBtn') as HTMLButtonElement;
|
||||||
|
|
||||||
|
if (!checkbox || !approveBtn) return;
|
||||||
|
|
||||||
|
checkbox.addEventListener('change', () => {
|
||||||
|
approveBtn.disabled = !checkbox.checked;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup date filter defaults (last 30 days)
|
||||||
|
*/
|
||||||
|
private setupDateFilters(): void {
|
||||||
|
const dateFrom = document.getElementById('dateFrom') as HTMLInputElement;
|
||||||
|
const dateTo = document.getElementById('dateTo') as HTMLInputElement;
|
||||||
|
|
||||||
|
if (!dateFrom || !dateTo) return;
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
const thirtyDaysAgo = new Date(today);
|
||||||
|
thirtyDaysAgo.setDate(today.getDate() - 30);
|
||||||
|
|
||||||
|
dateTo.value = this.formatDateISO(today);
|
||||||
|
dateFrom.value = this.formatDateISO(thirtyDaysAgo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format number as Danish currency
|
||||||
|
*/
|
||||||
|
private formatNumber(num: number): string {
|
||||||
|
return num.toLocaleString('da-DK', {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse Danish number format
|
||||||
|
*/
|
||||||
|
private parseNumber(str: string): number {
|
||||||
|
if (!str) return 0;
|
||||||
|
return parseFloat(str.replace(/\./g, '').replace(',', '.')) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format date as ISO string (YYYY-MM-DD)
|
||||||
|
*/
|
||||||
|
private formatDateISO(date: Date): string {
|
||||||
|
return date.toISOString().split('T')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup row toggle for expandable details
|
||||||
|
*/
|
||||||
|
private setupRowToggle(): void {
|
||||||
|
const rows = document.querySelectorAll<HTMLElement>('swp-kasse-table-row[data-id]:not(.draft-row)');
|
||||||
|
|
||||||
|
rows.forEach(row => {
|
||||||
|
const rowId = row.getAttribute('data-id');
|
||||||
|
if (!rowId) return;
|
||||||
|
|
||||||
|
const detail = document.querySelector<HTMLElement>(`swp-kasse-row-detail[data-for="${rowId}"]`);
|
||||||
|
if (!detail) return;
|
||||||
|
|
||||||
|
row.addEventListener('click', (e) => {
|
||||||
|
// Don't toggle if clicking on checkbox
|
||||||
|
if ((e.target as HTMLElement).closest('input[type="checkbox"]')) return;
|
||||||
|
|
||||||
|
const icon = row.querySelector('swp-row-toggle i');
|
||||||
|
const isExpanded = row.classList.contains('expanded');
|
||||||
|
|
||||||
|
// Close other expanded rows
|
||||||
|
document.querySelectorAll('swp-kasse-table-row.expanded').forEach(r => {
|
||||||
|
if (r !== row) {
|
||||||
|
const otherId = r.getAttribute('data-id');
|
||||||
|
if (otherId) {
|
||||||
|
const otherDetail = document.querySelector<HTMLElement>(`swp-kasse-row-detail[data-for="${otherId}"]`);
|
||||||
|
const otherIcon = r.querySelector('swp-row-toggle i');
|
||||||
|
if (otherDetail && otherIcon) {
|
||||||
|
this.collapseRow(r, otherDetail, otherIcon as HTMLElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle current row
|
||||||
|
if (isExpanded) {
|
||||||
|
this.collapseRow(row, detail, icon);
|
||||||
|
} else {
|
||||||
|
this.expandRow(row, detail, icon);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand a row with animation
|
||||||
|
*/
|
||||||
|
private expandRow(row: Element, detail: HTMLElement, icon: Element | null): void {
|
||||||
|
row.classList.add('expanded');
|
||||||
|
detail.classList.add('expanded');
|
||||||
|
|
||||||
|
// Animate icon rotation
|
||||||
|
icon?.animate([
|
||||||
|
{ transform: 'rotate(0deg)' },
|
||||||
|
{ transform: 'rotate(90deg)' }
|
||||||
|
], {
|
||||||
|
duration: 200,
|
||||||
|
easing: 'ease-out',
|
||||||
|
fill: 'forwards'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Animate detail expansion
|
||||||
|
const content = detail.querySelector('swp-row-detail-content') as HTMLElement;
|
||||||
|
if (content) {
|
||||||
|
const height = content.offsetHeight;
|
||||||
|
detail.animate([
|
||||||
|
{ height: '0px', opacity: 0 },
|
||||||
|
{ height: `${height}px`, opacity: 1 }
|
||||||
|
], {
|
||||||
|
duration: 250,
|
||||||
|
easing: 'ease-out',
|
||||||
|
fill: 'forwards'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collapse a row with animation
|
||||||
|
*/
|
||||||
|
private collapseRow(row: Element, detail: HTMLElement, icon: Element | null): void {
|
||||||
|
// Animate icon rotation
|
||||||
|
icon?.animate([
|
||||||
|
{ transform: 'rotate(90deg)' },
|
||||||
|
{ transform: 'rotate(0deg)' }
|
||||||
|
], {
|
||||||
|
duration: 200,
|
||||||
|
easing: 'ease-out',
|
||||||
|
fill: 'forwards'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Animate detail collapse
|
||||||
|
const content = detail.querySelector('swp-row-detail-content') as HTMLElement;
|
||||||
|
if (content) {
|
||||||
|
const height = content.offsetHeight;
|
||||||
|
const animation = detail.animate([
|
||||||
|
{ height: `${height}px`, opacity: 1 },
|
||||||
|
{ height: '0px', opacity: 0 }
|
||||||
|
], {
|
||||||
|
duration: 200,
|
||||||
|
easing: 'ease-out',
|
||||||
|
fill: 'forwards'
|
||||||
|
});
|
||||||
|
|
||||||
|
animation.onfinish = () => {
|
||||||
|
row.classList.remove('expanded');
|
||||||
|
detail.classList.remove('expanded');
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
row.classList.remove('expanded');
|
||||||
|
detail.classList.remove('expanded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup draft row click to navigate to Kasseafstemning tab
|
||||||
|
*/
|
||||||
|
private setupDraftRowClick(): void {
|
||||||
|
const draftRow = document.querySelector<HTMLElement>('swp-kasse-table-row.draft-row');
|
||||||
|
if (!draftRow) return;
|
||||||
|
|
||||||
|
draftRow.style.cursor = 'pointer';
|
||||||
|
draftRow.addEventListener('click', (e) => {
|
||||||
|
// Don't navigate if clicking on checkbox
|
||||||
|
if ((e.target as HTMLElement).closest('input[type="checkbox"]')) return;
|
||||||
|
|
||||||
|
this.switchToTab('afstemning');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue