Files
votianng/frontend/src/EvaluationField.tsx
T
Sven 781a2b82dc Erweitere VoTiAn NG um Fachfunktionen und verbessere die Listenoberfläche
Ergänze Auftragsbearbeitung, Disposition, Stammdaten, Kunden- und
Transporteurportale, Lagerverwaltung, Abrechnung, Exporte und Statistik.
Füge Kontoeinstellungen, Mitteilungen, Automatikdienste und Betriebsstatus
sowie die zugehörigen Tests und PHP-Abgleichsdokumentation hinzu.

Vergrößere die persönlichen Auftragslisten auf einheitliche Höhen und
richte Statusanzeigen sowie die Ranking-Vorschau-Steuerelemente bündig aus.

Validierung: 44 Frontendtests und Produktionsbuild erfolgreich.
Backend funktional unverändert gegenüber dem vollständigen Lauf mit 1.624
erfolgreichen und drei übersprungenen Tests. Layout im Browser und lokale
Bereitstellung geprüft.
2026-10-20 11:19:43 +02:00

17 lines
2.9 KiB
TypeScript

import {useUiText} from './UiLanguage';
import { useEffect, useState } from 'react';
import { Field } from './FormElements';
import { request } from './api';
type Def={id:number;label:string;type:string};type Hq={id:number;label:string};
const kinds:Record<string,string>={'button:cs:cs_eid':'customer','button:cr:cr_eid':'courier','select:crvh:crvh_sid':'vehicle','select:vht':'vehicleType','select:grp':'group','select:br':'branch'};
export function EvaluationField({field,value,headquarters,headquartersOptions,onChange}:{field:Def;value:string;headquarters:number;headquartersOptions:Hq[];onChange:(v:string)=>void}){
const uiText=useUiText();
const [query,setQuery]=useState(''),[items,setItems]=useState<{id:number;value:string|number;label:string}[]>([]),[error,setError]=useState('');const kind=kinds[field.type];
useEffect(()=>{if(!kind)return;let live=true;const timer=setTimeout(()=>request<typeof items>('/evaluations/lookup?'+new URLSearchParams({headquarters:String(headquarters),kind,q:query})).then(v=>{if(live){setItems(v);setError('');}}).catch(e=>{if(live)setError(e.message);}),250);return()=>{live=false;clearTimeout(timer);};},[query,kind,headquarters]);
if(['date_begin','date_end','date'].includes(field.type)){const match=value.match(/^(\d{2})\.(\d{2})\.(\d{4})$/),iso=match?`${match[3]}-${match[2]}-${match[1]}`:'';return <Field label={field.label}><input type="date" value={iso} onInput={e=>{const v=e.currentTarget.value;onChange(v?v.split('-').reverse().join('.'):'');}}/></Field>;}
if(field.type==='checkboxes_hq'){const selected=value.split(',').filter(Boolean);return <fieldset><legend>{field.label}</legend><div className="admin-toolbar">{headquartersOptions.map(h=><label className="admin-check" key={h.id}><input type="checkbox" checked={selected.includes(String(h.id))} onChange={e=>onChange((e.target.checked?[...selected,String(h.id)]:selected.filter(v=>v!==String(h.id))).join(','))}/>{h.label}</label>)}<button type="button" className="button" onClick={()=>onChange(headquartersOptions.map(h=>h.id).join(','))}>{uiText("Alle")}</button><button type="button" className="button" onClick={()=>onChange('')}>{uiText("Keine")}</button></div></fieldset>;}
if(!kind)return <Field label={field.label}><input readOnly value={value}/></Field>;
return <div className="entry-lookup"><Field label={field.label}><input value={value} onChange={e=>onChange(e.target.value)} placeholder="Keine Auswahl"/></Field><Field label={`${field.label} suchen`}><input type="search" value={query} onChange={e=>setQuery(e.target.value)} placeholder="Nummer oder Name"/></Field>{error&&<p role="alert" className="alert error">{error}</p>}<select aria-label={`${field.label} auswählen`} value="" onChange={e=>{const v=e.target.value;if(v)onChange(v);}}><option value="">Treffer auswählen …</option>{items.map(v=><option key={v.id} value={v.value}>{v.label}</option>)}</select><p className="form-hint">Bis zu 100 Treffer. Leeres Feld hebt die Auswahl auf.</p></div>;
}