Compare commits
4 Commits
71e5131aa1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3af9f42e66 | ||
|
|
7fb60ad2ed | ||
|
|
5151fd8b73 | ||
|
|
176f68e32d |
14
src/App.tsx
14
src/App.tsx
@@ -8,7 +8,8 @@ import Legend from "./components/Legend";
|
|||||||
import QRCode from "./components/QRCode";
|
import QRCode from "./components/QRCode";
|
||||||
import { WaffleChart } from "./components/WaffleChart";
|
import { WaffleChart } from "./components/WaffleChart";
|
||||||
|
|
||||||
import { fetchGoogleSheet } from "./lib/parser";
|
import { config } from "./config";
|
||||||
|
import { getSampleData } from "./lib/data";
|
||||||
import "./styles/App.scss";
|
import "./styles/App.scss";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -18,9 +19,9 @@ function App() {
|
|||||||
});
|
});
|
||||||
const responseQuery = useQuery({
|
const responseQuery = useQuery({
|
||||||
queryKey: ["responses"],
|
queryKey: ["responses"],
|
||||||
queryFn: fetchGoogleSheet,
|
// queryFn: fetchGoogleSheet,
|
||||||
// queryFn: getSampleData,
|
queryFn: getSampleData,
|
||||||
refetchInterval: 5 * 1000, // Refresh every 5 seconds
|
refetchInterval: config.refreshIntervalSeconds * 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (metadataQuery.isPending || responseQuery.isPending)
|
if (metadataQuery.isPending || responseQuery.isPending)
|
||||||
@@ -29,19 +30,16 @@ function App() {
|
|||||||
return <div>Error loading data</div>;
|
return <div>Error loading data</div>;
|
||||||
|
|
||||||
// Sort responses by timestamp to easily find the latest response
|
// Sort responses by timestamp to easily find the latest response
|
||||||
const responses = responseQuery.data.sort(
|
const responses = [...responseQuery.data].sort(
|
||||||
(a, b) => a.timestamp - b.timestamp
|
(a, b) => a.timestamp - b.timestamp
|
||||||
);
|
);
|
||||||
const categoryMetadata = metadataQuery.data;
|
const categoryMetadata = metadataQuery.data;
|
||||||
|
|
||||||
if (!responses.length) return null;
|
|
||||||
|
|
||||||
// Group data by question (outside the component)
|
// Group data by question (outside the component)
|
||||||
const questionGroups = Array.from(
|
const questionGroups = Array.from(
|
||||||
d3.group(responses, (d) => d.question).entries()
|
d3.group(responses, (d) => d.question).entries()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create scales
|
|
||||||
return (
|
return (
|
||||||
<div className="layout">
|
<div className="layout">
|
||||||
<div className="chart-container">
|
<div className="chart-container">
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
import { colorScheme } from "../config";
|
import { colorScheme } from "../config";
|
||||||
|
import { skills } from "../lib/parser";
|
||||||
import "../styles/Legend.scss";
|
import "../styles/Legend.scss";
|
||||||
|
|
||||||
const labels = {
|
|
||||||
0: "Keine Erfahrung",
|
|
||||||
1: "Grundkenntnisse",
|
|
||||||
2: "Geübte Anwendung",
|
|
||||||
3: "Sichere Praxisanwendung",
|
|
||||||
4: "Fachwissen und Erfahrung",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Legend() {
|
export default function Legend() {
|
||||||
return (
|
return (
|
||||||
<div className="legend">
|
<div className="legend">
|
||||||
<ul>
|
<ul>
|
||||||
{Object.entries(labels).map(([level, label]) => (
|
{Object.entries(skills).map(([label, level]) => (
|
||||||
<li key={level}>
|
<li key={level}>
|
||||||
<span
|
<span
|
||||||
className="box"
|
className="box"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const config = {
|
|||||||
chartHeight: 50,
|
chartHeight: 50,
|
||||||
dotShape: "rect", // "circle" or "rect"
|
dotShape: "rect", // "circle" or "rect"
|
||||||
renderXAxis: true, // Whether to render the x-axis
|
renderXAxis: true, // Whether to render the x-axis
|
||||||
|
refreshIntervalSeconds: 1, // Refresh interval for response data in seconds
|
||||||
};
|
};
|
||||||
|
|
||||||
// Color scheme for Likert scale responses
|
// Color scheme for Likert scale responses
|
||||||
@@ -27,3 +28,6 @@ export const colorScheme = Object.fromEntries(
|
|||||||
|
|
||||||
export const categoryMetadataUrl =
|
export const categoryMetadataUrl =
|
||||||
"https://docs.google.com/spreadsheets/d/e/2PACX-1vT6FQoV_8ET_pmEB5LGlI_ST9AAhsfiZrWydFwIB80G0Lr_kGwVJUzjM6fRPP9Yrx6iVZYMVAPTnLKq/pub?gid=0&single=true&output=csv";
|
"https://docs.google.com/spreadsheets/d/e/2PACX-1vT6FQoV_8ET_pmEB5LGlI_ST9AAhsfiZrWydFwIB80G0Lr_kGwVJUzjM6fRPP9Yrx6iVZYMVAPTnLKq/pub?gid=0&single=true&output=csv";
|
||||||
|
|
||||||
|
export const responsesSheetId = "12pGfvJx0SQmb6mnnVygmZsEeLZ6bFrpZvq8GYw2oX9E";
|
||||||
|
export const responsesSheetName = "Responses";
|
||||||
|
|||||||
@@ -1,31 +1,26 @@
|
|||||||
|
import { fetchCategoryMetadata } from "./metadata";
|
||||||
import { ResponseData } from "./parser";
|
import { ResponseData } from "./parser";
|
||||||
|
|
||||||
export function getSampleData(): Promise<ResponseData[]> {
|
function randInt(min: number, max: number): number {
|
||||||
const questions = [
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
"Service Quality",
|
}
|
||||||
"Value for Money",
|
|
||||||
"Ease of Use",
|
export async function getSampleData(): Promise<ResponseData[]> {
|
||||||
"Recommendation",
|
// Use the actual categories
|
||||||
"Overall Satisfaction",
|
const questions = (await fetchCategoryMetadata()).map(
|
||||||
"Customer Support",
|
(metadata) => metadata.category
|
||||||
"Product Features",
|
);
|
||||||
];
|
|
||||||
const sampleData: ResponseData[] = [];
|
const sampleData: ResponseData[] = [];
|
||||||
let id = 1;
|
const numResponses = randInt(10, 20);
|
||||||
questions.forEach((question) => {
|
|
||||||
const numResponses = Math.floor(Math.random() * 50) + 30;
|
|
||||||
for (let i = 0; i < numResponses; i++) {
|
for (let i = 0; i < numResponses; i++) {
|
||||||
const response = Math.floor(Math.random() * 5);
|
questions.forEach((question) => {
|
||||||
|
const response = randInt(0, 4); // Likert scale response (0-4)
|
||||||
sampleData.push({
|
sampleData.push({
|
||||||
timestamp: id++,
|
timestamp: i, // Group all responses by the same timestamp to mimic Google Forms behavior
|
||||||
position: "",
|
|
||||||
question: question,
|
question: question,
|
||||||
response: response,
|
response: response,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
return Promise.resolve(sampleData);
|
||||||
// Simulate a delay to mimic fetching actual data
|
|
||||||
return new Promise<ResponseData[]>((resolve) => {
|
|
||||||
setTimeout(() => resolve(sampleData), 500);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export interface CategoryMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sampleMetadataCsv = `title,text,icon
|
const sampleMetadataCsv = `title,text,icon
|
||||||
Allgemeines KI-Wissen,Grundlegendes Wissen über Künstliche Intelligenz und deren Anwendung in Organisationen,school
|
Generelles 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-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
|
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
|
Stakeholder-Landschaft,"Fähigkeit, relevante Stakeholder für KI-Initiativen zu identifizieren, einzubinden und zu koordinieren",people_alt
|
||||||
@@ -18,7 +18,7 @@ Python-Programmierung,Grundlegende Programmier-kenntnisse zur Umsetzung und Anpa
|
|||||||
Software Design,"Gestaltung robuster, skalierbarer und wartbarer Softwarelösungen mit KI-Komponenten",code
|
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
|
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
|
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`;
|
Generative KI,Verständnis generativer KI-Modelle (z. B. Large Language Models) und ihrer praktischen Nutzung,auto_awesome`;
|
||||||
|
|
||||||
export function fetchCategoryMetadata(): Promise<CategoryMetadata[]> {
|
export function fetchCategoryMetadata(): Promise<CategoryMetadata[]> {
|
||||||
const parseCsv = (csv: string): CategoryMetadata[] => {
|
const parseCsv = (csv: string): CategoryMetadata[] => {
|
||||||
|
|||||||
@@ -1,28 +1,20 @@
|
|||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
|
import { responsesSheetId, responsesSheetName } from "../config";
|
||||||
|
|
||||||
function mapSkillToNumber(skill: string): number {
|
export const skills: { [key: string]: number } = {
|
||||||
const skills: { [key: string]: number } = {
|
|
||||||
"Gar nicht qualifiziert": 0,
|
|
||||||
"Leicht qualifiziert": 1,
|
|
||||||
"Mäßig qualifiziert": 2,
|
|
||||||
"Sehr qualifiziert": 3,
|
|
||||||
"Äußerst qualifiziert": 4,
|
|
||||||
"Keine Kenntnisse": 0,
|
"Keine Kenntnisse": 0,
|
||||||
"Geringe Kenntnisse": 1,
|
"Geringe Kenntnisse": 1,
|
||||||
"Grundlegende Kenntnisse": 2,
|
"Grundlegende Kenntnisse": 2,
|
||||||
"Gute Kenntnisse": 3,
|
"Gute Kenntnisse": 3,
|
||||||
"Sehr fundierte Kenntnisse": 4,
|
"Sehr fundierte Kenntnisse": 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function mapSkillToNumber(skill: string): number {
|
||||||
return skills[skill] !== undefined ? skills[skill] : -1;
|
return skills[skill] !== undefined ? skills[skill] : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sheet_id = "12pGfvJx0SQmb6mnnVygmZsEeLZ6bFrpZvq8GYw2oX9E";
|
|
||||||
const sheet_name = "Responses";
|
|
||||||
const url = `https://docs.google.com/spreadsheets/d/${sheet_id}/gviz/tq?tqx=out:csv&sheet=${sheet_name}`;
|
|
||||||
|
|
||||||
export interface ResponseData {
|
export interface ResponseData {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
position: string;
|
|
||||||
question: string;
|
question: string;
|
||||||
response: number;
|
response: number;
|
||||||
}
|
}
|
||||||
@@ -40,7 +32,6 @@ export function parseCSV(csv: string): ResponseData[] {
|
|||||||
});
|
});
|
||||||
return Object.entries(responses).flatMap(([category, response]) => ({
|
return Object.entries(responses).flatMap(([category, response]) => ({
|
||||||
timestamp: new Date(row["Timestamp"]).getTime(),
|
timestamp: new Date(row["Timestamp"]).getTime(),
|
||||||
position: row["Position"],
|
|
||||||
question: category,
|
question: category,
|
||||||
response: response,
|
response: response,
|
||||||
}));
|
}));
|
||||||
@@ -50,6 +41,7 @@ export function parseCSV(csv: string): ResponseData[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function fetchGoogleSheet() {
|
export function fetchGoogleSheet() {
|
||||||
|
const url = `https://docs.google.com/spreadsheets/d/${responsesSheetId}/gviz/tq?tqx=out:csv&sheet=${responsesSheetName}`;
|
||||||
return fetch(url)
|
return fetch(url)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"module": "nodenext",
|
"skipLibCheck": true,
|
||||||
"moduleResolution": "nodenext",
|
"module": "ESNext",
|
||||||
"allowSyntheticDefaultImports": true
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user