shinkan-jinkendo/frontend/src/components/planning/TrainingPlanningTrainerAssignModal.jsx
Lars a1a3f2e0a1
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m8s
chore(version): update version and changelog for release 0.8.129
- Bumped APP_VERSION to 0.8.129 and updated the changelog to reflect recent changes.
- Added the TrainingPlanningTrainerAssignModal component to the TrainingPlanningPage for enhanced trainer assignment functionality.
- Implemented new callback functions for managing lead trainer and assistant assignments in the training planning process.
2026-05-14 15:32:21 +02:00

156 lines
5.1 KiB
JavaScript

import React from 'react'
/**
* Modal: organisatorische Trainer-Zuweisung (Leitung + Co) für eine bestehende Einheit.
*/
export default function TrainingPlanningTrainerAssignModal({
open,
unit,
leadTrainerProfileId,
onLeadChange,
sessionAssistantsInherit,
onSessionAssistantsInheritChange,
sessionAssistantProfileIds,
onCoTrainerToggle,
clubDirectory,
coTrainerOptions,
saving,
onBackdropRequestClose,
onCancel,
onSave,
}) {
if (!open || !unit) return null
return (
<div
data-testid="planning-trainer-assign-modal"
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1020,
padding: '1rem',
overflowY: 'auto',
}}
role="presentation"
onClick={() => {
if (!saving) onBackdropRequestClose()
}}
>
<div
role="dialog"
aria-modal="true"
aria-labelledby="trainer-assign-modal-title"
onClick={(e) => e.stopPropagation()}
style={{
background: 'var(--surface)',
borderRadius: '12px',
padding: 'clamp(14px, 3vw, 1.75rem)',
maxWidth: 'min(460px, 100%)',
width: '100%',
maxHeight: '90vh',
overflowY: 'auto',
boxSizing: 'border-box',
}}
>
<h2 id="trainer-assign-modal-title" style={{ marginBottom: '0.5rem', fontSize: '1.1rem' }}>
Trainer zuweisen (organisatorisch)
</h2>
<p style={{ fontSize: '0.86rem', color: 'var(--text2)', marginBottom: '1rem', lineHeight: 1.45 }}>
{(unit.planned_date || '').toString().slice(0, 10)}
{unit.planned_time_start ? ` · ${String(unit.planned_time_start).slice(0, 5)}` : ''}
{(unit.group_name || '').trim() ? ` · ${(unit.group_name || '').trim()}` : null}
</p>
<div className="form-row">
<label className="form-label">Leitung (diese Einheit)</label>
<select
className="form-input"
value={leadTrainerProfileId}
onChange={(e) => onLeadChange(e.target.value)}
disabled={saving}
>
<option value="">Standard (Haupttrainer der Gruppe)</option>
{(clubDirectory || []).map((m) => {
const idStr = String(m.id)
return (
<option key={idStr} value={idStr}>
{(m.name || '').trim() || m.email || `Profil ${m.id}`}
</option>
)
})}
</select>
</div>
<div className="form-row" style={{ marginTop: '0.85rem' }}>
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
<input
type="checkbox"
checked={sessionAssistantsInherit}
disabled={saving}
onChange={(e) => onSessionAssistantsInheritChange(e.target.checked)}
/>
<span style={{ fontSize: '0.9rem', color: 'var(--text1)' }}>Co-Trainer wie in der Trainingsgruppe</span>
</label>
</div>
{!sessionAssistantsInherit ? (
<div style={{ marginTop: '10px', maxHeight: '180px', overflowY: 'auto' }}>
{coTrainerOptions.map((m) => {
const mid = typeof m.id === 'number' ? m.id : parseInt(String(m.id), 10)
const labelText = `${(m.name || '').trim() || m.email || `Profil ${mid}`}`
const isOn = Number.isFinite(mid) && sessionAssistantProfileIds.includes(mid)
return (
<label
key={`assign-co-${mid}`}
style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
fontSize: '0.875rem',
marginBottom: '6px',
cursor: saving ? 'default' : 'pointer',
color: 'var(--text1)',
}}
>
<input
type="checkbox"
checked={isOn}
disabled={saving}
onChange={() => onCoTrainerToggle(mid)}
/>
<span>{labelText}</span>
</label>
)
})}
</div>
) : null}
{!clubDirectory.length ? (
<p style={{ marginTop: '10px', fontSize: '0.82rem', color: 'var(--text3)' }}>
Mitgliederverzeichnis konnte nicht geladen werden.
</p>
) : null}
<div
style={{
display: 'flex',
gap: '0.65rem',
flexWrap: 'wrap',
justifyContent: 'flex-end',
marginTop: '1.25rem',
}}
>
<button type="button" className="btn btn-secondary" disabled={saving} onClick={onCancel}>
Abbrechen
</button>
<button type="button" className="btn btn-primary" disabled={saving} onClick={onSave}>
{saving ? 'Speichern …' : 'Speichern'}
</button>
</div>
</div>
</div>
)
}