2026-03-16 13:35:11 +01:00
import { useState , useEffect , useRef } from 'react'
import { Pencil , Trash2 , Check , X , Camera , BookOpen } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import { api } from '../utils/api'
import { CIRCUMFERENCE _POINTS } from '../utils/guideData'
2026-03-21 07:14:34 +01:00
import UsageBadge from '../components/UsageBadge'
2026-03-16 13:35:11 +01:00
import dayjs from 'dayjs'
const FIELDS = [ 'c_neck' , 'c_chest' , 'c_waist' , 'c_belly' , 'c_hip' , 'c_thigh' , 'c_calf' , 'c_arm' ]
const LABELS = { c _neck : 'Hals' , c _chest : 'Brust' , c _waist : 'Taille' , c _belly : 'Bauch' , c _hip : 'Hüfte' , c _thigh : 'Oberschenkel' , c _calf : 'Wade' , c _arm : 'Oberarm' }
function empty ( ) { return { date : dayjs ( ) . format ( 'YYYY-MM-DD' ) , c _neck : '' , c _chest : '' , c _waist : '' , c _belly : '' , c _hip : '' , c _thigh : '' , c _calf : '' , c _arm : '' , notes : '' , photo _id : '' } }
export default function CircumScreen ( ) {
const [ entries , setEntries ] = useState ( [ ] )
const [ form , setForm ] = useState ( empty ( ) )
const [ editing , setEditing ] = useState ( null )
const [ saving , setSaving ] = useState ( false )
const [ saved , setSaved ] = useState ( false )
2026-03-21 07:14:34 +01:00
const [ error , setError ] = useState ( null )
2026-03-16 13:35:11 +01:00
const [ photoFile , setPhotoFile ] = useState ( null )
const [ photoPreview , setPhotoPreview ] = useState ( null )
2026-03-21 07:14:34 +01:00
const [ circumUsage , setCircumUsage ] = useState ( null ) // Phase 4: Usage badge
2026-03-16 13:35:11 +01:00
const fileRef = useRef ( )
const nav = useNavigate ( )
const load = ( ) => api . listCirc ( ) . then ( setEntries )
2026-03-21 07:14:34 +01:00
const loadUsage = ( ) => {
api . getFeatureUsage ( ) . then ( features => {
const circumFeature = features . find ( f => f . feature _id === 'circumference_entries' )
setCircumUsage ( circumFeature )
} ) . catch ( err => console . error ( 'Failed to load usage:' , err ) )
}
useEffect ( ( ) => {
load ( )
loadUsage ( )
} , [ ] )
2026-03-16 13:35:11 +01:00
const set = ( k , v ) => setForm ( f => ( { ... f , [ k ] : v } ) )
const handleSave = async ( ) => {
setSaving ( true )
2026-03-21 07:14:34 +01:00
setError ( null )
2026-03-16 13:35:11 +01:00
try {
const payload = { }
payload . date = form . date
FIELDS . forEach ( k => { if ( form [ k ] !== '' ) payload [ k ] = parseFloat ( form [ k ] ) } )
if ( form . notes ) payload . notes = form . notes
if ( photoFile ) {
const pr = await api . uploadPhoto ( photoFile , form . date )
payload . photo _id = pr . id
}
await api . upsertCirc ( payload )
2026-03-21 07:14:34 +01:00
setSaved ( true )
await load ( )
await loadUsage ( ) // Reload usage after save
2026-03-16 13:35:11 +01:00
setTimeout ( ( ) => setSaved ( false ) , 2000 )
setForm ( empty ( ) ) ; setPhotoFile ( null ) ; setPhotoPreview ( null )
2026-03-21 07:14:34 +01:00
} catch ( err ) {
console . error ( 'Save failed:' , err )
setError ( err . message || 'Fehler beim Speichern' )
setTimeout ( ( ) => setError ( null ) , 5000 )
} finally {
setSaving ( false )
}
2026-03-16 13:35:11 +01:00
}
const startEdit = ( e ) => setEditing ( { ... e } )
const cancelEdit = ( ) => setEditing ( null )
const handleUpdate = async ( ) => {
const payload = { }
payload . date = editing . date
FIELDS . forEach ( k => { if ( editing [ k ] != null && editing [ k ] !== '' ) payload [ k ] = parseFloat ( editing [ k ] ) } )
if ( editing . notes ) payload . notes = editing . notes
await api . updateCirc ( editing . id , payload )
setEditing ( null ) ; await load ( )
}
const handleDelete = async ( id ) => {
if ( ! confirm ( 'Eintrag löschen?' ) ) return
await api . deleteCirc ( id ) ; await load ( )
}
return (
< div >
< div style = { { display : 'flex' , alignItems : 'center' , justifyContent : 'space-between' , marginBottom : 16 } } >
< h1 className = "page-title" style = { { margin : 0 } } > Umfänge < / h1 >
< button className = "btn btn-secondary" style = { { fontSize : 12 , padding : '6px 10px' } } onClick = { ( ) => nav ( '/guide' ) } >
< BookOpen size = { 13 } / > Anleitung
< / button >
< / div >
{ /* Eingabe */ }
< div className = "card section-gap" >
2026-03-21 07:14:34 +01:00
< div className = "card-title badge-container-right" >
< span > Neue Messung < / span >
{ circumUsage && < UsageBadge { ...circumUsage } / > }
< / div >
2026-03-16 13:35:11 +01:00
< div className = "form-row" >
< label className = "form-label" > Datum < / label >
< input type = "date" className = "form-input" style = { { width : 140 } } value = { form . date } onChange = { e => set ( 'date' , e . target . value ) } / >
< span className = "form-unit" / >
< / div >
{ CIRCUMFERENCE _POINTS . map ( p => (
< div key = { p . id } className = "form-row" >
< label className = "form-label" title = { p . where } > { p . label } < / label >
< input type = "number" className = "form-input" min = { 10 } max = { 200 } step = { 0.1 }
placeholder = "– " value = { form [ p . id ] || '' } onChange = { e => set ( p . id , e . target . value ) } / >
< span className = "form-unit" > cm < / span >
< / div >
) ) }
< div className = "form-row" >
< label className = "form-label" > Notiz < / label >
< input type = "text" className = "form-input" placeholder = "optional"
value = { form . notes } onChange = { e => set ( 'notes' , e . target . value ) } / >
< span className = "form-unit" / >
< / div >
{ /* Photo */ }
< input ref = { fileRef } type = "file" accept = "image/*" capture = "environment" style = { { display : 'none' } }
onChange = { e => { const f = e . target . files [ 0 ] ; if ( f ) { setPhotoFile ( f ) ; setPhotoPreview ( URL . createObjectURL ( f ) ) } } } / >
{ photoPreview && < img src = { photoPreview } style = { { width : '100%' , borderRadius : 8 , marginBottom : 8 } } alt = "preview" / > }
< button className = "btn btn-secondary btn-full" onClick = { ( ) => fileRef . current . click ( ) } >
< Camera size = { 14 } / > { photoPreview ? 'Foto ändern' : 'Foto hinzufügen' }
< / button >
2026-03-21 07:14:34 +01:00
{ error && (
< div style = { { padding : '10px' , background : 'var(--danger-bg)' , border : '1px solid var(--danger)' , borderRadius : 8 , fontSize : 13 , color : 'var(--danger)' , marginTop : 8 } } >
{ error }
< / div >
) }
2026-03-21 07:25:47 +01:00
< div
2026-03-21 07:14:34 +01:00
title = { circumUsage && ! circumUsage . allowed ? ` Limit erreicht ( ${ circumUsage . used } / ${ circumUsage . limit } ). Kontaktiere den Admin oder warte bis zum nächsten Reset. ` : '' }
2026-03-21 07:25:47 +01:00
style = { { display : 'inline-block' , width : '100%' , marginTop : 8 } }
2026-03-21 07:14:34 +01:00
>
2026-03-21 07:25:47 +01:00
< button
className = "btn btn-primary btn-full"
style = { { cursor : ( circumUsage && ! circumUsage . allowed ) ? 'not-allowed' : 'pointer' } }
onClick = { handleSave }
disabled = { saving || ( circumUsage && ! circumUsage . allowed ) }
>
{ saved ? < > < Check size = { 14 } / > Gespeichert ! < / >
: saving ? '…'
: ( circumUsage && ! circumUsage . allowed ) ? '🔒 Limit erreicht'
: 'Speichern' }
< / button >
< / div >
2026-03-16 13:35:11 +01:00
< / div >
{ /* Liste */ }
< div className = "section-gap" >
< div className = "card-title" style = { { marginBottom : 8 } } > Verlauf ( { entries . length } ) < / div >
{ entries . length === 0 && < p className = "muted" > Noch keine Einträge . < / p > }
{ entries . map ( ( e , i ) => {
const prev = entries [ i + 1 ]
const isEd = editing ? . id === e . id
return (
< div key = { e . id } className = "card" style = { { marginBottom : 8 } } >
{ isEd ? (
< div >
< div className = "form-row" >
< label className = "form-label" > Datum < / label >
< input type = "date" className = "form-input" style = { { width : 140 } }
value = { editing . date } onChange = { ev => setEditing ( d => ( { ... d , date : ev . target . value } ) ) } / >
< span className = "form-unit" / >
< / div >
{ CIRCUMFERENCE _POINTS . map ( p => (
< div key = { p . id } className = "form-row" >
< label className = "form-label" > { p . label } < / label >
< input type = "number" className = "form-input" step = { 0.1 } placeholder = "– "
value = { editing [ p . id ] || '' } onChange = { ev => setEditing ( d => ( { ... d , [ p . id ] : ev . target . value } ) ) } / >
< span className = "form-unit" > cm < / span >
< / div >
) ) }
< div style = { { display : 'flex' , gap : 6 , marginTop : 8 } } >
< button className = "btn btn-primary" style = { { flex : 1 } } onClick = { handleUpdate } > < Check size = { 13 } / > Speichern < / button >
< button className = "btn btn-secondary" style = { { flex : 1 } } onClick = { cancelEdit } > < X size = { 13 } / > Abbrechen < / button >
< / div >
< / div >
) : (
< div >
< div style = { { display : 'flex' , justifyContent : 'space-between' , alignItems : 'center' , marginBottom : 8 } } >
< div style = { { fontWeight : 600 , fontSize : 14 } } > { dayjs ( e . date ) . format ( 'DD. MMMM YYYY' ) } < / div >
< div style = { { display : 'flex' , gap : 6 } } >
< button className = "btn btn-secondary" style = { { padding : '5px 8px' } } onClick = { ( ) => startEdit ( e ) } > < Pencil size = { 13 } / > < / button >
< button className = "btn btn-danger" style = { { padding : '5px 8px' } } onClick = { ( ) => handleDelete ( e . id ) } > < Trash2 size = { 13 } / > < / button >
< / div >
< / div >
< div style = { { display : 'grid' , gridTemplateColumns : '1fr 1fr' , gap : '3px 12px' } } >
{ FIELDS . map ( k => e [ k ] != null ? (
< div key = { k } style = { { display : 'flex' , justifyContent : 'space-between' , fontSize : 13 , padding : '2px 0' , borderBottom : '1px solid var(--border)' } } >
< span style = { { color : 'var(--text3)' } } > { LABELS [ k ] } < / span >
< span > { e [ k ] } cm { prev ? . [ k ] != null && < span style = { { fontSize : 11 , color : e [ k ] < prev [ k ] ? ' var ( - - accent ) ' : e [ k ] > prev [ k ] ? 'var(--warn)' : 'var(--text3)' , marginLeft : 4 } } >
{ e [ k ] - prev [ k ] > 0 ? '+' : '' } { Math . round ( ( e [ k ] - prev [ k ] ) * 10 ) / 10 }
< / span > } < / span >
< / div >
) : null ) }
< / div >
{ e . notes && < p style = { { fontSize : 12 , color : 'var(--text2)' , fontStyle : 'italic' , marginTop : 6 } } > "{e.notes}" < / p > }
< / div >
) }
< / div >
)
} ) }
< / div >
< / div >
)
}