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.
50 lines
5.4 KiB
TypeScript
50 lines
5.4 KiB
TypeScript
import {useUiText} from './UiLanguage';
|
||
import {useEffect,useState} from 'react';
|
||
import {request} from './api';
|
||
import {Modal} from './FormElements';
|
||
import type {Order} from './types';
|
||
|
||
type RunningOrder={id:number;at:string;status:number;longhaul:boolean;headquarters:string;canOpen:boolean;occupied:boolean;stations:{id:number;position:number;postalCode:string;completed:boolean}[]};
|
||
type Result={sid:string;total:number;page:number;pageSize:number;items:RunningOrder[]};
|
||
const time=(v:string|null|undefined)=>{if(!v)return '–';const date=new Date(v.replace(' ','T'));return Number.isNaN(date.getTime())?v:date.toLocaleString('de-DE',{dateStyle:'short',timeStyle:'short'});};
|
||
|
||
export function AssignmentOrders({job,vehicle,courier,sid,refresh}:{job:number;vehicle:number;courier:number;sid:string;refresh:number}){
|
||
const [open,setOpen]=useState(false),[page,setPage]=useState(1),[reload,setReload]=useState(0),[result,setResult]=useState<Result|null>(null),[error,setError]=useState(''),[loading,setLoading]=useState(false),[detail,setDetail]=useState<number|null>(null);
|
||
useEffect(()=>{setOpen(false);setPage(1);setResult(null);setDetail(null);},[job,vehicle,courier,refresh]);
|
||
useEffect(()=>{if(!open)return;const c=new AbortController();setLoading(true);setResult(null);setError('');request<Result>(`/orders/${job}/courier-assignment/vehicles/${vehicle}/orders?courierId=${courier}&page=${page}`,{signal:c.signal}).then(r=>{if(!c.signal.aborted)setResult(r);}).catch(e=>{if(!c.signal.aborted)setError(e.message);}).finally(()=>{if(!c.signal.aborted)setLoading(false);});return()=>c.abort();},[job,vehicle,courier,refresh,open,page,reload]);
|
||
return <div className="assignment-orders"><button type="button" className="button" aria-expanded={open} onClick={()=>setOpen(v=>!v)}>{open?'Aufträge ausblenden':`Aufträge von ${sid} anzeigen`}</button>
|
||
{open&&<section aria-label={`Laufende Aufträge von ${sid}`}>
|
||
{loading&&<p role="status">Aufträge und Stationsfolge werden geladen …</p>}
|
||
{error&&<p role="alert" className="alert error">{error}</p>}
|
||
<button type="button" className="button" disabled={loading} onClick={()=>setReload(v=>v+1)}>Auftragsstand aktualisieren</button>
|
||
{result&&<><p>{result.total} laufende / vorgemerkte Aufträge</p><ul className="assignment-order-list">{result.items.map(o=><li key={o.id}>
|
||
<div>{o.canOpen?<button type="button" className="button" onClick={()=>setDetail(o.id)}>Auftrag #{o.id} öffnen</button>:<strong>Auftrag #{o.id}</strong>} {o.longhaul?'BWV · ':''}{o.headquarters}</div>
|
||
<div>{time(o.at)} · {o.status===0?'Angeboten':'Angenommen'} · {o.occupied?'Belegt im Vorlauffenster':'Außerhalb des Vorlauffensters'}</div>
|
||
<ol className="assignment-route" aria-label={`Stationsfolge Auftrag ${o.id}`}>{o.stations.map(t=><li key={t.id} className={t.completed?'completed':''}><span>{t.position}. {t.postalCode||'PLZ fehlt'}</span>{t.completed&&<span className="assignment-stop-state"> ✓ erledigt</span>}</li>)}</ol>
|
||
{!o.canOpen&&<p className="form-hint">Auftrag einer anderen Niederlassung. Vollständige Details sind hier nicht freigegeben.</p>}
|
||
</li>)}</ul>{!result.total&&<p>Derzeit keine weiteren laufenden Aufträge.</p>}
|
||
{result.total>result.pageSize&&<div className="admin-row-actions"><button type="button" className="button" disabled={page<=1} onClick={()=>setPage(v=>v-1)}>Vorherige Aufträge</button><span>Seite {page} / {Math.ceil(result.total/result.pageSize)}</span><button type="button" className="button" disabled={page*result.pageSize>=result.total} onClick={()=>setPage(v=>v+1)}>Weitere Aufträge</button></div>}
|
||
</>}
|
||
</section>}{detail!==null&&<AssignmentOrderDetails id={detail} onClose={()=>setDetail(null)}/>}</div>;
|
||
}
|
||
|
||
function AssignmentOrderDetails({id,onClose}:{id:number;onClose:()=>void}){
|
||
const uiText=useUiText();
|
||
|
||
const [order,setOrder]=useState<Order|null>(null),[error,setError]=useState(''),[reload,setReload]=useState(0);
|
||
useEffect(()=>{const c=new AbortController();setOrder(null);setError('');request<Order>(`/orders/${id}`,{signal:c.signal}).then(v=>{if(!c.signal.aborted)setOrder(v);}).catch(e=>{if(!c.signal.aborted)setError(e.message);});return()=>c.abort();},[id,reload]);
|
||
return <Modal title={`Auftragsdetails #${id}`} onClose={onClose}><div className="modal-body">
|
||
{error?<p role="alert" className="alert error">{error}</p>:!order?<p role="status">Auftrag wird geladen …</p>:<>
|
||
<p><strong>{order.legacyStatusLabel||order.status}</strong> · {order.service} · {order.vehicle}</p>
|
||
<p>{order.customerName||'Kunde nicht hinterlegt'} · {time(order.pickupAt)}<br/>Referenz: {order.reference||'–'} · Kostenstelle: {order.costCenter||'–'}</p>
|
||
{!!order.notes&&<p className="pre-line">{order.notes}</p>}
|
||
<ol className="assignment-detail-stops">{order.stops.map((s,i)=><li key={s.id||i}>
|
||
<strong>{s.sequence}. {s.name}</strong> · {s.type==='PICKUP'?uiText("Abholung"):s.type==='DELIVERY'?'Zustellung':s.type==='BOTH'?'Abholung und Zustellung':'Station'}
|
||
<p>{s.street}<br/>{s.postalCode} {s.city}</p>
|
||
{(s.contact||s.phone)&&<p>{s.contact} {s.phone}</p>}{s.note&&<p className="pre-line">{s.note}</p>}
|
||
{s.completedAt&&<p>Erledigt: {time(s.completedAt)}{s.recipientName?` · ${s.recipientName}`:''}</p>}
|
||
</li>)}</ol>
|
||
</>}
|
||
</div><div className="modal-footer"><button type="button" className="button" onClick={()=>setReload(v=>v+1)}>Details aktualisieren</button><button type="button" className="button primary" onClick={onClose}>Zurück zur Kurierauswahl</button></div></Modal>;
|
||
}
|