shinkan-jinkendo/frontend/src/components/ProgressionFindingsPanel.jsx

142 lines
5.0 KiB
React
Raw Normal View History

/**
* Zentrales Findings-Panel für Progressionsgraph-QA (Phase B.2).
*/
import React from 'react'
function severityStyle(pathQa) {
if (!pathQa) return {}
return {
background: pathQa.overall_ok
? 'color-mix(in srgb, var(--accent) 8%, var(--surface2))'
: 'color-mix(in srgb, var(--danger) 8%, var(--surface2))',
}
}
export default function ProgressionFindingsPanel({
pathQa = null,
gapFillOffers = [],
loading = false,
error = '',
onEvaluate,
onApplyGapOffer,
evaluateDisabled = false,
}) {
return (
<div className="card" style={{ position: 'sticky', top: '12px' }}>
<h3 style={{ marginTop: 0, fontSize: '1rem' }}>Graph-Bewertung</h3>
<p style={{ fontSize: '12px', color: 'var(--text3)', marginTop: 0, lineHeight: 1.45 }}>
Prüft den aktuellen Slot-Stand (inkl. KI-Entwürfe) ohne erneutes Übungs-Matching.
</p>
<button
type="button"
className="btn btn-primary btn-full"
disabled={loading || evaluateDisabled}
onClick={onEvaluate}
style={{ marginBottom: '12px' }}
>
{loading ? 'Bewertung läuft…' : 'Graph bewerten'}
</button>
{error ? (
<p className="form-error" style={{ marginTop: 0 }}>
{error}
</p>
) : null}
{pathQa ? (
<div
style={{
padding: '10px 12px',
borderRadius: '8px',
fontSize: '12px',
lineHeight: 1.45,
...severityStyle(pathQa),
}}
>
<strong>
Pfad-QS: {pathQa.overall_ok ? 'OK' : 'Hinweise'}
{pathQa.quality_score != null
? ` (${Math.round(Number(pathQa.quality_score) * 100)} %)`
: ''}
</strong>
{pathQa.topic_coverage ? (
<p style={{ margin: '6px 0 0', color: 'var(--text2)' }}>{pathQa.topic_coverage}</p>
) : null}
{Array.isArray(pathQa.issues) && pathQa.issues.length > 0 ? (
<ul style={{ margin: '6px 0 0', paddingLeft: '16px', color: 'var(--text2)' }}>
{pathQa.issues.map((issue) => (
<li key={issue}>{issue}</li>
))}
</ul>
) : null}
{Array.isArray(pathQa.recommendations) && pathQa.recommendations.length > 0 ? (
<>
<p style={{ margin: '8px 0 4px', fontWeight: 600, color: 'var(--text2)' }}>Empfehlungen</p>
<ul style={{ margin: 0, paddingLeft: '16px', color: 'var(--text2)' }}>
{pathQa.recommendations.map((rec) => (
<li key={rec}>{rec}</li>
))}
</ul>
</>
) : null}
{Number(pathQa.off_topic_count) > 0 ? (
<p style={{ margin: '6px 0 0', color: 'var(--danger)' }}>
{pathQa.off_topic_count} Schritt(e) ohne Bezug zum Pfad-Thema.
</p>
) : null}
{pathQa.roadmap_qa_mode === 'roadmap_first_lite' ? (
<p style={{ margin: '6px 0 0', color: 'var(--text2)' }}>
QS an Roadmap gekoppelt (keine Brücken zwischen Major Steps).
</p>
) : null}
</div>
) : (
<p style={{ fontSize: '12px', color: 'var(--text3)', margin: 0 }}>
Noch keine Bewertung. Slots befüllen und „Graph bewerten“ ausführen.
</p>
)}
{Array.isArray(gapFillOffers) && gapFillOffers.length > 0 ? (
<div style={{ marginTop: '14px' }}>
<h4 style={{ margin: '0 0 8px', fontSize: '0.9rem' }}>Lücken-Angebote</h4>
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '8px' }}>
{gapFillOffers.slice(0, 6).map((offer) => (
<li
key={offer.offer_id || offer.title_hint}
style={{
padding: '8px 10px',
borderRadius: '8px',
border: '1px solid var(--border)',
background: 'var(--surface2)',
fontSize: '12px',
}}
>
<div style={{ fontWeight: 600 }}>
{offer.title_hint || 'Übungsvorschlag'}
{offer.roadmap_major_step_index != null
? ` · Slot ${Number(offer.roadmap_major_step_index) + 1}`
: ''}
</div>
{offer.rationale ? (
<p style={{ margin: '4px 0 0', color: 'var(--text2)' }}>{offer.rationale}</p>
) : null}
{typeof onApplyGapOffer === 'function' ? (
<button
type="button"
className="btn btn-secondary"
style={{ marginTop: '6px', fontSize: '11px', padding: '4px 8px' }}
onClick={() => onApplyGapOffer(offer)}
>
Als Entwurf in Slot
</button>
) : null}
</li>
))}
</ul>
</div>
) : null}
</div>
)
}