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
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 { SearchController } from './modules/search';
|
||||
import { LockScreenController } from './modules/lockscreen';
|
||||
import { KasseController } from './modules/kasse';
|
||||
|
||||
/**
|
||||
* Main application class
|
||||
|
|
@ -19,6 +20,7 @@ export class App {
|
|||
readonly theme: ThemeController;
|
||||
readonly search: SearchController;
|
||||
readonly lockScreen: LockScreenController;
|
||||
readonly kasse: KasseController;
|
||||
|
||||
constructor() {
|
||||
// Initialize controllers
|
||||
|
|
@ -27,6 +29,7 @@ export class App {
|
|||
this.theme = new ThemeController();
|
||||
this.search = new SearchController();
|
||||
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