Compare commits

..

2 Commits

Author SHA1 Message Date
Adrian Rumpold
3498e45ab2 Migrate styles to SCSS 2025-07-10 08:33:50 +02:00
Adrian Rumpold
a690718192 Category metadata / icon support 2025-07-10 08:33:42 +02:00
16 changed files with 1280 additions and 68 deletions

View File

@@ -2,8 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/svg+xml" href="/aai-favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preconnect" href="https://rsms.me/" />
@@ -12,7 +13,12 @@
href="https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,100..900;1,100..900&display=swap"
rel="stylesheet"
/>
<title>Vite + React</title>
<!-- Material Icons font -->
<link
rel="stylesheet"
href="https://fonts.googleapis.com/icon?family=Material+Icons"
/>
<title>AI Skills Framework</title>
</head>
<body>
<div id="root"></div>

1165
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,9 +10,12 @@
"preview": "vite preview"
},
"dependencies": {
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.2.0",
"d3": "^7.9.0",
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react-dom": "^19.1.0",
"sass": "^1.89.2"
},
"devDependencies": {
"@eslint/js": "^9.30.1",

BIN
public/aai-favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

View File

@@ -4,14 +4,20 @@ import Legend from "./components/Legend";
import QRCode from "./components/QRCode";
import { QuestionGroupChart } from "./components/QuestionGroupChart";
import { config } from "./config";
import { getSampleData } from "./lib/data";
import { ResponseData } from "./lib/parser";
import { CategoryMetadata, fetchCategoryMetadata } from "./lib/metadata";
import { fetchGoogleSheet, ResponseData } from "./lib/parser";
function App() {
const [data, setData] = useState<ResponseData[]>([]);
const [categoryMetadata, setCategoryMetadata] = useState<CategoryMetadata[]>(
[]
);
useEffect(() => {
// fetchGoogleSheet().then(setData);
setData(getSampleData());
fetchGoogleSheet().then(setData);
// setData(getSampleData());
fetchCategoryMetadata().then(setCategoryMetadata);
}, []);
if (!data.length) return null;
@@ -38,6 +44,7 @@ function App() {
<QuestionGroupChart
key={question}
question={question}
metadata={categoryMetadata.find((m) => m.category === question)}
groupData={groupData}
responses={sortedResponses}
xScale={xScale}

View File

@@ -1,5 +1,5 @@
import { colorScheme } from "../config";
import "./Legend.css";
import "../styles/Legend.scss";
const labels = {
0: "Keine Erfahrung",

View File

@@ -1,4 +1,4 @@
import "./QRCode.css";
import "../styles/QRCode.scss";
export default function QRCode() {
return (
@@ -9,7 +9,7 @@ export default function QRCode() {
egal, auf welchem Level du bist.
</p>
<div className="qr-code bracket-frame">
<img src="https://upload.wikimedia.org/wikipedia/commons/4/41/QR_Code_Example.svg" />
<img src="/qr_code_ki-skills-umfrage.png" />
</div>
</div>
);

View File

@@ -1,11 +1,15 @@
import MaterialIcon from "@mui/material/Icon";
import * as d3 from "d3";
import { useEffect, useRef } from "react";
import { colorScheme, config } from "../config";
import { CategoryMetadata } from "../lib/metadata";
import "./Chart.css";
interface QuestionGroupChartProps {
question: string;
metadata?: CategoryMetadata;
groupData: { response: string }[];
responses: string[];
xScale: d3.ScaleBand<string>;
@@ -38,6 +42,7 @@ function makeDot(
export function QuestionGroupChart({
question,
metadata,
groupData,
responses,
xScale,
@@ -81,13 +86,11 @@ export function QuestionGroupChart({
return (
<div className="question-group">
<svg ref={svgRef}></svg>
<div className="question-title">{question}</div>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo.
</p>
<div className="question-title">
{metadata?.icon && <MaterialIcon>{metadata.icon}</MaterialIcon>}
{question}
</div>
<p>{metadata?.description || question}</p>
</div>
);
}

View File

@@ -23,3 +23,6 @@ const aaiColors = [
export const colorScheme = Object.fromEntries(
aaiColors.map((color, index) => [index, color])
);
export const categoryMetadataUrl =
"https://docs.google.com/spreadsheets/d/e/2PACX-1vT6FQoV_8ET_pmEB5LGlI_ST9AAhsfiZrWydFwIB80G0Lr_kGwVJUzjM6fRPP9Yrx6iVZYMVAPTnLKq/pub?gid=0&single=true&output=csv";

37
src/lib/metadata.ts Normal file
View File

@@ -0,0 +1,37 @@
import * as d3 from "d3";
export interface CategoryMetadata {
category: string;
description: string;
icon: string;
}
const sampleMetadataCsv = `title,text,icon
Allgemeines KI-Wissen,Grundlegendes Wissen über Künstliche Intelligenz und deren Anwendung in Organisationen,school
KI-Innovation,"Fähigkeiten zur Entwicklung, Bewertung und Förderung von KI-Innovationen im Unternehmen",science
KI-Geschäftsstrategie,"Verstehen, wie KI strategisch in Geschäftsmodelle integriert und eingesetzt werden kann",business
Stakeholder-Landschaft,"Fähigkeit, relevante Stakeholder für KI-Initiativen zu identifizieren, einzubinden und zu koordinieren",people_alt
KI-Ethik,Kenntnisse über ethische Fragestellungen und verantwortungsvollen KI-Einsatz,local_police
KI-Regulation,Verständnis rechtlicher Rahmenbedingungen und Regulierungen rund um KI,gavel
Datenkompetenz,"Fähigkeit, Daten kritisch zu beurteilen, aufzubereiten und für KI nutzbar zu machen",equalizer
Python-Programmierung,Grundlegende Programmier-kenntnisse zur Umsetzung und Anpassung von KI-Lösungen,data_object
Software Design,"Gestaltung robuster, skalierbarer und wartbarer Softwarelösungen mit KI-Komponenten",code
Maschinelles Lernen,Kenntnisse in maschinellem Lernen zur Entwicklung datengetriebener Modelle,model_training
MLOps / Infrastruktur,Fähigkeiten zum produktiven Einsatz und Betrieb von KI-Systemen in Unternehmen,all_inclusive
GenAI-Kenntnisse,Verständnis generativer KI-Modelle (z.B. Large Language Models) und ihrer praktischen Nutzung,auto_awesome`;
export function fetchCategoryMetadata(): Promise<CategoryMetadata[]> {
const parseCsv = (csv: string): CategoryMetadata[] => {
const parsed = d3.csvParse(csv);
return parsed.map((row) => ({
category: row.title,
description: row.text,
icon: row.icon,
}));
};
/*return fetch(categoryMetadataUrl)
.then((response) => response.text())
.then(parseCsv);*/
return Promise.resolve(parseCsv(sampleMetadataCsv));
}

View File

@@ -1,7 +1,7 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./index.css";
import "./styles/index.scss";
createRoot(document.getElementById("root")!).render(
<StrictMode>

View File

@@ -1,3 +1,5 @@
@use "shared" as *;
.legend {
.box {
display: inline-block;
@@ -6,7 +8,9 @@
border-radius: 4px;
margin-right: 8px;
vertical-align: middle;
@include shadow;
}
ul {
list-style: none;
margin: 16px;

View File

@@ -1,11 +1,12 @@
@use "shared" as *;
.qr-code-container {
background-color: hsl(18.6deg 100% 55.1%);
@include rounded;
background-color: $aai-orange;
flex-basis: 20%;
flex-shrink: 0;
padding: 16px;
border-radius: 16px;
filter: drop-shadow(0 8px 8px hsl(202.5deg 20% 76.5%));
color: #fff;

50
src/styles/_shared.scss Normal file
View File

@@ -0,0 +1,50 @@
$aai-blue-dark: hsl(198.5deg 83.5% 19%);
$aai-blue-light: hsl(196.8deg 63% 55.5%);
$aai-green-dark: hsl(181deg 75% 37%);
$aai-green-light: hsl(127.5deg 100% 87.5%);
$aai-grey: hsl(202.5deg 20% 76.5%);
$aai-orange: hsl(18.6deg 100% 55.1%);
@mixin rounded {
border-radius: 16px;
}
@mixin shadow {
filter: drop-shadow(0 16px 16px rgb($aai-grey, 80%));
}
@mixin no-shadow {
filter: none;
}
$border-radius: 16px;
@mixin button {
@include shadow;
background-color: $aai-action-button;
color: #ffffff;
border-radius: $border-radius;
cursor: pointer;
display: flex;
align-items: center;
overflow: hidden;
&:active {
@include no-shadow;
}
span {
flex: 1 auto;
white-space: nowrap;
text-align: center;
font-weight: bold;
text-transform: uppercase;
font-size: 16px;
}
}

View File

@@ -1,3 +1,5 @@
@use "shared" as *;
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
@@ -32,11 +34,11 @@ h6 {
a {
font-weight: 500;
color: #646cff;
color: $aai-blue-dark;
text-decoration: inherit;
}
a:hover {
color: #747bff;
color: $aai-blue-light;
}
body {
@@ -56,13 +58,13 @@ body {
}
.chart-container {
@include rounded;
@include shadow;
display: flex;
flex-direction: column;
background-color: #ffffff;
border-radius: 16px;
filter: drop-shadow(0 8px 8px hsl(202.5deg 20% 76.5%));
}
.charts {
@@ -73,12 +75,14 @@ body {
}
.question-group {
@include rounded;
@include shadow;
background-color: #ffffff;
padding: 16px;
border: 1px solid #ddd;
border-radius: 8px;
filter: drop-shadow(0 2px 2px hsl(202.5deg 20% 76.5%));
flex: 1 1 300px;
display: flex;
flex-direction: column;
@@ -95,7 +99,6 @@ body {
font-size: 14px;
color: #666;
line-height: 1.4;
max-width: 40ch;
}
.question-title {
@@ -103,6 +106,8 @@ body {
font-weight: bold;
color: #333;
}
.question-title::before {
content: "💻 ";
.question-title .material-icons {
vertical-align: top;
margin-right: 1ex;
}