From 6c3662f571815f1ec21f923d8a16731eb577f1a9 Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Sun, 28 Dec 2025 23:31:31 +0100 Subject: [PATCH] Adds employee revenue and utilization chart Updates package dependencies and charts for employee performance visualization Includes: - Upgrade to latest charting library version - New chart displaying revenue and utilization metrics - Forecast and actual data visualization with dual axis support --- package-lock.json | 8 +- package.json | 2 +- .../data/employee-revenue-utilization.json | 67 +++++++++++++++ wwwroot/poc-employee.html | 84 +++++++++++++++++++ 4 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 wwwroot/data/employee-revenue-utilization.json diff --git a/package-lock.json b/package-lock.json index b746aa7..aaf69a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@novadi/core": "^0.6.0", "@rollup/rollup-win32-x64-msvc": "^4.52.2", - "@sevenweirdpeople/swp-charting": "^0.1.7", + "@sevenweirdpeople/swp-charting": "^0.2.1", "dayjs": "^1.11.19", "fuse.js": "^7.1.0", "json-diff-ts": "^4.8.2", @@ -1175,9 +1175,9 @@ ] }, "node_modules/@sevenweirdpeople/swp-charting": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@sevenweirdpeople/swp-charting/-/swp-charting-0.1.7.tgz", - "integrity": "sha512-i3HEQMQmltmxykPGXCRcN8VMJZXD1sI8urb7rN8eTjB9EscDsACdkS+WyvxOEEEhyHj1hMdtULeX3BIZ1ZNrng==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@sevenweirdpeople/swp-charting/-/swp-charting-0.2.1.tgz", + "integrity": "sha512-QtY77Dyv4Vs/rWfBVSDTmuxgD4L8tGu4pmTF0l3i8HDwK6qtT2wEtH35UHD1RDFE1VtOGcnU0/dTdqjNWCqzxA==", "license": "MIT" }, "node_modules/@types/chai": { diff --git a/package.json b/package.json index 105cb7a..8eb633b 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "dependencies": { "@novadi/core": "^0.6.0", "@rollup/rollup-win32-x64-msvc": "^4.52.2", - "@sevenweirdpeople/swp-charting": "^0.1.7", + "@sevenweirdpeople/swp-charting": "^0.2.1", "dayjs": "^1.11.19", "fuse.js": "^7.1.0", "json-diff-ts": "^4.8.2", diff --git a/wwwroot/data/employee-revenue-utilization.json b/wwwroot/data/employee-revenue-utilization.json new file mode 100644 index 0000000..02746a0 --- /dev/null +++ b/wwwroot/data/employee-revenue-utilization.json @@ -0,0 +1,67 @@ +{ + "categories": ["Uge 40", "Uge 41", "Uge 42", "Uge 43", "Uge 44", "Uge 45", "Uge 46", "Uge 47", "Uge 48", "Uge 49", "Uge 50", "Uge 51", "Uge 52", "Uge 1", "Uge 2", "Uge 3", "Uge 4", "Uge 5", "Uge 6", "Uge 7", "Uge 8", "Uge 9", "Uge 10", "Uge 11", "Uge 12"], + "nowCategory": "Uge 52", + "forecastStartCategory": "Uge 1", + "actual": { + "revenue": [ + { "x": "Uge 40", "y": 38500 }, + { "x": "Uge 41", "y": 42000 }, + { "x": "Uge 42", "y": 35200 }, + { "x": "Uge 43", "y": 44800 }, + { "x": "Uge 44", "y": 41500 }, + { "x": "Uge 45", "y": 39200 }, + { "x": "Uge 46", "y": 46100 }, + { "x": "Uge 47", "y": 43800 }, + { "x": "Uge 48", "y": 40500 }, + { "x": "Uge 49", "y": 45200 }, + { "x": "Uge 50", "y": 42800 }, + { "x": "Uge 51", "y": 44500 }, + { "x": "Uge 52", "y": 28450 } + ], + "utilization": [ + { "x": "Uge 40", "y": 82 }, + { "x": "Uge 41", "y": 88 }, + { "x": "Uge 42", "y": 75 }, + { "x": "Uge 43", "y": 91 }, + { "x": "Uge 44", "y": 85 }, + { "x": "Uge 45", "y": 80 }, + { "x": "Uge 46", "y": 94 }, + { "x": "Uge 47", "y": 89 }, + { "x": "Uge 48", "y": 83 }, + { "x": "Uge 49", "y": 92 }, + { "x": "Uge 50", "y": 87 }, + { "x": "Uge 51", "y": 90 }, + { "x": "Uge 52", "y": 84 } + ] + }, + "forecast": { + "revenue": [ + { "x": "Uge 1", "y": 0 }, + { "x": "Uge 2", "y": 32500 }, + { "x": "Uge 3", "y": 28800 }, + { "x": "Uge 4", "y": 22400 }, + { "x": "Uge 5", "y": 15200 }, + { "x": "Uge 6", "y": 9800 }, + { "x": "Uge 7", "y": 6500 }, + { "x": "Uge 8", "y": 4200 }, + { "x": "Uge 9", "y": 2800 }, + { "x": "Uge 10", "y": 1500 }, + { "x": "Uge 11", "y": 800 }, + { "x": "Uge 12", "y": 0 } + ], + "utilization": [ + { "x": "Uge 1", "y": 0 }, + { "x": "Uge 2", "y": 74 }, + { "x": "Uge 3", "y": 58 }, + { "x": "Uge 4", "y": 39 }, + { "x": "Uge 5", "y": 21 }, + { "x": "Uge 6", "y": 11 }, + { "x": "Uge 7", "y": 8 }, + { "x": "Uge 8", "y": 5 }, + { "x": "Uge 9", "y": 3 }, + { "x": "Uge 10", "y": 2 }, + { "x": "Uge 11", "y": 1 }, + { "x": "Uge 12", "y": 0 } + ] + } +} diff --git a/wwwroot/poc-employee.html b/wwwroot/poc-employee.html index 4227315..a46c0eb 100644 --- a/wwwroot/poc-employee.html +++ b/wwwroot/poc-employee.html @@ -1895,6 +1895,13 @@ color: var(--color-text-secondary); } + swp-chart-subtitle { + display: block; + font-size: 12px; + color: var(--color-text-muted); + margin-top: 2px; + } + swp-chart-legend { display: flex; gap: 16px; @@ -2658,6 +2665,17 @@ + + + + + Omsætning & Belægningsgrad + 3 måneder bagud · 3 måneder frem + + + + +
@@ -3489,6 +3507,72 @@ height: 200, legend: false }); + + // Revenue & Utilization chart - dual axis (3 mdr bagud + 3 mdr frem) + fetch('data/employee-revenue-utilization.json') + .then(res => res.json()) + .then(data => { + createChart(document.getElementById('revenueUtilizationChart'), { + xAxis: { categories: data.categories }, + yAxis: [ + { min: 0, max: 50000, format: v => `${(v/1000).toFixed(0)}k` }, // Left: Revenue + { min: 0, max: 100, format: v => `${v}%` }, // Right: Utilization + ], + series: [ + // Actual revenue (solid bars) + { + name: 'Omsætning', + color: '#3b82f6', + type: 'bar', + yAxisIndex: 0, + unit: 'kr', + data: data.actual.revenue, + bar: { radius: 2 }, + }, + // Forecast revenue (transparent bars) + { + name: 'Omsætning (forecast)', + color: '#3b82f6', + type: 'bar', + yAxisIndex: 0, + unit: 'kr', + data: data.forecast.revenue, + bar: { radius: 2, opacity: 0.35 }, + }, + // Actual utilization (solid line) + { + name: 'Belægning', + color: '#00897b', + type: 'line', + yAxisIndex: 1, + unit: '%', + data: data.actual.utilization, + line: { width: 2.5 }, + point: { radius: 0 }, + showArea: false, + }, + // Forecast utilization (dashed line) + { + name: 'Belægning (forecast)', + color: '#00897b', + type: 'line', + yAxisIndex: 1, + unit: '%', + data: data.forecast.utilization, + line: { width: 2.5, dashArray: '4 4' }, + point: { radius: 0 }, + showArea: false, + }, + ], + annotations: [ + { type: 'region', x: data.forecastStartCategory, x2: data.categories[data.categories.length - 1], backgroundColor: 'rgba(0,0,0,0.03)' }, + { type: 'verticalLine', x: data.nowCategory, dashArray: '4 4', label: 'Nu', labelPosition: 'top' }, + ], + legend: { position: 'top', align: 'end' }, + padding: { right: 60 }, + height: 280, + }); + });