Category metadata / icon support

This commit is contained in:
Adrian Rumpold
2025-07-10 08:33:42 +02:00
parent 461820cb70
commit a690718192
9 changed files with 1204 additions and 51 deletions

View File

@@ -2,8 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <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" /> <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.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preconnect" href="https://rsms.me/" /> <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" href="https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,100..900;1,100..900&display=swap"
rel="stylesheet" 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> </head>
<body> <body>
<div id="root"></div> <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" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.2.0",
"d3": "^7.9.0", "d3": "^7.9.0",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0" "react-dom": "^19.1.0",
"sass": "^1.89.2"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.30.1", "@eslint/js": "^9.30.1",

BIN
public/aai-favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

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

View File

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

View File

@@ -23,3 +23,6 @@ const aaiColors = [
export const colorScheme = Object.fromEntries( export const colorScheme = Object.fromEntries(
aaiColors.map((color, index) => [index, color]) 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";

View File

@@ -78,6 +78,8 @@ body {
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 8px; border-radius: 8px;
flex: 1 1 380px;
filter: drop-shadow(0 2px 2px hsl(202.5deg 20% 76.5%)); filter: drop-shadow(0 2px 2px hsl(202.5deg 20% 76.5%));
display: flex; display: flex;
@@ -95,7 +97,6 @@ body {
font-size: 14px; font-size: 14px;
color: #666; color: #666;
line-height: 1.4; line-height: 1.4;
max-width: 40ch;
} }
.question-title { .question-title {

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));
}