Erweitere VoTiAn NG um Auftrags-, Stamm- und Verwaltungsfunktionen

Ergänzt Auftragserfassung und -bearbeitung, Unternehmerbelege, Stornos, Lagerkorrekturen, Stammdaten, Abrechnung, CRM, Formulare, Berichte und Preisverwaltung auf Basis der aktuellen PHP-Anwendung.

Enthält Integrations- und Oberflächentests sowie dokumentierte Abgleiche und Prüfstände.
This commit is contained in:
Sven committed 2026-10-13 20:31:31 +02:00
1 parent 42c87282ee
commit 363bc27c26
190 files changed
+14815 -1857

No files matched your search

+13
View File
@@ -0,0 +1,13 @@
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 [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(','))}>Alle</button><button type="button" className="button" onClick={()=>onChange('')}>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>;
}