1010 lines
26 KiB
HTML
1010 lines
26 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="da">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>AI Booking Optimering - KARINA KNUDSEN®</title>
|
||
|
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@phosphor-icons/web@2.1.2/src/regular/style.css">
|
||
|
|
<style>
|
||
|
|
@font-face { font-family: 'Poppins'; src: url('fonts/Poppins-Regular.woff') format('woff'); font-weight: 400; }
|
||
|
|
@font-face { font-family: 'Poppins'; src: url('fonts/Poppins-Medium.woff') format('woff'); font-weight: 500; }
|
||
|
|
@font-face { font-family: 'Poppins'; src: url('fonts/Poppins-SemiBold.woff') format('woff'); font-weight: 600; }
|
||
|
|
@font-face { font-family: 'Poppins'; src: url('fonts/Poppins-Bold.woff') format('woff'); font-weight: 700; }
|
||
|
|
|
||
|
|
:root {
|
||
|
|
--color-teal: #00897b;
|
||
|
|
--color-teal-light: #e0f2f1;
|
||
|
|
--color-surface: #ffffff;
|
||
|
|
--color-background: #f5f5f5;
|
||
|
|
--color-background-alt: #fafafa;
|
||
|
|
--color-border: #e0e0e0;
|
||
|
|
--color-text: #333;
|
||
|
|
--color-text-secondary: #666;
|
||
|
|
--color-text-muted: #999;
|
||
|
|
--color-green: #43a047;
|
||
|
|
--color-amber: #f59e0b;
|
||
|
|
--color-red: #e53935;
|
||
|
|
--color-purple: #8b5cf6;
|
||
|
|
--color-blue: #1976d2;
|
||
|
|
--font-family: 'Poppins', -apple-system, sans-serif;
|
||
|
|
--font-mono: 'JetBrains Mono', monospace;
|
||
|
|
}
|
||
|
|
|
||
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
|
|
||
|
|
body {
|
||
|
|
font-family: var(--font-family);
|
||
|
|
background: var(--color-background);
|
||
|
|
color: var(--color-text);
|
||
|
|
min-height: 100vh;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ==========================================
|
||
|
|
TOPBAR
|
||
|
|
========================================== */
|
||
|
|
swp-topbar {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: space-between;
|
||
|
|
padding: 16px 24px;
|
||
|
|
background: var(--color-surface);
|
||
|
|
border-bottom: 1px solid var(--color-border);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-topbar-left {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-topbar-title {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-topbar-title h1 {
|
||
|
|
font-size: 18px;
|
||
|
|
font-weight: 600;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-ai-badge-header {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 6px;
|
||
|
|
background: linear-gradient(135deg, var(--color-purple) 0%, var(--color-blue) 100%);
|
||
|
|
color: white;
|
||
|
|
font-size: 11px;
|
||
|
|
font-weight: 600;
|
||
|
|
padding: 4px 10px;
|
||
|
|
border-radius: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-topbar-date {
|
||
|
|
font-size: 14px;
|
||
|
|
color: var(--color-text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ==========================================
|
||
|
|
PAGE CONTAINER
|
||
|
|
========================================== */
|
||
|
|
swp-page-container {
|
||
|
|
display: block;
|
||
|
|
max-width: 1200px;
|
||
|
|
margin: 0 auto;
|
||
|
|
padding: 24px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ==========================================
|
||
|
|
STATS GRID
|
||
|
|
========================================== */
|
||
|
|
swp-stats-grid {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: repeat(4, 1fr);
|
||
|
|
gap: 16px;
|
||
|
|
margin-bottom: 24px;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (max-width: 900px) {
|
||
|
|
swp-stats-grid {
|
||
|
|
grid-template-columns: repeat(2, 1fr);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-stat-card {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 8px;
|
||
|
|
padding: 20px;
|
||
|
|
background: var(--color-surface);
|
||
|
|
border-radius: 12px;
|
||
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-stat-card.highlight {
|
||
|
|
background: linear-gradient(135deg, var(--color-teal) 0%, #00695c 100%);
|
||
|
|
color: white;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-stat-label {
|
||
|
|
font-size: 12px;
|
||
|
|
font-weight: 500;
|
||
|
|
color: var(--color-text-secondary);
|
||
|
|
text-transform: uppercase;
|
||
|
|
letter-spacing: 0.5px;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-stat-card.highlight swp-stat-label {
|
||
|
|
color: rgba(255,255,255,0.8);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-stat-value {
|
||
|
|
font-size: 28px;
|
||
|
|
font-weight: 700;
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-stat-change {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 4px;
|
||
|
|
font-size: 12px;
|
||
|
|
font-weight: 500;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-stat-change.positive {
|
||
|
|
color: var(--color-green);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-stat-change.negative {
|
||
|
|
color: var(--color-red);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-stat-card.highlight swp-stat-change {
|
||
|
|
color: rgba(255,255,255,0.9);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ==========================================
|
||
|
|
MAIN GRID
|
||
|
|
========================================== */
|
||
|
|
swp-main-grid {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: 1fr 340px;
|
||
|
|
gap: 24px;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (max-width: 1000px) {
|
||
|
|
swp-main-grid {
|
||
|
|
grid-template-columns: 1fr;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ==========================================
|
||
|
|
CARD
|
||
|
|
========================================== */
|
||
|
|
swp-card {
|
||
|
|
display: block;
|
||
|
|
background: var(--color-surface);
|
||
|
|
border-radius: 12px;
|
||
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
||
|
|
margin-bottom: 24px;
|
||
|
|
}
|
||
|
|
|
||
|
|
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 {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 10px;
|
||
|
|
font-size: 15px;
|
||
|
|
font-weight: 600;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-card-title i {
|
||
|
|
font-size: 20px;
|
||
|
|
color: var(--color-teal);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-card-content {
|
||
|
|
display: block;
|
||
|
|
padding: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ==========================================
|
||
|
|
MINI CALENDAR
|
||
|
|
========================================== */
|
||
|
|
swp-mini-calendar {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: repeat(5, 1fr);
|
||
|
|
gap: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-mini-day {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: center;
|
||
|
|
gap: 6px;
|
||
|
|
padding: 12px 8px;
|
||
|
|
background: var(--color-background-alt);
|
||
|
|
border-radius: 8px;
|
||
|
|
cursor: pointer;
|
||
|
|
transition: all 150ms ease;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-mini-day:hover {
|
||
|
|
background: var(--color-background);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-mini-day.today {
|
||
|
|
background: var(--color-teal-light);
|
||
|
|
border: 2px solid var(--color-teal);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-mini-day-name {
|
||
|
|
font-size: 11px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: var(--color-text-secondary);
|
||
|
|
text-transform: uppercase;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-mini-day-date {
|
||
|
|
font-size: 16px;
|
||
|
|
font-weight: 600;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-mini-day-status {
|
||
|
|
display: flex;
|
||
|
|
gap: 3px;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-mini-day-dot {
|
||
|
|
width: 6px;
|
||
|
|
height: 6px;
|
||
|
|
border-radius: 50%;
|
||
|
|
background: var(--color-green);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-mini-day-dot.gap {
|
||
|
|
background: var(--color-amber);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-mini-day-dot.critical {
|
||
|
|
background: var(--color-red);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ==========================================
|
||
|
|
GAPS LIST
|
||
|
|
========================================== */
|
||
|
|
swp-gaps-list {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-gap-card {
|
||
|
|
display: block;
|
||
|
|
border: 1px solid var(--color-border);
|
||
|
|
border-radius: 10px;
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-gap-header {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: space-between;
|
||
|
|
padding: 14px 16px;
|
||
|
|
background: var(--color-background-alt);
|
||
|
|
border-bottom: 1px solid var(--color-border);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-gap-time {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 8px;
|
||
|
|
font-size: 14px;
|
||
|
|
font-weight: 600;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-gap-time i {
|
||
|
|
font-size: 18px;
|
||
|
|
color: var(--color-amber);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-gap-meta {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 12px;
|
||
|
|
font-size: 12px;
|
||
|
|
color: var(--color-text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-gap-employee {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 6px;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-gap-employee-avatar {
|
||
|
|
width: 20px;
|
||
|
|
height: 20px;
|
||
|
|
border-radius: 50%;
|
||
|
|
background: var(--color-purple);
|
||
|
|
color: white;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
font-size: 10px;
|
||
|
|
font-weight: 600;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-gap-revenue {
|
||
|
|
font-weight: 500;
|
||
|
|
color: var(--color-red);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-gap-content {
|
||
|
|
padding: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-gap-suggestions-title {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 6px;
|
||
|
|
font-size: 12px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: var(--color-text-secondary);
|
||
|
|
margin-bottom: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-gap-suggestions-title i {
|
||
|
|
font-size: 16px;
|
||
|
|
color: var(--color-purple);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-suggestion-list {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-suggestion-item {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: space-between;
|
||
|
|
padding: 12px;
|
||
|
|
background: var(--color-background-alt);
|
||
|
|
border-radius: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-suggestion-info {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 2px;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-suggestion-customer {
|
||
|
|
font-size: 13px;
|
||
|
|
font-weight: 500;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-suggestion-detail {
|
||
|
|
font-size: 11px;
|
||
|
|
color: var(--color-text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-flex-score {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 4px;
|
||
|
|
font-size: 11px;
|
||
|
|
font-weight: 500;
|
||
|
|
color: var(--color-green);
|
||
|
|
background: color-mix(in srgb, var(--color-green) 12%, transparent);
|
||
|
|
padding: 2px 8px;
|
||
|
|
border-radius: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-suggestion-action {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-btn {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 6px;
|
||
|
|
padding: 8px 14px;
|
||
|
|
font-size: 12px;
|
||
|
|
font-weight: 500;
|
||
|
|
font-family: var(--font-family);
|
||
|
|
border-radius: 6px;
|
||
|
|
cursor: pointer;
|
||
|
|
transition: all 150ms ease;
|
||
|
|
border: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-btn.primary {
|
||
|
|
background: var(--color-teal);
|
||
|
|
color: white;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-btn.primary:hover {
|
||
|
|
background: #00695c;
|
||
|
|
}
|
||
|
|
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-btn i {
|
||
|
|
font-size: 14px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Waitlist suggestion */
|
||
|
|
swp-suggestion-item.waitlist {
|
||
|
|
border: 1px dashed var(--color-border);
|
||
|
|
background: transparent;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-waitlist-badge {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 4px;
|
||
|
|
font-size: 10px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: var(--color-blue);
|
||
|
|
background: color-mix(in srgb, var(--color-blue) 12%, transparent);
|
||
|
|
padding: 2px 8px;
|
||
|
|
border-radius: 4px;
|
||
|
|
text-transform: uppercase;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ==========================================
|
||
|
|
SMS HISTORY
|
||
|
|
========================================== */
|
||
|
|
swp-sms-list {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-sms-item {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 12px;
|
||
|
|
padding: 12px 0;
|
||
|
|
border-bottom: 1px solid var(--color-border);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-sms-item:last-child {
|
||
|
|
border-bottom: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-sms-status-icon {
|
||
|
|
width: 28px;
|
||
|
|
height: 28px;
|
||
|
|
border-radius: 50%;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
font-size: 14px;
|
||
|
|
flex-shrink: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-sms-status-icon.accepted {
|
||
|
|
background: color-mix(in srgb, var(--color-green) 15%, transparent);
|
||
|
|
color: var(--color-green);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-sms-status-icon.rejected {
|
||
|
|
background: color-mix(in srgb, var(--color-red) 15%, transparent);
|
||
|
|
color: var(--color-red);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-sms-status-icon.pending {
|
||
|
|
background: color-mix(in srgb, var(--color-amber) 15%, transparent);
|
||
|
|
color: var(--color-amber);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-sms-info {
|
||
|
|
flex: 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-sms-customer {
|
||
|
|
font-size: 13px;
|
||
|
|
font-weight: 500;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-sms-detail {
|
||
|
|
font-size: 11px;
|
||
|
|
color: var(--color-text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-sms-date {
|
||
|
|
font-size: 11px;
|
||
|
|
color: var(--color-text-muted);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ==========================================
|
||
|
|
OPTIMIZATION SCORE
|
||
|
|
========================================== */
|
||
|
|
swp-optimization-score {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: center;
|
||
|
|
gap: 12px;
|
||
|
|
padding: 24px;
|
||
|
|
text-align: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-score-circle {
|
||
|
|
width: 100px;
|
||
|
|
height: 100px;
|
||
|
|
border-radius: 50%;
|
||
|
|
background: conic-gradient(var(--color-teal) var(--score-percent), var(--color-border) var(--score-percent));
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
position: relative;
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-score-circle::before {
|
||
|
|
content: '';
|
||
|
|
position: absolute;
|
||
|
|
width: 80px;
|
||
|
|
height: 80px;
|
||
|
|
border-radius: 50%;
|
||
|
|
background: var(--color-surface);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-score-value {
|
||
|
|
position: relative;
|
||
|
|
z-index: 1;
|
||
|
|
font-size: 24px;
|
||
|
|
font-weight: 700;
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
color: var(--color-teal);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-score-label {
|
||
|
|
font-size: 13px;
|
||
|
|
color: var(--color-text-secondary);
|
||
|
|
}
|
||
|
|
|
||
|
|
swp-score-hint {
|
||
|
|
font-size: 11px;
|
||
|
|
color: var(--color-text-muted);
|
||
|
|
max-width: 200px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ==========================================
|
||
|
|
SIDEBAR SECTION
|
||
|
|
========================================== */
|
||
|
|
swp-sidebar {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 24px;
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
|
||
|
|
<swp-topbar>
|
||
|
|
<swp-topbar-left>
|
||
|
|
<swp-topbar-title>
|
||
|
|
<h1>AI Booking Optimering</h1>
|
||
|
|
<swp-ai-badge-header>
|
||
|
|
<i class="ph ph-sparkle"></i>
|
||
|
|
AI-drevet
|
||
|
|
</swp-ai-badge-header>
|
||
|
|
</swp-topbar-title>
|
||
|
|
</swp-topbar-left>
|
||
|
|
<swp-topbar-date id="currentDate"></swp-topbar-date>
|
||
|
|
</swp-topbar>
|
||
|
|
|
||
|
|
<swp-page-container>
|
||
|
|
<!-- Stats Grid -->
|
||
|
|
<swp-stats-grid>
|
||
|
|
<swp-stat-card>
|
||
|
|
<swp-stat-label>Huller i dag</swp-stat-label>
|
||
|
|
<swp-stat-value id="gapsToday">3</swp-stat-value>
|
||
|
|
<swp-stat-change class="negative">
|
||
|
|
<i class="ph ph-arrow-up"></i>
|
||
|
|
+1 fra i går
|
||
|
|
</swp-stat-change>
|
||
|
|
</swp-stat-card>
|
||
|
|
|
||
|
|
<swp-stat-card>
|
||
|
|
<swp-stat-label>Tabt omsætning</swp-stat-label>
|
||
|
|
<swp-stat-value id="lostRevenue">1.950 kr.</swp-stat-value>
|
||
|
|
<swp-stat-change class="negative">
|
||
|
|
<i class="ph ph-arrow-up"></i>
|
||
|
|
+450 kr.
|
||
|
|
</swp-stat-change>
|
||
|
|
</swp-stat-card>
|
||
|
|
|
||
|
|
<swp-stat-card>
|
||
|
|
<swp-stat-label>Huller denne uge</swp-stat-label>
|
||
|
|
<swp-stat-value id="gapsWeek">12</swp-stat-value>
|
||
|
|
<swp-stat-change class="positive">
|
||
|
|
<i class="ph ph-arrow-down"></i>
|
||
|
|
-3 fra sidste uge
|
||
|
|
</swp-stat-change>
|
||
|
|
</swp-stat-card>
|
||
|
|
|
||
|
|
<swp-stat-card class="highlight">
|
||
|
|
<swp-stat-label>Potentiel besparelse</swp-stat-label>
|
||
|
|
<swp-stat-value id="potentialSavings">8.400 kr.</swp-stat-value>
|
||
|
|
<swp-stat-change>
|
||
|
|
<i class="ph ph-trend-up"></i>
|
||
|
|
Denne måned
|
||
|
|
</swp-stat-change>
|
||
|
|
</swp-stat-card>
|
||
|
|
</swp-stats-grid>
|
||
|
|
|
||
|
|
<!-- Main Grid -->
|
||
|
|
<swp-main-grid>
|
||
|
|
<!-- Left Column -->
|
||
|
|
<div>
|
||
|
|
<!-- Mini Calendar -->
|
||
|
|
<swp-card>
|
||
|
|
<swp-card-header>
|
||
|
|
<swp-card-title>
|
||
|
|
<i class="ph ph-calendar"></i>
|
||
|
|
Denne uge
|
||
|
|
</swp-card-title>
|
||
|
|
</swp-card-header>
|
||
|
|
<swp-card-content>
|
||
|
|
<swp-mini-calendar id="miniCalendar"></swp-mini-calendar>
|
||
|
|
</swp-card-content>
|
||
|
|
</swp-card>
|
||
|
|
|
||
|
|
<!-- Gaps List -->
|
||
|
|
<swp-card>
|
||
|
|
<swp-card-header>
|
||
|
|
<swp-card-title>
|
||
|
|
<i class="ph ph-warning-circle"></i>
|
||
|
|
Identificerede huller
|
||
|
|
</swp-card-title>
|
||
|
|
<swp-btn class="secondary">
|
||
|
|
<i class="ph ph-funnel"></i>
|
||
|
|
Filter
|
||
|
|
</swp-btn>
|
||
|
|
</swp-card-header>
|
||
|
|
<swp-card-content>
|
||
|
|
<swp-gaps-list id="gapsList"></swp-gaps-list>
|
||
|
|
</swp-card-content>
|
||
|
|
</swp-card>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Sidebar -->
|
||
|
|
<swp-sidebar>
|
||
|
|
<!-- Optimization Score -->
|
||
|
|
<swp-card>
|
||
|
|
<swp-card-header>
|
||
|
|
<swp-card-title>
|
||
|
|
<i class="ph ph-chart-pie"></i>
|
||
|
|
Optimeringsscore
|
||
|
|
</swp-card-title>
|
||
|
|
</swp-card-header>
|
||
|
|
<swp-card-content>
|
||
|
|
<swp-optimization-score>
|
||
|
|
<swp-score-circle style="--score-percent: 78%;">
|
||
|
|
<swp-score-value>78%</swp-score-value>
|
||
|
|
</swp-score-circle>
|
||
|
|
<swp-score-label>Kalenderudnyttelse</swp-score-label>
|
||
|
|
<swp-score-hint>Mål: 90% udnyttelse. Fyld 2 huller mere for at nå målet.</swp-score-hint>
|
||
|
|
</swp-optimization-score>
|
||
|
|
</swp-card-content>
|
||
|
|
</swp-card>
|
||
|
|
|
||
|
|
<!-- SMS History -->
|
||
|
|
<swp-card>
|
||
|
|
<swp-card-header>
|
||
|
|
<swp-card-title>
|
||
|
|
<i class="ph ph-chat-circle-text"></i>
|
||
|
|
SMS-historik
|
||
|
|
</swp-card-title>
|
||
|
|
</swp-card-header>
|
||
|
|
<swp-card-content>
|
||
|
|
<swp-sms-list id="smsList"></swp-sms-list>
|
||
|
|
</swp-card-content>
|
||
|
|
</swp-card>
|
||
|
|
</swp-sidebar>
|
||
|
|
</swp-main-grid>
|
||
|
|
</swp-page-container>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
// ==========================================
|
||
|
|
// DATA
|
||
|
|
// ==========================================
|
||
|
|
const employees = [
|
||
|
|
{ id: 'EMP001', name: 'Camilla', initials: 'CA', color: '#9c27b0' },
|
||
|
|
{ id: 'EMP002', name: 'Isabella', initials: 'IS', color: '#e91e63' },
|
||
|
|
{ id: 'EMP003', name: 'Alexander', initials: 'AL', color: '#3f51b5' },
|
||
|
|
{ id: 'EMP004', name: 'Viktor', initials: 'VI', color: '#009688' }
|
||
|
|
];
|
||
|
|
|
||
|
|
const gaps = [
|
||
|
|
{
|
||
|
|
id: 'gap1',
|
||
|
|
date: '2026-01-06',
|
||
|
|
dayName: 'Tirsdag',
|
||
|
|
start: '11:00',
|
||
|
|
end: '12:00',
|
||
|
|
duration: 60,
|
||
|
|
employeeId: 'EMP001',
|
||
|
|
lostRevenue: 650,
|
||
|
|
suggestions: [
|
||
|
|
{
|
||
|
|
type: 'move',
|
||
|
|
customerId: 'C001',
|
||
|
|
customerName: 'Maria Jensen',
|
||
|
|
currentTime: '14:00',
|
||
|
|
currentDate: '2026-01-06',
|
||
|
|
flexScore: 87,
|
||
|
|
service: 'Dameklip'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'waitlist',
|
||
|
|
customerId: 'C002',
|
||
|
|
customerName: 'Lars Hansen',
|
||
|
|
note: 'Ønsker tid denne uge',
|
||
|
|
service: 'Herreklip'
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'gap2',
|
||
|
|
date: '2026-01-06',
|
||
|
|
dayName: 'Tirsdag',
|
||
|
|
start: '14:30',
|
||
|
|
end: '15:30',
|
||
|
|
duration: 60,
|
||
|
|
employeeId: 'EMP002',
|
||
|
|
lostRevenue: 725,
|
||
|
|
suggestions: [
|
||
|
|
{
|
||
|
|
type: 'move',
|
||
|
|
customerId: 'C003',
|
||
|
|
customerName: 'Anne Larsen',
|
||
|
|
currentTime: '10:00',
|
||
|
|
currentDate: '2026-01-07',
|
||
|
|
flexScore: 62,
|
||
|
|
service: 'Dameklip'
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'gap3',
|
||
|
|
date: '2026-01-08',
|
||
|
|
dayName: 'Torsdag',
|
||
|
|
start: '09:00',
|
||
|
|
end: '10:30',
|
||
|
|
duration: 90,
|
||
|
|
employeeId: 'EMP003',
|
||
|
|
lostRevenue: 575,
|
||
|
|
suggestions: [
|
||
|
|
{
|
||
|
|
type: 'waitlist',
|
||
|
|
customerId: 'C004',
|
||
|
|
customerName: 'Peter Olsen',
|
||
|
|
note: 'Ny kunde, fleksibel',
|
||
|
|
service: 'Bundfarve'
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
];
|
||
|
|
|
||
|
|
const weekDays = [
|
||
|
|
{ date: '2026-01-05', name: 'Man', day: 5, gaps: 0, bookings: 8 },
|
||
|
|
{ date: '2026-01-06', name: 'Tir', day: 6, gaps: 2, bookings: 6, isToday: true },
|
||
|
|
{ date: '2026-01-07', name: 'Ons', day: 7, gaps: 1, bookings: 7 },
|
||
|
|
{ date: '2026-01-08', name: 'Tor', day: 8, gaps: 1, bookings: 5 },
|
||
|
|
{ date: '2026-01-09', name: 'Fre', day: 9, gaps: 0, bookings: 9 }
|
||
|
|
];
|
||
|
|
|
||
|
|
const smsHistory = [
|
||
|
|
{
|
||
|
|
customerId: 'C005',
|
||
|
|
customerName: 'Sofie Nielsen',
|
||
|
|
status: 'accepted',
|
||
|
|
action: 'Flyttede fra 15:00 til 11:00',
|
||
|
|
discount: '5%',
|
||
|
|
date: 'I går'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
customerId: 'C006',
|
||
|
|
customerName: 'Peter Olsen',
|
||
|
|
status: 'rejected',
|
||
|
|
action: 'Afviste flytning',
|
||
|
|
date: '3. jan'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
customerId: 'C003',
|
||
|
|
customerName: 'Anne Larsen',
|
||
|
|
status: 'pending',
|
||
|
|
action: 'Tilbud sendt: Flyt til 14:30',
|
||
|
|
discount: '5%',
|
||
|
|
date: 'I dag'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
customerId: 'C007',
|
||
|
|
customerName: 'Michael Bruun',
|
||
|
|
status: 'accepted',
|
||
|
|
action: 'Booket fra venteliste',
|
||
|
|
date: '2. jan'
|
||
|
|
}
|
||
|
|
];
|
||
|
|
|
||
|
|
// ==========================================
|
||
|
|
// RENDER FUNCTIONS
|
||
|
|
// ==========================================
|
||
|
|
function getEmployee(id) {
|
||
|
|
return employees.find(e => e.id === id) || employees[0];
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderCurrentDate() {
|
||
|
|
const now = new Date();
|
||
|
|
const options = { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' };
|
||
|
|
document.getElementById('currentDate').textContent = now.toLocaleDateString('da-DK', options);
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderMiniCalendar() {
|
||
|
|
const container = document.getElementById('miniCalendar');
|
||
|
|
container.innerHTML = weekDays.map(day => `
|
||
|
|
<swp-mini-day class="${day.isToday ? 'today' : ''}">
|
||
|
|
<swp-mini-day-name>${day.name}</swp-mini-day-name>
|
||
|
|
<swp-mini-day-date>${day.day}</swp-mini-day-date>
|
||
|
|
<swp-mini-day-status>
|
||
|
|
${day.gaps > 0 ? `<swp-mini-day-dot class="${day.gaps > 1 ? 'critical' : 'gap'}"></swp-mini-day-dot>`.repeat(Math.min(day.gaps, 3)) : '<swp-mini-day-dot></swp-mini-day-dot>'}
|
||
|
|
</swp-mini-day-status>
|
||
|
|
</swp-mini-day>
|
||
|
|
`).join('');
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderGapsList() {
|
||
|
|
const container = document.getElementById('gapsList');
|
||
|
|
container.innerHTML = gaps.map(gap => {
|
||
|
|
const emp = getEmployee(gap.employeeId);
|
||
|
|
return `
|
||
|
|
<swp-gap-card>
|
||
|
|
<swp-gap-header>
|
||
|
|
<swp-gap-time>
|
||
|
|
<i class="ph ph-clock"></i>
|
||
|
|
${gap.dayName} ${gap.start}-${gap.end} (${gap.duration} min)
|
||
|
|
</swp-gap-time>
|
||
|
|
<swp-gap-meta>
|
||
|
|
<swp-gap-employee>
|
||
|
|
<swp-gap-employee-avatar style="background: ${emp.color}">${emp.initials}</swp-gap-employee-avatar>
|
||
|
|
${emp.name}
|
||
|
|
</swp-gap-employee>
|
||
|
|
<swp-gap-revenue>~${gap.lostRevenue} kr. tabt</swp-gap-revenue>
|
||
|
|
</swp-gap-meta>
|
||
|
|
</swp-gap-header>
|
||
|
|
<swp-gap-content>
|
||
|
|
<swp-gap-suggestions-title>
|
||
|
|
<i class="ph ph-sparkle"></i>
|
||
|
|
AI-forslag
|
||
|
|
</swp-gap-suggestions-title>
|
||
|
|
<swp-suggestion-list>
|
||
|
|
${gap.suggestions.map(s => renderSuggestion(s)).join('')}
|
||
|
|
</swp-suggestion-list>
|
||
|
|
</swp-gap-content>
|
||
|
|
</swp-gap-card>
|
||
|
|
`;
|
||
|
|
}).join('');
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderSuggestion(suggestion) {
|
||
|
|
if (suggestion.type === 'move') {
|
||
|
|
return `
|
||
|
|
<swp-suggestion-item>
|
||
|
|
<swp-suggestion-info>
|
||
|
|
<swp-suggestion-customer>${suggestion.customerName}</swp-suggestion-customer>
|
||
|
|
<swp-suggestion-detail>Nuværende tid: ${suggestion.currentTime} · ${suggestion.service}</swp-suggestion-detail>
|
||
|
|
<swp-flex-score>
|
||
|
|
<i class="ph ph-chart-line-up"></i>
|
||
|
|
Fleksibilitet: ${suggestion.flexScore}%
|
||
|
|
</swp-flex-score>
|
||
|
|
</swp-suggestion-info>
|
||
|
|
<swp-suggestion-action>
|
||
|
|
<swp-btn class="primary" onclick="sendOffer('${suggestion.customerId}')">
|
||
|
|
<i class="ph ph-paper-plane-tilt"></i>
|
||
|
|
Send tilbud -5%
|
||
|
|
</swp-btn>
|
||
|
|
</swp-suggestion-action>
|
||
|
|
</swp-suggestion-item>
|
||
|
|
`;
|
||
|
|
} else {
|
||
|
|
return `
|
||
|
|
<swp-suggestion-item class="waitlist">
|
||
|
|
<swp-suggestion-info>
|
||
|
|
<swp-suggestion-customer>
|
||
|
|
${suggestion.customerName}
|
||
|
|
<swp-waitlist-badge>
|
||
|
|
<i class="ph ph-clock"></i>
|
||
|
|
Venteliste
|
||
|
|
</swp-waitlist-badge>
|
||
|
|
</swp-suggestion-customer>
|
||
|
|
<swp-suggestion-detail>${suggestion.note} · ${suggestion.service}</swp-suggestion-detail>
|
||
|
|
</swp-suggestion-info>
|
||
|
|
<swp-suggestion-action>
|
||
|
|
<swp-btn class="secondary" onclick="sendOffer('${suggestion.customerId}')">
|
||
|
|
<i class="ph ph-paper-plane-tilt"></i>
|
||
|
|
Send tilbud
|
||
|
|
</swp-btn>
|
||
|
|
</swp-suggestion-action>
|
||
|
|
</swp-suggestion-item>
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderSmsHistory() {
|
||
|
|
const container = document.getElementById('smsList');
|
||
|
|
container.innerHTML = smsHistory.map(sms => {
|
||
|
|
let icon, iconClass;
|
||
|
|
if (sms.status === 'accepted') {
|
||
|
|
icon = 'check';
|
||
|
|
iconClass = 'accepted';
|
||
|
|
} else if (sms.status === 'rejected') {
|
||
|
|
icon = 'x';
|
||
|
|
iconClass = 'rejected';
|
||
|
|
} else {
|
||
|
|
icon = 'hourglass';
|
||
|
|
iconClass = 'pending';
|
||
|
|
}
|
||
|
|
|
||
|
|
return `
|
||
|
|
<swp-sms-item>
|
||
|
|
<swp-sms-status-icon class="${iconClass}">
|
||
|
|
<i class="ph ph-${icon}"></i>
|
||
|
|
</swp-sms-status-icon>
|
||
|
|
<swp-sms-info>
|
||
|
|
<swp-sms-customer>${sms.customerName}</swp-sms-customer>
|
||
|
|
<swp-sms-detail>${sms.action}${sms.discount ? ` · ${sms.discount} rabat` : ''}</swp-sms-detail>
|
||
|
|
</swp-sms-info>
|
||
|
|
<swp-sms-date>${sms.date}</swp-sms-date>
|
||
|
|
</swp-sms-item>
|
||
|
|
`;
|
||
|
|
}).join('');
|
||
|
|
}
|
||
|
|
|
||
|
|
// ==========================================
|
||
|
|
// ACTIONS
|
||
|
|
// ==========================================
|
||
|
|
function sendOffer(customerId) {
|
||
|
|
// Simuler send
|
||
|
|
alert(`SMS sendt til kunde ${customerId} med tilbud om at flytte tid!`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ==========================================
|
||
|
|
// INIT
|
||
|
|
// ==========================================
|
||
|
|
function init() {
|
||
|
|
renderCurrentDate();
|
||
|
|
renderMiniCalendar();
|
||
|
|
renderGapsList();
|
||
|
|
renderSmsHistory();
|
||
|
|
}
|
||
|
|
|
||
|
|
init();
|
||
|
|
</script>
|
||
|
|
|
||
|
|
</body>
|
||
|
|
</html>
|