Fix Suche: SearchResultItem als Top-Level, type-Feld fix, Position zwischen Titel und Schnellzugriff

This commit is contained in:
2026-06-05 09:14:04 +02:00
parent 90fed8fb5f
commit f4e64811c1

View File

@@ -1430,6 +1430,37 @@ function ChangelogModal({ user, toast, onClose, onRead }) {
</div> </div>
); );
} }
function SearchResultItem({ item, idx, activeIdx, onNavigate, onHover }) {
const isActive = idx === activeIdx;
let icon = '❓', primary = '', secondary = '';
if (item.type==='tool') { icon=item.icon||'🛠'; primary=item.label; secondary=item.sub||'Navigation'; }
if (item.type==='devtool') { icon=item.icon||'🔧'; primary=item.label; secondary='Dev-Tools'; }
if (item.type==='link') {
const hasCustom = item.icon && item.icon !== '🔗';
icon = hasCustom ? item.icon : '🔗';
primary = item.title; secondary = item.url;
}
if (item.type==='snippet') { icon='📄'; primary=item.title; secondary=`Code · ${item.language||''}`; }
if (item.type==='calc') { icon='🖨'; primary=item.name; secondary='3D-Archiv'; }
if (item.type==='order') { icon='📦'; primary=item.name; secondary=`Bestellung · ${item.status||''}`; }
return (
<div onClick={()=>onNavigate(item)} onMouseEnter={()=>onHover(idx)}
style={{display:'flex',alignItems:'center',gap:10,padding:'9px 14px',cursor:'pointer',
background:isActive?'rgba(78,205,196,0.1)':'transparent',
borderBottom:'1px solid rgba(255,255,255,0.04)'}}>
<span style={{fontSize:16,flexShrink:0,width:24,textAlign:'center'}}>{icon}</span>
<div style={{flex:1,minWidth:0}}>
<div style={{color:isActive?'#4ecdc4':'#fff',fontFamily:'monospace',fontSize:13,fontWeight:500,
overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{primary}</div>
<div style={{color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:10,
overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{secondary}</div>
</div>
{item.type==='link' && <span style={{color:'rgba(255,255,255,0.2)',fontSize:10,flexShrink:0}}></span>}
{(item.type==='tool'||item.type==='devtool') && <span style={{color:'rgba(255,255,255,0.2)',fontSize:10,flexShrink:0}}></span>}
</div>
);
}
// ── Globale Suche ───────────────────────────────────────────────────────────── // ── Globale Suche ─────────────────────────────────────────────────────────────
// Statischer Such-Index für Tools & Dev-Tools Sub-Tools // Statischer Such-Index für Tools & Dev-Tools Sub-Tools
const SEARCH_TOOL_INDEX = [ const SEARCH_TOOL_INDEX = [
@@ -1520,38 +1551,7 @@ function GlobalSearch({ setActive, setDevToolsNav, mobile }) {
else if (e.key === 'Escape') { close(); inputRef.current?.blur(); } else if (e.key === 'Escape') { close(); inputRef.current?.blur(); }
}; };
const ResultItem = ({item, idx}) => {
const isActive = idx === active;
let icon = '🔍', primary = '', secondary = '';
if (item.type==='tool') { icon=item.icon; primary=item.label; secondary=item.sub||'Navigation'; }
if (item.type==='devtool') { icon=item.icon; primary=item.label; secondary='Dev-Tools'; }
if (item.type==='link') {
const hasCustomIcon = item.icon && item.icon!=='🔗';
icon = hasCustomIcon ? item.icon : '🔗';
primary = item.title; secondary = item.url;
}
if (item.type==='snippet') { icon='📄'; primary=item.title; secondary=`Code · ${item.language||''}`; }
if (item.type==='calc') { icon='🖨'; primary=item.name; secondary='3D-Archiv'; }
if (item.type==='order') { icon='📦'; primary=item.name; secondary=`Bestellung · ${item.status||''}`; }
return (
<div onClick={()=>navigate(item)}
onMouseEnter={()=>setActiveR(idx)}
style={{display:'flex',alignItems:'center',gap:10,padding:'9px 14px',cursor:'pointer',
background:isActive?'rgba(78,205,196,0.1)':'transparent',
borderBottom:'1px solid rgba(255,255,255,0.04)'}}>
<span style={{fontSize:16,flexShrink:0,width:24,textAlign:'center'}}>{icon}</span>
<div style={{flex:1,minWidth:0}}>
<div style={{color:isActive?'#4ecdc4':'#fff',fontFamily:'monospace',fontSize:13,fontWeight:500,
overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{primary}</div>
<div style={{color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:10,
overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{secondary}</div>
</div>
{item.type==='link' && <span style={{color:'rgba(255,255,255,0.2)',fontSize:10,flexShrink:0}}></span>}
{(item.type==='tool'||item.type==='devtool') && <span style={{color:'rgba(255,255,255,0.2)',fontSize:10,flexShrink:0}}></span>}
</div>
);
};
const hasResults = allItems.length > 0; const hasResults = allItems.length > 0;
const showDropdown = !!query.trim() && results !== null; const showDropdown = !!query.trim() && results !== null;
@@ -1595,13 +1595,13 @@ function GlobalSearch({ setActive, setDevToolsNav, mobile }) {
{results.static?.length>0 && ( {results.static?.length>0 && (
<div> <div>
<div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>NAVIGATION</div> <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>NAVIGATION</div>
{results.static.map((item,i)=><ResultItem key={`s${i}`} item={item} idx={i}/>)} {results.static.map((item,i)=><SearchResultItem key={`s${i}`} item={item} idx={i} activeIdx={active} onNavigate={navigate} onHover={setActiveR}/>)}
</div> </div>
)} )}
{results.links?.length>0 && ( {results.links?.length>0 && (
<div> <div>
<div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>LINKS</div> <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>LINKS</div>
{results.links.map((item,i)=><ResultItem key={`l${i}`} item={item} idx={results.static.length+i}/>)} {results.links.map((item,i)=><SearchResultItem key={`l${i}`} item={{...item,type:'link'}} idx={results.static.length+i} activeIdx={active} onNavigate={navigate} onHover={setActiveR}/>)}
</div> </div>
)} )}
{results.snippets?.length>0 && ( {results.snippets?.length>0 && (
@@ -1609,7 +1609,7 @@ function GlobalSearch({ setActive, setDevToolsNav, mobile }) {
<div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>CODE-SCHNIPSEL</div> <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>CODE-SCHNIPSEL</div>
{results.snippets.map((item,i)=>{ {results.snippets.map((item,i)=>{
const off=results.static.length+(results.links||[]).length; const off=results.static.length+(results.links||[]).length;
return <ResultItem key={`sn${i}`} item={item} idx={off+i}/>; return <SearchResultItem key={`sn${i}`} item={{...item,type:'snippet'}} idx={off+i} activeIdx={active} onNavigate={navigate} onHover={setActiveR}/>;
})} })}
</div> </div>
)} )}
@@ -1618,7 +1618,7 @@ function GlobalSearch({ setActive, setDevToolsNav, mobile }) {
<div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>3D-ARCHIV</div> <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>3D-ARCHIV</div>
{results.calculations.map((item,i)=>{ {results.calculations.map((item,i)=>{
const off=results.static.length+(results.links||[]).length+(results.snippets||[]).length; const off=results.static.length+(results.links||[]).length+(results.snippets||[]).length;
return <ResultItem key={`c${i}`} item={item} idx={off+i}/>; return <SearchResultItem key={`c${i}`} item={{...item,type:'calc'}} idx={off+i} activeIdx={active} onNavigate={navigate} onHover={setActiveR}/>;
})} })}
</div> </div>
)} )}
@@ -1627,7 +1627,7 @@ function GlobalSearch({ setActive, setDevToolsNav, mobile }) {
<div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>BESTELLUNGEN</div> <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>BESTELLUNGEN</div>
{results.orders.map((item,i)=>{ {results.orders.map((item,i)=>{
const off=results.static.length+(results.links||[]).length+(results.snippets||[]).length+(results.calculations||[]).length; const off=results.static.length+(results.links||[]).length+(results.snippets||[]).length+(results.calculations||[]).length;
return <ResultItem key={`o${i}`} item={item} idx={off+i}/>; return <SearchResultItem key={`o${i}`} item={{...item,type:'order'}} idx={off+i} activeIdx={active} onNavigate={navigate} onHover={setActiveR}/>;
})} })}
</div> </div>
)} )}
@@ -1655,8 +1655,6 @@ function Dashboard({ toast, mobile, setActive, setDevToolsNav, unreadMsgs=0, unr
{showBoard && <BoardModal user={user} toast={toast} onClose={()=>setShowBoard(false)} onRead={onBoardRead}/>} {showBoard && <BoardModal user={user} toast={toast} onClose={()=>setShowBoard(false)} onRead={onBoardRead}/>}
{showChangelog && <ChangelogModal user={user} toast={toast} onClose={()=>setShowChangelog(false)} onRead={onChangelogRead}/>} {showChangelog && <ChangelogModal user={user} toast={toast} onClose={()=>setShowChangelog(false)} onRead={onChangelogRead}/>}
{/* Globale Suche */}
<GlobalSearch setActive={setActive} setDevToolsNav={setDevToolsNav} mobile={mobile}/>
<div style={{ marginBottom:16, display:'flex', alignItems:'flex-start', justifyContent:'space-between' }}> <div style={{ marginBottom:16, display:'flex', alignItems:'flex-start', justifyContent:'space-between' }}>
<div> <div>
<div style={{ display:'flex', alignItems:'center', gap:10 }}> <div style={{ display:'flex', alignItems:'center', gap:10 }}>
@@ -1683,6 +1681,8 @@ function Dashboard({ toast, mobile, setActive, setDevToolsNav, unreadMsgs=0, unr
color:'rgba(255,255,255,0.4)', cursor:'pointer', padding:'6px 10px', fontSize:14, color:'rgba(255,255,255,0.4)', cursor:'pointer', padding:'6px 10px', fontSize:14,
fontFamily:'monospace', marginTop:2, flexShrink:0 }}>↺</button> fontFamily:'monospace', marginTop:2, flexShrink:0 }}>↺</button>
</div> </div>
{/* Globale Suche */}
<GlobalSearch setActive={setActive} setDevToolsNav={setDevToolsNav} mobile={mobile}/>
<QuickLinks toast={toast} mobile={mobile} /> <QuickLinks toast={toast} mobile={mobile} />
<PushScheduler toast={toast}/> <PushScheduler toast={toast}/>
<CalendarWidget/> <CalendarWidget/>