PlanTempusApp/.workbench/POC/poc-kasseafstemninger.html
2026-02-03 19:12:45 +01:00

798 lines
23 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="da">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kasseafstemninger - POC</title>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
/* ==========================================
FONT FACE (Poppins)
========================================== */
@font-face {
font-family: 'Poppins';
src: url('fonts/Poppins-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Poppins';
src: url('fonts/Poppins-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Poppins';
src: url('fonts/Poppins-SemiBold.woff') format('woff');
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Poppins';
src: url('fonts/Poppins-Bold.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}
/* ==========================================
CSS VARIABLES (Design System)
========================================== */
:root {
--color-surface: #fff;
--color-background: #f5f5f5;
--color-background-hover: #f0f0f0;
--color-background-alt: #fafafa;
--color-border: #e0e0e0;
--color-text: #333;
--color-text-secondary: #666;
--color-teal: #00897b;
--color-blue: #1976d2;
--color-red: #e53935;
--color-amber: #f59e0b;
--color-purple: #8b5cf6;
--color-green: #43a047;
--font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
}
/* ==========================================
RESET & BASE
========================================== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family);
font-size: 14px;
color: var(--color-text);
background: var(--color-background);
line-height: 1.5;
}
/* ==========================================
TOPBAR
========================================== */
swp-topbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 24px;
background: var(--color-surface);
border-bottom: 1px solid var(--color-border);
position: sticky;
top: 0;
z-index: 100;
}
swp-topbar-left {
display: flex;
align-items: center;
gap: 16px;
}
swp-back-link {
display: flex;
align-items: center;
gap: 6px;
color: var(--color-text-secondary);
text-decoration: none;
font-size: 13px;
cursor: pointer;
transition: color 150ms ease;
}
swp-back-link:hover {
color: var(--color-teal);
}
swp-back-link svg {
width: 16px;
height: 16px;
fill: currentColor;
}
swp-page-title {
font-size: 16px;
font-weight: 600;
}
swp-topbar-right {
display: flex;
align-items: center;
gap: 12px;
}
/* ==========================================
LAYOUT
========================================== */
swp-page-container {
display: block;
max-width: 1400px;
margin: 0 auto;
padding: 24px;
}
/* ==========================================
FILTER BAR
========================================== */
swp-filter-bar {
display: flex;
align-items: center;
gap: 16px;
padding: 16px 20px;
background: var(--color-surface);
border-radius: 8px;
margin-bottom: 20px;
flex-wrap: wrap;
}
swp-filter-group {
display: flex;
align-items: center;
gap: 8px;
}
swp-filter-label {
font-size: 12px;
font-weight: 500;
color: var(--color-text-secondary);
}
swp-filter-bar input,
swp-filter-bar select {
padding: 8px 12px;
font-size: 13px;
font-family: var(--font-family);
border: 1px solid var(--color-border);
border-radius: 6px;
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;
}
/* ==========================================
STATS BAR
========================================== */
swp-stats-bar {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 20px;
}
swp-stat-card {
background: var(--color-surface);
border-radius: 8px;
padding: 16px 20px;
border: 1px solid var(--color-border);
}
swp-stat-value {
display: block;
font-size: 24px;
font-weight: 600;
font-family: var(--font-mono);
color: var(--color-text);
}
swp-stat-label {
display: block;
font-size: 12px;
color: var(--color-text-secondary);
margin-top: 4px;
}
swp-stat-card.highlight swp-stat-value {
color: var(--color-teal);
}
swp-stat-card.warning swp-stat-value {
color: var(--color-amber);
}
swp-stat-card.negative swp-stat-value {
color: var(--color-red);
}
/* ==========================================
ACTION BAR
========================================== */
swp-action-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: var(--color-surface);
border-radius: 8px 8px 0 0;
border: 1px solid var(--color-border);
border-bottom: none;
}
swp-selection-info {
font-size: 13px;
color: var(--color-text-secondary);
}
swp-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* ==========================================
TABLE
========================================== */
swp-table {
display: block;
background: var(--color-surface);
border-radius: 0 0 8px 8px;
overflow: hidden;
border: 1px solid var(--color-border);
}
swp-th.checkbox,
swp-td.checkbox {
display: flex;
align-items: center;
justify-content: center;
}
swp-table input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: var(--color-teal);
cursor: pointer;
}
swp-table-header,
swp-table-row {
display: grid;
grid-template-columns: 40px 70px 60px minmax(140px, 1fr) 90px 100px 100px 90px 100px 40px;
align-items: center;
}
swp-table-header {
background: var(--color-background-alt);
border-bottom: 1px solid var(--color-border);
padding: 12px 20px;
}
swp-th {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--color-text-secondary);
}
swp-th.right {
text-align: right;
}
swp-table-body {
display: block;
}
swp-table-row {
padding: 14px 20px;
border-bottom: 1px solid var(--color-border);
transition: background 150ms ease;
cursor: pointer;
}
swp-table-row:last-child {
border-bottom: none;
}
swp-table-row:hover {
background: var(--color-background-hover);
}
swp-td {
font-size: 14px;
color: var(--color-text);
}
swp-td.right {
text-align: right;
}
swp-td.mono {
font-family: var(--font-mono);
}
swp-td.muted {
color: var(--color-text-secondary);
font-size: 12px;
}
swp-td.negative {
color: var(--color-red);
}
swp-td.positive {
color: var(--color-green);
}
swp-period-cell {
display: block;
}
swp-period-cell .dates {
font-size: 12px;
color: var(--color-text-secondary);
}
/* ==========================================
STATUS BADGE
========================================== */
swp-status-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
font-size: 11px;
font-weight: 500;
border-radius: 20px;
}
swp-status-badge.approved {
background: color-mix(in srgb, var(--color-green) 15%, white);
color: var(--color-green);
}
swp-status-badge.draft {
background: color-mix(in srgb, var(--color-amber) 15%, white);
color: #b45309;
}
swp-status-badge::before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
background: currentColor;
}
swp-td:nth-child(9) {
padding-left: 12px;
}
swp-td.id {
font-size: 12px;
color: var(--color-text-secondary);
font-family: var(--font-mono);
}
/* ==========================================
TABLE FOOTER
========================================== */
swp-table-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 20px;
background: var(--color-background-alt);
border-top: 1px solid var(--color-border);
font-size: 13px;
color: var(--color-text-secondary);
}
/* ==========================================
BUTTONS
========================================== */
swp-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 18px;
font-size: 13px;
font-weight: 500;
font-family: var(--font-family);
border-radius: 6px;
cursor: pointer;
transition: all 150ms ease;
border: none;
}
swp-btn svg {
width: 16px;
height: 16px;
fill: currentColor;
}
swp-btn.primary {
background: var(--color-teal);
color: white;
}
swp-btn.primary:hover {
background: #00796b;
}
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-row-arrow {
display: flex;
align-items: center;
justify-content: flex-end;
color: var(--color-text-secondary);
}
swp-row-arrow svg {
width: 16px;
height: 16px;
fill: currentColor;
}
/* ==========================================
RESPONSIVE
========================================== */
@media (max-width: 1000px) {
swp-stats-bar {
grid-template-columns: repeat(2, 1fr);
}
swp-table-header,
swp-table-row {
grid-template-columns: 80px 1fr 100px 100px 80px 50px;
}
swp-th:nth-child(3),
swp-td:nth-child(3),
swp-th:nth-child(4),
swp-td:nth-child(4) {
display: none;
}
}
</style>
</head>
<body>
<!-- Topbar -->
<swp-topbar>
<swp-topbar-left>
<swp-back-link>
<svg viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
Tilbage
</swp-back-link>
<swp-page-title>Kasseafstemninger</swp-page-title>
</swp-topbar-left>
<swp-topbar-right>
</swp-topbar-right>
</swp-topbar>
<swp-page-container>
<!-- Filter Bar -->
<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>
<!-- Stats Bar -->
<swp-stats-bar>
<swp-stat-card>
<swp-stat-value>12</swp-stat-value>
<swp-stat-label>Afstemninger i periode</swp-stat-label>
</swp-stat-card>
<swp-stat-card class="highlight">
<swp-stat-value>186.450 kr</swp-stat-value>
<swp-stat-label>Total omsætning</swp-stat-label>
</swp-stat-card>
<swp-stat-card>
<swp-stat-value>42.340 kr</swp-stat-value>
<swp-stat-label>Kontantsalg</swp-stat-label>
</swp-stat-card>
<swp-stat-card class="warning">
<swp-stat-value>-75 kr</swp-stat-value>
<swp-stat-label>Samlet difference</swp-stat-label>
</swp-stat-card>
</swp-stats-bar>
<!-- Action Bar -->
<swp-action-bar>
<swp-selection-info>
<span id="selectionCount">0 valgt</span>
</swp-selection-info>
<swp-btn class="primary" id="exportBtn" disabled>
<svg viewBox="0 0 24 24"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg>
Eksporter SAF-T
</swp-btn>
</swp-action-bar>
<!-- Table -->
<swp-table>
<swp-table-header>
<swp-th class="checkbox"><input type="checkbox" id="selectAll" /></swp-th>
<swp-th>Dato</swp-th>
<swp-th>ID</swp-th>
<swp-th>Periode</swp-th>
<swp-th>Kassepunkt</swp-th>
<swp-th>Afsluttet af</swp-th>
<swp-th class="right">Omsætning</swp-th>
<swp-th class="right">Difference</swp-th>
<swp-th>Status</swp-th>
<swp-th></swp-th>
</swp-table-header>
<swp-table-body>
<swp-table-row data-id="draft" class="draft-row">
<swp-td class="checkbox"></swp-td>
<swp-td class="muted">I dag</swp-td>
<swp-td class="id muted"></swp-td>
<swp-td>
<swp-period-cell>
<span class="dates">29. dec 17:45 → ...</span>
</swp-period-cell>
</swp-td>
<swp-td>Kasse 1</swp-td>
<swp-td class="muted"></swp-td>
<swp-td class="right mono muted">4.250 kr</swp-td>
<swp-td class="right mono muted"></swp-td>
<swp-td><swp-status-badge class="draft">Kladde</swp-status-badge></swp-td>
<swp-td><swp-row-arrow><svg viewBox="0 0 24 24"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg></swp-row-arrow></swp-td>
</swp-table-row>
<swp-table-row data-id="043">
<swp-td class="checkbox"><input type="checkbox" class="row-select" /></swp-td>
<swp-td>29. dec</swp-td>
<swp-td class="id">Z-043</swp-td>
<swp-td>
<swp-period-cell>
<span class="dates">28. dec 18:00 → 29. dec 17:45</span>
</swp-period-cell>
</swp-td>
<swp-td>Kasse 1</swp-td>
<swp-td>Anna Jensen</swp-td>
<swp-td class="right mono">18.865 kr</swp-td>
<swp-td class="right mono">0 kr</swp-td>
<swp-td><swp-status-badge class="approved">Godkendt</swp-status-badge></swp-td>
<swp-td><swp-row-arrow><svg viewBox="0 0 24 24"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg></swp-row-arrow></swp-td>
</swp-table-row>
<swp-table-row data-id="042">
<swp-td class="checkbox"><input type="checkbox" class="row-select" /></swp-td>
<swp-td>28. dec</swp-td>
<swp-td class="id">Z-042</swp-td>
<swp-td>
<swp-period-cell>
<span class="dates">27. dec 18:30 → 28. dec 18:00</span>
</swp-period-cell>
</swp-td>
<swp-td>Kasse 1</swp-td>
<swp-td>Karina Knudsen</swp-td>
<swp-td class="right mono">12.450 kr</swp-td>
<swp-td class="right mono negative">-25 kr</swp-td>
<swp-td><swp-status-badge class="approved">Godkendt</swp-status-badge></swp-td>
<swp-td><swp-row-arrow><svg viewBox="0 0 24 24"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg></swp-row-arrow></swp-td>
</swp-table-row>
<swp-table-row data-id="041">
<swp-td class="checkbox"><input type="checkbox" class="row-select" /></swp-td>
<swp-td>27. dec</swp-td>
<swp-td class="id">Z-041</swp-td>
<swp-td>
<swp-period-cell>
<span class="dates">26. dec 18:00 → 27. dec 18:30</span>
</swp-period-cell>
</swp-td>
<swp-td>Kasse 1</swp-td>
<swp-td>Martin Nielsen</swp-td>
<swp-td class="right mono">21.340 kr</swp-td>
<swp-td class="right mono">0 kr</swp-td>
<swp-td><swp-status-badge class="approved">Godkendt</swp-status-badge></swp-td>
<swp-td><swp-row-arrow><svg viewBox="0 0 24 24"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg></swp-row-arrow></swp-td>
</swp-table-row>
<swp-table-row data-id="040">
<swp-td class="checkbox"><input type="checkbox" class="row-select" /></swp-td>
<swp-td>23. dec</swp-td>
<swp-td class="id">Z-040</swp-td>
<swp-td>
<swp-period-cell>
<span class="dates">22. dec 18:00 → 23. dec 17:30</span>
</swp-period-cell>
</swp-td>
<swp-td>Kasse 1</swp-td>
<swp-td>Anna Jensen</swp-td>
<swp-td class="right mono">28.750 kr</swp-td>
<swp-td class="right mono negative">-50 kr</swp-td>
<swp-td><swp-status-badge class="approved">Godkendt</swp-status-badge></swp-td>
<swp-td><swp-row-arrow><svg viewBox="0 0 24 24"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg></swp-row-arrow></swp-td>
</swp-table-row>
<swp-table-row data-id="039">
<swp-td class="checkbox"><input type="checkbox" class="row-select" /></swp-td>
<swp-td>22. dec</swp-td>
<swp-td class="id">Z-039</swp-td>
<swp-td>
<swp-period-cell>
<span class="dates">21. dec 18:15 → 22. dec 18:00</span>
</swp-period-cell>
</swp-td>
<swp-td>Kasse 1</swp-td>
<swp-td>Karina Knudsen</swp-td>
<swp-td class="right mono">15.890 kr</swp-td>
<swp-td class="right mono">0 kr</swp-td>
<swp-td><swp-status-badge class="approved">Godkendt</swp-status-badge></swp-td>
<swp-td><swp-row-arrow><svg viewBox="0 0 24 24"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg></swp-row-arrow></swp-td>
</swp-table-row>
</swp-table-body>
<swp-table-footer>
<span>Viser 6 afstemninger</span>
<span>Z-038 → Z-043</span>
</swp-table-footer>
</swp-table>
</swp-page-container>
<script>
// Set default date range (last 30 days)
const today = new Date();
const thirtyDaysAgo = new Date(today);
thirtyDaysAgo.setDate(today.getDate() - 30);
document.getElementById('dateTo').value = today.toISOString().split('T')[0];
document.getElementById('dateFrom').value = thirtyDaysAgo.toISOString().split('T')[0];
// Checkbox selection handling
const selectAll = document.getElementById('selectAll');
const rowCheckboxes = document.querySelectorAll('.row-select');
const exportBtn = document.getElementById('exportBtn');
const selectionCount = document.getElementById('selectionCount');
function updateSelection() {
const checked = document.querySelectorAll('.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', function() {
rowCheckboxes.forEach(cb => cb.checked = this.checked);
updateSelection();
});
rowCheckboxes.forEach(cb => {
cb.addEventListener('change', updateSelection);
// Stop click from bubbling to row
cb.addEventListener('click', e => e.stopPropagation());
});
// SAF-T Export
document.getElementById('exportBtn').addEventListener('click', function() {
const selected = document.querySelectorAll('.row-select:checked');
const ids = Array.from(selected).map(cb => cb.closest('swp-table-row').dataset.id);
// Generate demo SAF-T XML
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<AuditFile xmlns="urn:StandardAuditFile-Taxation-CashRegister:DK">
<Header>
<AuditFileVersion>1.30</AuditFileVersion>
<AuditFileCountry>DK</AuditFileCountry>
<AuditFileDateCreated>${today.toISOString().split('T')[0]}</AuditFileDateCreated>
<SoftwareCompanyName>Calendar Plantempus</SoftwareCompanyName>
<SoftwareID>SWP-POS</SoftwareID>
<SoftwareVersion>1.0</SoftwareVersion>
<Company>
<Name>Demo Salon ApS</Name>
<RegistrationNumber>12345678</RegistrationNumber>
</Company>
<DefaultCurrencyCode>DKK</DefaultCurrencyCode>
<SelectionCriteria>
<SelectionStartDate>${document.getElementById('dateFrom').value}</SelectionStartDate>
<SelectionEndDate>${document.getElementById('dateTo').value}</SelectionEndDate>
</SelectionCriteria>
</Header>
<CashRegisters>
<CashRegister>
<CashRegisterID>KASSE-001</CashRegisterID>
<CashRegisterLocation>Reception</CashRegisterLocation>
<Periods>
<Period>
<PeriodNumber>043</PeriodNumber>
<StartDateTime>2025-12-28T18:00:00</StartDateTime>
<EndDateTime>2025-12-29T17:45:00</EndDateTime>
<CashStatement>
<OpeningBalance>2000.00</OpeningBalance>
<ClosingBalance>5220.00</ClosingBalance>
<CashSales>3540.00</CashSales>
<CardSales>12875.50</CardSales>
<MobilePaySales>2450.00</MobilePaySales>
<TotalSales>18865.50</TotalSales>
<NumberOfTransactions>47</NumberOfTransactions>
<CashDifference>0.00</CashDifference>
</CashStatement>
<TaxSummary>
<TaxCode>S</TaxCode>
<TaxPercentage>25.00</TaxPercentage>
<TaxBase>15092.40</TaxBase>
<TaxAmount>3773.10</TaxAmount>
</TaxSummary>
</Period>
</Periods>
</CashRegister>
</CashRegisters>
</AuditFile>`;
// Download file
const blob = new Blob([xml], { type: 'application/xml' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `SAF-T_CashRegister_${document.getElementById('dateFrom').value}_${document.getElementById('dateTo').value}.xml`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
</script>
</body>
</html>