shinkan-jinkendo/frontend/src/components/ProgressionGraphListCard.jsx
Lars 48d51c07c5
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m26s
Enhance Exercise Progression Graph Panel and Editor with New Features
- Refactored `ExerciseProgressionGraphPanel` to support a create dialog for new progression graphs, improving user experience.
- Integrated `ProgressionGraphListCard` for better visualization of existing graphs and streamlined management.
- Updated `ProgressionGraphEditor` to handle start/target analysis and improved draft hydration with AI suggestions.
- Added utility functions for managing structured responses from AI, enhancing the planning process.
- Incremented application version to reflect these updates.
2026-06-10 16:17:40 +02:00

127 lines
4.5 KiB
JavaScript

import React from 'react'
import { GitBranch, Lock, Users, Globe, Pencil, Trash2 } from 'lucide-react'
import { graphGoalQueryFromRow, graphSlotCountFromRow } from '../utils/progressionGraphDraft'
import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../constants/exerciseGovernanceLabels'
const VIS_LABELS = { official: 'Global', club: 'Verein', private: 'Privat' }
function visibilityLabel(v) {
return VIS_LABELS[v] || v || '—'
}
function cardClassName(graph, userId) {
const vis = graph.visibility || 'private'
const visKey = vis === 'official' || vis === 'club' || vis === 'private' ? vis : 'private'
const mine = userId != null && Number(graph.created_by) === Number(userId)
return ['card', 'exercise-card', 'progression-graph-card', `exercise-card--scope-${visKey}`, mine ? 'exercise-card--mine' : '']
.filter(Boolean)
.join(' ')
}
function VisIcon({ visibility }) {
if (visibility === 'official') return <Globe size={14} aria-hidden="true" />
if (visibility === 'club') return <Users size={14} aria-hidden="true" />
return <Lock size={14} aria-hidden="true" />
}
export default function ProgressionGraphListCard({
graph,
userId = null,
onOpen,
onDelete,
disabled = false,
}) {
const goalQuery = graphGoalQueryFromRow(graph)
const slotCount = graphSlotCountFromRow(graph)
const edgesCount = Number(graph.edges_count) || 0
const description = (graph.description || '').trim()
return (
<article className={cardClassName(graph, userId)}>
<div className="exercise-card__body">
<div className="exercise-card-title" style={{ display: 'flex', alignItems: 'flex-start', gap: '8px' }}>
<GitBranch size={18} style={{ flexShrink: 0, marginTop: '2px', color: 'var(--accent)' }} aria-hidden="true" />
<button
type="button"
className="exercise-card__body--clickable"
style={{
border: 'none',
background: 'none',
padding: 0,
textAlign: 'left',
font: 'inherit',
color: 'inherit',
cursor: disabled ? 'default' : 'pointer',
width: '100%',
}}
disabled={disabled}
onClick={() => onOpen?.(graph)}
>
{graph.name || `Graph #${graph.id}`}
</button>
</div>
<div className="exercise-card-tags" style={{ marginTop: '8px' }}>
<span className="exercise-tag" title={EXERCISE_VISIBILITY_FIELD_LABEL}>
<VisIcon visibility={graph.visibility} />
{visibilityLabel(graph.visibility)}
</span>
{slotCount != null ? (
<span className="exercise-tag">{slotCount} Stufen</span>
) : null}
<span className="exercise-tag">
{edgesCount === 1 ? '1 Kante' : `${edgesCount} Kanten`}
</span>
</div>
{goalQuery ? (
<p className="exercise-card-summary" style={{ marginTop: '10px' }}>
<strong style={{ fontWeight: 600, color: 'var(--text2)' }}>Ziel: </strong>
{goalQuery.length > 160 ? `${goalQuery.slice(0, 160)}` : goalQuery}
</p>
) : description ? (
<p className="exercise-card-summary" style={{ marginTop: '10px' }}>
{description.length > 160 ? `${description.slice(0, 160)}` : description}
</p>
) : (
<p className="exercise-card-summary muted" style={{ marginTop: '10px' }}>
Noch kein Planungsziel hinterlegt öffnen und Roadmap anlegen.
</p>
)}
</div>
<div
className="exercise-card-layout"
style={{
padding: '10px 14px 14px',
borderTop: '1px solid var(--border)',
display: 'flex',
gap: '8px',
flexWrap: 'wrap',
}}
>
<button
type="button"
className="btn btn-primary"
style={{ fontSize: '12px', padding: '6px 12px' }}
disabled={disabled}
onClick={() => onOpen?.(graph)}
>
<Pencil size={14} style={{ marginRight: '4px', verticalAlign: '-2px' }} aria-hidden="true" />
Bearbeiten
</button>
<button
type="button"
className="btn"
style={{ fontSize: '12px', padding: '6px 12px' }}
disabled={disabled}
onClick={() => onDelete?.(graph)}
>
<Trash2 size={14} style={{ marginRight: '4px', verticalAlign: '-2px' }} aria-hidden="true" />
Löschen
</button>
</div>
</article>
)
}