Erste Version - Universal CSV Importer für EAV und activity_log #85

Merged
Lars merged 17 commits from develop into main 2026-04-15 11:46:31 +02:00
Showing only changes of commit 58ddde6b1e - Show all commits

View File

@ -38,6 +38,29 @@ function dedupeActivitiesById(rows) {
return [...m.values()].sort(compareActivities)
}
/** activity_log: Spalten start_time / end_time sind TIME (Uhrzeit zum Kalendertag date), nicht volles Timestamp. */
function timeInputValueFromApi(t) {
if (t == null || t === '') return ''
const s = String(t)
if (s.includes('T') && s.length >= 16) return s.slice(11, 16)
const m = s.match(/^(\d{1,2}):(\d{2})/)
if (!m) return ''
return `${m[1].padStart(2, '0')}:${m[2]}`
}
function timePayloadFromInput(v) {
const s = v == null ? '' : String(v).trim()
if (!s) return null
if (/^\d{2}:\d{2}$/.test(s)) return `${s}:00`
if (/^\d{2}:\d{2}:\d{2}$/.test(s)) return s
return s
}
function formatTimeForList(t) {
const v = timeInputValueFromApi(t)
return v || ''
}
const ACTIVITY_TYPES = [
'Traditionelles Krafttraining','Matrial Arts','Outdoor Spaziergang',
'Innenräume Spaziergang','Laufen','Radfahren','Schwimmen',
@ -76,6 +99,8 @@ const ACTIVITY_LOG_PAYLOAD_KEYS = new Set([
function empty() {
return {
date: dayjs().format('YYYY-MM-DD'),
start_time: '',
end_time: '',
activity_type: 'Traditionelles Krafttraining',
duration_min: '', kcal_active: '',
hr_avg: '', hr_max: '', rpe: '', notes: '',
@ -282,6 +307,30 @@ function EntryForm({
<input type="date" className="form-input" style={{width:140}} value={form.date} onChange={e=>set('date',e.target.value)}/>
<span className="form-unit"/>
</div>
<div className="form-row">
<label className="form-label">Start (Uhrzeit)</label>
<input
type="time"
step={1}
className="form-input"
style={{ width: 'auto', minWidth: 140 }}
value={timeInputValueFromApi(form.start_time)}
onChange={(e) => set('start_time', e.target.value ? timePayloadFromInput(e.target.value) || '' : '')}
/>
<span className="form-unit">zum Datum oben</span>
</div>
<div className="form-row">
<label className="form-label">Ende (Uhrzeit)</label>
<input
type="time"
step={1}
className="form-input"
style={{ width: 'auto', minWidth: 140 }}
value={timeInputValueFromApi(form.end_time)}
onChange={(e) => set('end_time', e.target.value ? timePayloadFromInput(e.target.value) || '' : '')}
/>
<span className="form-unit">optional</span>
</div>
<div style={{marginBottom:12}}>
<TrainingTypeSelect
value={form.training_type_id}
@ -502,6 +551,14 @@ export default function ActivityPage() {
setError(null)
try {
const payload = {...form}
payload.start_time =
payload.start_time === '' || payload.start_time == null
? null
: timePayloadFromInput(payload.start_time)
payload.end_time =
payload.end_time === '' || payload.end_time == null
? null
: timePayloadFromInput(payload.end_time)
if(payload.duration_min) payload.duration_min = parseFloat(payload.duration_min)
if(payload.kcal_active) payload.kcal_active = parseFloat(payload.kcal_active)
if(payload.hr_avg) payload.hr_avg = parseFloat(payload.hr_avg)
@ -557,6 +614,14 @@ export default function ActivityPage() {
if (payload.hr_avg !== '' && payload.hr_avg != null) payload.hr_avg = parseFloat(payload.hr_avg)
if (payload.hr_max !== '' && payload.hr_max != null) payload.hr_max = parseFloat(payload.hr_max)
if (payload.rpe !== '' && payload.rpe != null) payload.rpe = parseInt(payload.rpe, 10)
payload.start_time =
payload.start_time === '' || payload.start_time == null
? null
: timePayloadFromInput(payload.start_time)
payload.end_time =
payload.end_time === '' || payload.end_time == null
? null
: timePayloadFromInput(payload.end_time)
await api.updateActivity(editing.id, payload)
if (sessionDetail?.schema?.length > 0) {
const metrics = buildMetricsPayload(sessionDetail.schema, metricDraft)
@ -837,7 +902,12 @@ export default function ActivityPage() {
</div>
<div style={{fontSize:11,color:'var(--text3)',marginBottom:4}}>
{dayjs(e.date).format('dd, DD. MMMM YYYY')}
{e.start_time && e.start_time.length>10 && ` · ${e.start_time.slice(11,16)}`}
{(formatTimeForList(e.start_time) || formatTimeForList(e.end_time)) && (
<span>
{formatTimeForList(e.start_time) && ` · Start ${formatTimeForList(e.start_time)}`}
{formatTimeForList(e.end_time) && ` · Ende ${formatTimeForList(e.end_time)}`}
</span>
)}
</div>
<div style={{display:'flex',gap:10,flexWrap:'wrap'}}>
{e.duration_min && <span style={{fontSize:12,color:'var(--text2)'}}> {Math.round(e.duration_min)} Min</span>}