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.
This commit is contained in:
Sven committed 2026-10-20 11:19:43 +02:00
1 parent 363bc27c26
commit 781a2b82dc
1165 files changed
+74986 -1028

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
import {useEffect,useState,type CSSProperties} from 'react';
import {request} from './api';
import {Modal} from './FormElements';
export type DispositionCard={id:number;lines:string[];details:string[];zipcode:string;verticalId:boolean;colour:string;category:string};
export function useDispositionCards(path:string|null,board:unknown):{cards:Record<number,DispositionCard>;error:string;denied:boolean;hideHours:boolean}{
const [result,setResult]=useState<{path:string;board:unknown;cards:Record<number,DispositionCard>;error:string;denied:boolean;hideHours:boolean}|null>(null);
useEffect(()=>{const c=new AbortController();if(!path)return;request<{cards:DispositionCard[];hideHours?:boolean}>(path,{signal:c.signal}).then(v=>{if(!c.signal.aborted)setResult({path,board,cards:Object.fromEntries(v.cards.map(card=>[card.id,card])),error:'',denied:false,hideHours:v.hideHours===true});}).catch(e=>{if(!c.signal.aborted)setResult({path,board,cards:{},error:e.message,denied:e.status===403,hideHours:false});});return()=>c.abort();},[path,board]);
return result&&result.board===board&&result.path===path?result:{cards:{},error:'',denied:false,hideHours:false};
}
export function cardStyle(card?:DispositionCard):CSSProperties{
if(!card)return {};let hex=card.colour.slice(1);if(hex.length===3)hex=hex.split('').map(c=>c+c).join('');
const values=[0,2,4].map(i=>{const v=parseInt(hex.slice(i,i+2),16)/255;return v<=.04045?v/12.92:((v+.055)/1.055)**2.4;});
const luminance=values[0]*.2126+values[1]*.7152+values[2]*.0722;
return {backgroundColor:card.colour,color:(luminance+.05)/.05>=1.05/(luminance+.05)?'#000':'#fff'};
}
export function cardDescription(card?:DispositionCard){return card?[card.category,...card.lines,...card.details].join('\n'):'';}
export function DispositionCardText({job,card}:{job:number;card?:DispositionCard}){return <>{card?.verticalId&&<span className="fds-card-id-vertical" aria-hidden="true">{job}</span>}{card?.zipcode&&<span className="fds-card-zip" aria-label={`Postleitzahl ${card.zipcode}`}>{card.zipcode}</span>}<span className="fds-card-body"><strong>#{job}{card&&` · ${card.category}`}</strong>{card?.lines.map((line,i)=><span className="fds-card-line" key={i}>{line||'\u00a0'}</span>)}</span></>;}
export function DispositionCardDetails({card,onClose}:{card:DispositionCard;onClose:()=>void}){return <Modal title={`Auftrag #${card.id} · ${card.category}`} onClose={onClose}><div className="modal-body"><div className="fds-card-detail">{card.details.map((line,i)=><p key={i}>{line}</p>)}</div><hr/>{card.lines.filter(line=>line&&!card.details.includes(line)&&!card.details.includes("Zusage: "+line)).map((line,i)=><p key={i}>{line}</p>)}</div><div className="modal-footer"><button className="button" onClick={onClose}>Schließen</button></div></Modal>;}