Migrate all repos into monorepo context folders

Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
This commit is contained in:
2026-06-30 20:39:52 +02:00
parent 2f2b295531
commit a5f8fb49ab
1717 changed files with 447332 additions and 0 deletions
@@ -0,0 +1,76 @@
import { useState, useEffect } from 'react';
interface JurorProgress {
completed: number;
total: number;
}
/**
* Displays juror voting progress (X/11 teams completed per juror).
* Fetches from GET /api/progress. No individual scores shown.
*/
export default function ProgressMatrix() {
const [progress, setProgress] = useState<Record<string, JurorProgress>>({});
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchProgress();
const interval = setInterval(fetchProgress, 5000);
return () => clearInterval(interval);
}, []);
function fetchProgress() {
fetch('/api/progress')
.then((res) => res.json())
.then((data) => {
setProgress(data);
setLoading(false);
})
.catch(() => setLoading(false));
}
if (loading) {
return <div className="progress-loading">Lade Fortschritt...</div>;
}
const entries = Object.entries(progress);
const allComplete = entries.length > 0 && entries.every(([, p]) => p.completed === p.total);
return (
<div className="progress-matrix">
<h3 className="progress-title">Fortschritt</h3>
{allComplete && (
<div className="progress-complete-badge">
Alle Bewertungen abgeschlossen
</div>
)}
<div className="progress-list">
{entries.map(([jurorId, { completed, total }]) => {
const percent = total > 0 ? (completed / total) * 100 : 0;
const isComplete = completed === total;
return (
<div key={jurorId} className="progress-item">
<div className="progress-item-header">
<span className="progress-juror-name">
{jurorId.charAt(0).toUpperCase() + jurorId.slice(1)}
</span>
<span className={`progress-count ${isComplete ? 'complete' : ''}`}>
{completed}/{total}
</span>
</div>
<div className="progress-bar-track">
<div
className={`progress-bar-fill ${isComplete ? 'complete' : ''}`}
style={{ width: `${percent}%` }}
/>
</div>
</div>
);
})}
</div>
</div>
);
}
@@ -0,0 +1,69 @@
import { TeamResult } from '../../shared/types';
interface RankingTableProps {
results: TeamResult[];
}
/**
* Beamer-optimized ranking table with animated transitions.
* Displays ranked teams with scores, highlights top 3.
*/
export default function RankingTable({ results }: RankingTableProps) {
if (results.length === 0) {
return (
<div className="ranking-empty">
<p>Noch keine Bewertungen eingegangen.</p>
</div>
);
}
const getRankStyle = (rank: number): string => {
if (rank === 1) return 'rank-gold';
if (rank === 2) return 'rank-silver';
if (rank === 3) return 'rank-bronze';
return '';
};
const getRankLabel = (rank: number): string => {
if (rank === 1) return '🥇';
if (rank === 2) return '🥈';
if (rank === 3) return '🥉';
return `${rank}`;
};
return (
<div className="ranking-table">
<div className="ranking-header">
<span className="ranking-col-rank">Platz</span>
<span className="ranking-col-team">Team</span>
<span className="ranking-col-total">Gesamt</span>
<span className="ranking-col-avg">Ø</span>
</div>
<div className="ranking-rows">
{results.map((result, index) => (
<div
key={result.teamId}
className={`ranking-row glass-panel ${getRankStyle(result.rank)}`}
style={{
order: index,
transition: 'transform 0.6s ease, opacity 0.4s ease',
}}
>
<span className="ranking-col-rank rank-number">
{getRankLabel(result.rank)}
</span>
<span className="ranking-col-team team-name">
{result.teamName}
</span>
<span className="ranking-col-total total-score">
{result.total}
</span>
<span className="ranking-col-avg avg-score">
{result.average.toFixed(1)}
</span>
</div>
))}
</div>
</div>
);
}
@@ -0,0 +1,77 @@
import { useEffect } from 'react';
import { TeamResult } from '../../shared/types';
interface TeamDetailModalProps {
team: TeamResult;
scaleMax: number;
onClose: () => void;
}
/**
* Modal overlay showing per-category bar chart for a selected team.
*/
export default function TeamDetailModal({ team, scaleMax, onClose }: TeamDetailModalProps) {
// Close on Escape key
useEffect(() => {
const handleKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
document.addEventListener('keydown', handleKey);
return () => document.removeEventListener('keydown', handleKey);
}, [onClose]);
const stats = team.criteriaStats ?? [];
return (
<div className="modal-overlay" onClick={onClose} role="dialog" aria-modal="true" aria-label={`Ergebnisse für ${team.teamName}`}>
<div className="modal-content glass-panel" onClick={(e) => e.stopPropagation()}>
<header className="modal-header">
<h2 className="modal-title">{team.teamName}</h2>
<button className="modal-close" onClick={onClose} aria-label="Schließen"></button>
</header>
<div className="modal-summary">
<span className="modal-stat">
<span className="modal-stat-label">Gesamt</span>
<span className="modal-stat-value">{team.total}</span>
</span>
<span className="modal-stat">
<span className="modal-stat-label">Ø</span>
<span className="modal-stat-value">{team.average.toFixed(1)}</span>
</span>
<span className="modal-stat">
<span className="modal-stat-label">σ</span>
<span className="modal-stat-value">±{team.stddev.toFixed(1)}</span>
</span>
<span className="modal-stat">
<span className="modal-stat-label">Juroren</span>
<span className="modal-stat-value">{team.jurorCount}</span>
</span>
</div>
{stats.length > 0 ? (
<div className="modal-chart">
{stats.map((stat) => (
<div key={stat.criterionId} className="chart-row">
<span className="chart-label">{stat.criterionName}</span>
<div className="chart-bar-container">
<div
className="chart-bar"
style={{ width: `${(stat.mean / scaleMax) * 100}%` }}
>
<span className="chart-bar-value">{stat.mean.toFixed(1)}</span>
</div>
{stat.stddev > 0 && (
<span className="chart-stddev">±{stat.stddev.toFixed(1)}</span>
)}
</div>
</div>
))}
</div>
) : (
<p className="modal-empty">Noch keine Bewertungen für dieses Team.</p>
)}
</div>
</div>
);
}