Festpreis-Input-Fix, Archiv-Teilen (read-only)

This commit is contained in:
2026-05-28 13:30:39 +02:00
parent 188f121cda
commit 83e88745fa
4 changed files with 165 additions and 25 deletions

View File

@@ -541,48 +541,126 @@ export default function Kalkulator3D({ toast, editData, onEditDone, mobile, setA
}
// ── Archiv ──────────────────────────────────────────────────────────────
// ── Share Modal ───────────────────────────────────────────────────────────────
function CalcShareModal({ item, onClose, toast }) {
const [username, setUsername] = useState('');
const [shares, setShares] = useState([]);
const load = () => api(`/tools/kalkulator3d/${item.id}/shares`).then(setShares).catch(()=>{});
useEffect(()=>{ load(); },[]);
const share = async () => {
if (!username.trim()) return;
try { await api(`/tools/kalkulator3d/${item.id}/share`,{body:{username:username.trim()}}); toast('Geteilt ✓'); setUsername(''); load(); }
catch(e) { toast(e.message,'error'); }
};
const unshare = async uid => {
try { await api(`/tools/kalkulator3d/${item.id}/share/${uid}`,{method:'DELETE'}); setShares(p=>p.filter(s=>s.id!==uid)); toast('Freigabe entfernt'); }
catch(e) { toast(e.message,'error'); }
};
const isMobile = window.innerWidth<768;
return (
<div style={{position:'fixed',inset:0,background:'rgba(0,0,0,0.8)',zIndex:6000,
display:'flex',alignItems:isMobile?'flex-end':'center',justifyContent:'center',padding:isMobile?0:24}}
onClick={e=>e.target===e.currentTarget&&onClose()}>
<div style={{background:'#1a1a1e',borderRadius:isMobile?'16px 16px 0 0':16,
width:'100%',maxWidth:420,padding:'20px 20px 28px',border:'1px solid rgba(255,255,255,0.12)'}}>
{isMobile&&<div style={{width:36,height:4,background:'rgba(255,255,255,0.15)',borderRadius:2,margin:'0 auto 14px'}}/>}
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:14}}>
<div style={{color:'#fff',fontFamily:"'Space Mono',monospace",fontSize:13,fontWeight:700}}>
🖨 {item.name}
</div>
<button onClick={onClose} style={{background:'transparent',border:'none',color:'rgba(255,255,255,0.4)',cursor:'pointer',fontSize:18,padding:'0 4px'}}></button>
</div>
<p style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10,marginBottom:12,lineHeight:1.6}}>
Empfänger können das Modell nur ansehen, nicht bearbeiten oder löschen.
</p>
<div style={{display:'flex',gap:8,marginBottom:14}}>
<input value={username} onChange={e=>setUsername(e.target.value)} onKeyDown={e=>e.key==='Enter'&&share()}
placeholder="Benutzername" autoCapitalize="none" style={{...S.inp,flex:1,fontSize:15}}/>
<button onClick={share} style={S.btn('#4ecdc4')}>Teilen</button>
</div>
{shares.length>0 ? (
<div>
<div style={{...S.head,marginBottom:6}}>GETEILT MIT</div>
{shares.map(s=>(
<div key={s.id} style={{display:'flex',justifyContent:'space-between',alignItems:'center',
padding:'8px 0',borderBottom:'1px solid rgba(255,255,255,0.05)'}}>
<div>
<div style={{color:'rgba(255,255,255,0.8)',fontFamily:'monospace',fontSize:13}}>{s.username}</div>
{s.shared_at && <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:10}}>seit {new Date(s.shared_at.replace(' ','T')).toLocaleDateString('de-DE')}</div>}
</div>
<button onClick={()=>unshare(s.id)} style={S.btn('#ff6b9d',true)}></button>
</div>
))}
</div>
) : (
<div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:11}}>Noch mit niemandem geteilt.</div>
)}
</div>
</div>
);
}
export function SavedList({ toast, mobile }) {
const { confirm, ConfirmDialog } = useConfirm();
const [items, setItems] = useState([]);
const [own, setOwn] = useState([]);
const [shared, setShared] = useState([]);
const [loading, setLoading] = useState(true);
const [delId, setDelId] = useState(null);
const [tab, setTab] = useState('own');
const [editData, setEditData] = useState(null);
const [search, setSearch] = useState('');
const [lightbox, setLightbox] = useState(null);
const [shareItem,setShareItem]= useState(null);
useEffect(() => {
api('/tools/kalkulator3d').then(setItems).catch(e => toast(e.message, 'error')).finally(() => setLoading(false));
}, []);
const load = () => {
setLoading(true);
api('/tools/kalkulator3d')
.then(d => { setOwn(d.own||[]); setShared(d.shared||[]); })
.catch(e => toast(e.message,'error'))
.finally(()=>setLoading(false));
};
useEffect(()=>{ load(); },[]);
const del = async id => {
try { await api(`/tools/kalkulator3d/${id}`, { method:'DELETE' }); setItems(p => p.filter(i => i.id !== id)); toast('Gelöscht'); }
catch(e) { toast(e.message, 'error'); } finally { setDelId(null); }
try { await api(`/tools/kalkulator3d/${id}`,{method:'DELETE'}); setOwn(p=>p.filter(i=>i.id!==id)); toast('Gelöscht'); }
catch(e) { toast(e.message,'error'); }
};
const items = tab==='own' ? own : shared;
const filtered = search.trim()
? items.filter(i =>
i.name.toLowerCase().includes(search.toLowerCase()) ||
(i.bemerkung && i.bemerkung.toLowerCase().includes(search.toLowerCase()))
)
? items.filter(i => i.name.toLowerCase().includes(search.toLowerCase()) || (i.bemerkung&&i.bemerkung.toLowerCase().includes(search.toLowerCase())))
: items;
if (editData) return (
<Kalkulator3D toast={toast} mobile={mobile} editData={editData} onEditDone={() => {
setEditData(null);
api('/tools/kalkulator3d').then(setItems).catch(() => {});
}} />
<Kalkulator3D toast={toast} mobile={mobile} editData={editData} onEditDone={() => { setEditData(null); load(); }} />
);
return (
<div style={{ padding: mobile ? '14px 14px 90px' : '36px 44px', maxWidth:900 }}>
{lightbox && <ImageModal src={lightbox} onClose={() => setLightbox(null)} />}
{shareItem && <CalcShareModal item={shareItem} onClose={()=>{setShareItem(null);load();}} toast={toast}/>}
<ConfirmDialog/>
<div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:14 }}>
<h1 style={{ color:'#fff', fontFamily:"'Space Mono',monospace", fontSize:mobile?18:22, margin:0 }}>Archiv</h1>
<button onClick={() => api('/tools/kalkulator3d').then(setItems).catch(()=>{})} title="Neu laden"
<button onClick={load} title="Neu laden"
style={{ background:'transparent', border:'1px solid rgba(255,255,255,0.1)', borderRadius:8,
color:'rgba(255,255,255,0.4)', cursor:'pointer', padding:'6px 10px', fontSize:14, fontFamily:'monospace' }}></button>
</div>
{/* Tabs */}
<div style={{ display:'flex', gap:6, marginBottom:14 }}>
{[['own','Meine',own.length],['shared','Geteilt mit mir',shared.length]].map(([k,l,cnt])=>(
<button key={k} onClick={()=>{setTab(k);setSearch('');}} style={{
padding:'6px 14px', borderRadius:20, fontFamily:'monospace', fontSize:11, cursor:'pointer',
background: tab===k ? '#4ecdc4' : 'rgba(255,255,255,0.05)',
color: tab===k ? '#0d0d0f' : 'rgba(255,255,255,0.55)',
border: tab===k ? 'none' : '1px solid rgba(255,255,255,0.1)',
fontWeight: tab===k ? 700 : 400,
}}>
{l}{cnt>0&&<span style={{marginLeft:5,opacity:0.7}}>({cnt})</span>}
</button>
))}
</div>
{/* Suche */}
<div style={{ position:'relative', marginBottom:12 }}>
<span style={{ position:'absolute', left:12, top:'50%', transform:'translateY(-50%)',
@@ -674,8 +752,17 @@ export function SavedList({ toast, mobile }) {
{/* Aktionen */}
<div style={{ display:'flex', gap:6, marginTop:10 }}>
<button onClick={() => setEditData(item)} style={{ ...S.btn('#4ecdc4', true), flex:1, textAlign:'center' }}> Bearbeiten</button>
<button onClick={() => del(item.id)} style={{ ...S.btn('#ff6b9d', true), flex:1, textAlign:'center' }}> Löschen</button>
{!item.is_shared ? (
<>
<button onClick={() => setEditData(item)} style={{ ...S.btn('#4ecdc4', true), flex:1, textAlign:'center' }}> Bearbeiten</button>
<button onClick={() => setShareItem(item)} style={{ ...S.btn('#ffe66d', true), padding:'0 12px' }} title="Teilen">🤝</button>
<button onClick={() => del(item.id)} style={{ ...S.btn('#ff6b9d', true), padding:'0 12px' }} title="Löschen"></button>
</>
) : (
<div style={{ color:'rgba(255,255,255,0.25)', fontFamily:'monospace', fontSize:10, padding:'6px 0' }}>
von {item.owner_name} · nur Ansicht
</div>
)}
</div>
</div>
))}