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

995 lines
26 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>Kasseafstemning - 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-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: 1200px;
margin: 0 auto;
padding: 24px;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
@media (max-width: 900px) {
.grid-2 { grid-template-columns: 1fr; }
}
/* ==========================================
PAGE HEADER
========================================== */
swp-page-header {
display: block;
margin-bottom: 24px;
}
swp-page-header h1 {
font-size: 24px;
font-weight: 600;
color: var(--color-text);
margin-bottom: 6px;
}
swp-page-subtitle {
display: block;
font-size: 14px;
color: var(--color-text-secondary);
}
/* ==========================================
CARDS
========================================== */
swp-card {
display: block;
background: var(--color-surface);
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
margin-bottom: 20px;
overflow: hidden;
}
swp-card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid var(--color-border);
}
swp-card-title {
font-size: 14px;
font-weight: 600;
color: var(--color-text);
}
swp-card-hint {
font-size: 12px;
color: var(--color-text-secondary);
}
swp-card-body {
display: block;
padding: 20px;
}
/* ==========================================
FORM ELEMENTS
========================================== */
swp-form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
swp-form-field {
display: block;
}
swp-form-field.full-width {
grid-column: 1 / -1;
}
swp-form-label {
display: block;
font-size: 12px;
font-weight: 500;
color: var(--color-text-secondary);
margin-bottom: 6px;
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: 10px 12px;
font-size: 14px;
font-family: var(--font-family);
border: 1px solid var(--color-border);
border-radius: 6px;
background: var(--color-surface);
transition: border-color 150ms ease;
}
swp-form-input input:focus,
swp-form-input select:focus {
outline: none;
border-color: var(--color-teal);
}
swp-auto-id {
display: block;
font-size: 12px;
color: var(--color-text-secondary);
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--color-border);
}
/* ==========================================
PERIOD DISPLAY
========================================== */
swp-period-display {
display: block;
padding: 16px;
background: var(--color-background-alt);
border-radius: 8px;
}
swp-period-label {
display: block;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--color-text-secondary);
margin-bottom: 8px;
}
swp-period-value {
display: flex;
align-items: center;
gap: 10px;
font-size: 14px;
font-weight: 500;
color: var(--color-text);
}
swp-period-value .arrow {
color: var(--color-teal);
font-weight: 400;
}
swp-period-hint {
display: block;
font-size: 12px;
color: var(--color-text-secondary);
margin-top: 8px;
}
/* ==========================================
DATA TABLE
========================================== */
swp-data-table {
display: block;
width: 100%;
}
swp-data-header {
display: grid;
grid-template-columns: 1fr 100px 140px;
gap: 12px;
padding: 10px 0;
border-bottom: 2px solid var(--color-border);
}
swp-data-header span {
font-size: 11px;
font-weight: 600;
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: 12px;
padding: 14px 0;
border-bottom: 1px solid var(--color-border);
align-items: center;
}
swp-data-row:last-child {
border-bottom: none;
}
swp-data-label {
font-size: 14px;
color: var(--color-text);
}
swp-data-system {
text-align: right;
font-family: var(--font-mono);
font-size: 14px;
color: var(--color-text-secondary);
}
swp-data-input input {
width: 100%;
padding: 8px 10px;
font-size: 14px;
font-family: var(--font-mono);
text-align: right;
border: 1px solid var(--color-border);
border-radius: 6px;
background: var(--color-surface);
}
swp-data-input input:focus {
outline: none;
border-color: var(--color-teal);
}
swp-data-input input::placeholder {
color: #bbb;
font-family: var(--font-family);
font-size: 12px;
}
swp-data-value {
text-align: right;
font-family: var(--font-mono);
font-size: 14px;
color: var(--color-text);
}
swp-data-value.muted {
color: var(--color-text-secondary);
}
swp-table-note {
display: block;
font-size: 12px;
color: var(--color-text-secondary);
margin-top: 16px;
padding: 12px;
background: var(--color-background-alt);
border-radius: 6px;
}
/* ==========================================
CASH CALCULATION
========================================== */
swp-calc-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 0;
border-bottom: 1px solid var(--color-border);
}
swp-calc-row:last-of-type {
border-bottom: none;
}
swp-calc-row.input-row {
padding: 18px 0;
background: var(--color-background-alt);
margin: 16px -20px -20px -20px;
padding: 20px;
border-radius: 0 0 8px 8px;
}
swp-calc-label span {
display: block;
font-size: 14px;
color: var(--color-text);
}
swp-calc-label small {
display: block;
font-size: 12px;
color: var(--color-text-secondary);
margin-top: 2px;
}
swp-calc-value {
font-family: var(--font-mono);
font-size: 14px;
color: var(--color-text);
}
swp-calc-value.muted {
color: var(--color-text-secondary);
}
swp-calc-input input {
width: 140px;
padding: 12px 14px;
font-size: 16px;
font-family: var(--font-mono);
text-align: right;
border: 2px solid var(--color-border);
border-radius: 6px;
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: 20px;
border-radius: 8px;
background: var(--color-background-alt);
margin-top: 16px;
}
swp-difference-box.positive {
background: color-mix(in srgb, var(--color-green) 10%, white);
}
swp-difference-box.negative {
background: color-mix(in srgb, var(--color-red) 10%, white);
}
swp-difference-box.neutral {
background: color-mix(in srgb, var(--color-teal) 10%, white);
}
swp-difference-label {
font-size: 14px;
font-weight: 500;
color: var(--color-text);
}
swp-difference-label small {
display: block;
font-size: 12px;
font-weight: 400;
color: var(--color-text-secondary);
margin-top: 4px;
}
swp-difference-value {
font-size: 24px;
font-weight: 600;
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);
}
/* ==========================================
NOTE FIELD
========================================== */
swp-note-field textarea {
width: 100%;
min-height: 80px;
padding: 12px;
font-size: 14px;
font-family: var(--font-family);
border: 1px solid var(--color-border);
border-radius: 6px;
resize: vertical;
}
swp-note-field textarea:focus {
outline: none;
border-color: var(--color-teal);
}
swp-note-hint {
display: block;
font-size: 12px;
color: var(--color-text-secondary);
margin-top: 8px;
}
/* ==========================================
STATUS & APPROVAL
========================================== */
swp-status-row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
swp-status-label {
font-size: 13px;
color: var(--color-text-secondary);
}
swp-status-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
font-size: 12px;
font-weight: 500;
border-radius: 20px;
}
swp-status-badge.draft {
background: color-mix(in srgb, var(--color-amber) 15%, white);
color: #b45309;
}
swp-status-badge.approved {
background: color-mix(in srgb, var(--color-green) 15%, white);
color: var(--color-green);
}
swp-status-badge::before {
content: '';
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
}
swp-approval-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 20px;
}
swp-checkbox-field {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 16px;
background: var(--color-background-alt);
border-radius: 8px;
margin-bottom: 20px;
grid-column: 1 / -1;
}
swp-checkbox-field input[type="checkbox"] {
width: 18px;
height: 18px;
margin-top: 2px;
accent-color: var(--color-teal);
cursor: pointer;
}
swp-checkbox-field label {
font-size: 13px;
color: var(--color-text);
cursor: pointer;
line-height: 1.5;
}
/* ==========================================
ACTIONS
========================================== */
swp-card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
background: var(--color-background-alt);
border-top: 1px solid var(--color-border);
}
swp-actions-right {
display: flex;
gap: 10px;
}
swp-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 10px 18px;
font-size: 13px;
font-weight: 500;
font-family: var(--font-family);
border-radius: 6px;
cursor: pointer;
transition: all 150ms ease;
}
swp-btn.ghost {
background: transparent;
border: none;
color: var(--color-text-secondary);
}
swp-btn.ghost:hover {
color: var(--color-text);
}
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.primary {
background: var(--color-teal);
border: 1px solid var(--color-teal);
color: white;
}
swp-btn.primary:hover {
background: #00796b;
}
swp-btn.primary:disabled {
background: #ccc;
border-color: #ccc;
cursor: not-allowed;
}
swp-system-note {
display: block;
font-size: 11px;
color: var(--color-text-secondary);
text-align: center;
padding: 12px;
}
</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>Kasseafstemning</swp-page-title>
</swp-topbar-left>
<swp-topbar-right>
<swp-status-badge class="draft" id="statusBadge">Kladde</swp-status-badge>
</swp-topbar-right>
</swp-topbar>
<swp-page-container>
<!-- Page Header -->
<swp-page-header>
<h1>Kasseafstemning</h1>
<swp-page-subtitle>Enkel dagsafslutning med fokus på kontanter. Kort afstemmes separat mod bank/indløser.</swp-page-subtitle>
</swp-page-header>
<div class="grid-2">
<!-- VENSTRE KOLONNE -->
<div>
<!-- Dagens tal -->
<swp-card>
<swp-card-header>
<swp-card-title>Dagens tal</swp-card-title>
<swp-card-hint>Systemtal vs. kontrol</swp-card-hint>
</swp-card-header>
<swp-card-body>
<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-body>
</swp-card>
<!-- Kontanter i kassen -->
<swp-card>
<swp-card-header>
<swp-card-title>Kontanter i kassen</swp-card-title>
</swp-card-header>
<swp-card-body>
<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-body>
</swp-card>
<!-- Difference -->
<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>
</div>
<!-- HØJRE KOLONNE -->
<div>
<!-- Dagsoplysninger -->
<swp-card>
<swp-card-header>
<swp-card-title>Dagsoplysninger</swp-card-title>
<swp-card-hint>Identificér afstemningen</swp-card-hint>
</swp-card-header>
<swp-card-body>
<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: 20px;">
<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-body>
</swp-card>
<!-- Note -->
<swp-card>
<swp-card-header>
<swp-card-title>Note til difference</swp-card-title>
<swp-card-hint>Valgfrit</swp-card-hint>
</swp-card-header>
<swp-card-body>
<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-body>
</swp-card>
<!-- Godkendelse -->
<swp-card>
<swp-card-header>
<swp-card-title>Afslut dagen</swp-card-title>
</swp-card-header>
<swp-card-body>
<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-body>
<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>
</div>
</div>
<swp-system-note>Systemet gemmer hvornår og af hvem der er godkendt enkelt kontrolspor.</swp-system-note>
</swp-page-container>
<script>
// Base values (from system)
const startBalance = 2000;
const cashSales = 3540;
// Format number as Danish currency
function formatNumber(num) {
return num.toLocaleString('da-DK', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
// Parse Danish number format
function parseNumber(str) {
if (!str) return 0;
return parseFloat(str.replace(/\./g, '').replace(',', '.')) || 0;
}
// Calculate expected cash and difference
function calculate() {
const payoutsInput = document.getElementById('payouts');
const toBankInput = document.getElementById('toBank');
const actualInput = document.getElementById('actualCash');
const payouts = parseNumber(payoutsInput.value);
const toBank = parseNumber(toBankInput.value);
const actual = parseNumber(actualInput.value);
// Forventet = start + salg - udbetalinger - til bank
const expectedCash = startBalance + cashSales - payouts - toBank;
document.getElementById('expectedCash').textContent = formatNumber(expectedCash);
// Difference
const box = document.getElementById('differenceBox');
const value = document.getElementById('differenceValue');
const diff = actual - expectedCash;
box.classList.remove('positive', 'negative', 'neutral');
if (actual === 0 && actualInput.value === '') {
value.textContent = ' kr';
box.classList.add('neutral');
} else if (diff > 0) {
value.textContent = '+' + formatNumber(diff) + ' kr';
box.classList.add('positive');
} else if (diff < 0) {
value.textContent = formatNumber(diff) + ' kr';
box.classList.add('negative');
} else {
value.textContent = '0,00 kr';
box.classList.add('neutral');
}
}
document.getElementById('payouts').addEventListener('input', calculate);
document.getElementById('toBank').addEventListener('input', calculate);
document.getElementById('actualCash').addEventListener('input', calculate);
// Enable approve button when checkbox is checked
const checkbox = document.getElementById('confirmCheckbox');
const approveBtn = document.getElementById('approveBtn');
checkbox.addEventListener('change', () => {
approveBtn.disabled = !checkbox.checked;
});
// Initial display
calculate();
</script>
</body>
</html>