Archiv: Geteilt-von-mir Tab + 3D-Datei Downloads für Owner und Empfänger
This commit is contained in:
@@ -541,6 +541,68 @@ export default function Kalkulator3D({ toast, editData, onEditDone, mobile, setA
|
||||
}
|
||||
|
||||
// ── Archiv ──────────────────────────────────────────────────────────────
|
||||
// ── Datei-Downloads für ein Archiv-Modell ────────────────────────────────────
|
||||
function ModelFiles({ calcId, toast }) {
|
||||
const [files, setFiles] = useState(null); // null = noch nicht geladen
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const load = async () => {
|
||||
if (files !== null) return;
|
||||
try { setFiles(await api(`/tools/kalkulator3d/${calcId}/files`)); }
|
||||
catch { setFiles([]); }
|
||||
};
|
||||
|
||||
const download = async f => {
|
||||
try {
|
||||
const res = await fetch(`/api/tools/kalkulator3d/${calcId}/files/${f.id}/download`, {
|
||||
headers:{ Authorization:`Bearer ${localStorage.getItem('sk_token')}` }
|
||||
});
|
||||
if (!res.ok) { toast('Download fehlgeschlagen','error'); return; }
|
||||
const blob = await res.blob();
|
||||
const a = Object.assign(document.createElement('a'),{ href:URL.createObjectURL(blob), download:f.originalname });
|
||||
document.body.appendChild(a); a.click(); document.body.removeChild(a);
|
||||
setTimeout(()=>URL.revokeObjectURL(a.href),1000);
|
||||
} catch(e){ toast(e.message,'error'); }
|
||||
};
|
||||
|
||||
const toggle = () => { if (!open) load(); setOpen(v=>!v); };
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={toggle} style={{ ...S.btn('#4ecdc4', true), width:'100%', textAlign:'left',
|
||||
display:'flex', alignItems:'center', gap:6, fontSize:11 }}>
|
||||
<span>🖨</span>
|
||||
<span style={{flex:1}}>3D-Dateien {files!==null&&files.length>0?`(${files.length})`:''}</span>
|
||||
<span style={{transform:open?'rotate(90deg)':'rotate(0)',transition:'transform 0.2s',fontSize:10}}>▶</span>
|
||||
</button>
|
||||
{open && (
|
||||
<div style={{ marginTop:6, background:'rgba(255,255,255,0.02)', border:'1px solid rgba(255,255,255,0.07)',
|
||||
borderRadius:8, overflow:'hidden' }}>
|
||||
{files === null ? (
|
||||
<div style={{ padding:'10px 14px', color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:11 }}>Lädt…</div>
|
||||
) : files.length === 0 ? (
|
||||
<div style={{ padding:'10px 14px', color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:11 }}>Keine Dateien vorhanden</div>
|
||||
) : files.map((f,i) => (
|
||||
<div key={f.id} style={{ display:'flex', alignItems:'center', gap:8, padding:'8px 12px',
|
||||
borderBottom: i<files.length-1 ? '1px solid rgba(255,255,255,0.05)' : 'none' }}>
|
||||
<span style={{ color:'rgba(255,255,255,0.6)', fontFamily:'monospace', fontSize:11,
|
||||
flex:1, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
|
||||
{f.originalname}
|
||||
</span>
|
||||
<span style={{ color:'rgba(255,255,255,0.25)', fontFamily:'monospace', fontSize:9, flexShrink:0 }}>
|
||||
{(f.size/1024/1024).toFixed(1)} MB
|
||||
</span>
|
||||
<button onClick={()=>download(f)} style={{ ...S.btn('#4ecdc4'), padding:'4px 10px', fontSize:10, flexShrink:0 }}>
|
||||
↓
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Share Modal ───────────────────────────────────────────────────────────────
|
||||
function CalcShareModal({ item, onClose, toast }) {
|
||||
const [username, setUsername] = useState('');
|
||||
@@ -602,19 +664,20 @@ function CalcShareModal({ item, onClose, toast }) {
|
||||
|
||||
export function SavedList({ toast, mobile }) {
|
||||
const { confirm, ConfirmDialog } = useConfirm();
|
||||
const [own, setOwn] = useState([]);
|
||||
const [shared, setShared] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [tab, setTab] = useState('own');
|
||||
const [editData, setEditData] = useState(null);
|
||||
const [search, setSearch] = useState('');
|
||||
const [lightbox, setLightbox] = useState(null);
|
||||
const [shareItem,setShareItem]= useState(null);
|
||||
const [own, setOwn] = useState([]);
|
||||
const [shared, setShared] = useState([]);
|
||||
const [sharedByMe, setSharedByMe] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [tab, setTab] = useState('own');
|
||||
const [editData, setEditData] = useState(null);
|
||||
const [search, setSearch] = useState('');
|
||||
const [lightbox, setLightbox] = useState(null);
|
||||
const [shareItem, setShareItem]= useState(null);
|
||||
|
||||
const load = () => {
|
||||
setLoading(true);
|
||||
api('/tools/kalkulator3d')
|
||||
.then(d => { setOwn(d.own||[]); setShared(d.shared||[]); })
|
||||
.then(d => { setOwn(d.own||[]); setShared(d.shared||[]); setSharedByMe(d.sharedByMe||[]); })
|
||||
.catch(e => toast(e.message,'error'))
|
||||
.finally(()=>setLoading(false));
|
||||
};
|
||||
@@ -625,7 +688,7 @@ export function SavedList({ toast, mobile }) {
|
||||
catch(e) { toast(e.message,'error'); }
|
||||
};
|
||||
|
||||
const items = tab==='own' ? own : shared;
|
||||
const items = tab==='own' ? own : tab==='shared' ? shared : sharedByMe;
|
||||
const filtered = search.trim()
|
||||
? items.filter(i => i.name.toLowerCase().includes(search.toLowerCase()) || (i.bemerkung&&i.bemerkung.toLowerCase().includes(search.toLowerCase())))
|
||||
: items;
|
||||
@@ -648,8 +711,8 @@ export function SavedList({ toast, mobile }) {
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div style={{ display:'flex', gap:6, marginBottom:14 }}>
|
||||
{[['own','Meine',own.length],['shared','Geteilt mit mir',shared.length]].map(([k,l,cnt])=>(
|
||||
<div style={{ display:'flex', gap:6, marginBottom:14, flexWrap:'wrap' }}>
|
||||
{[['own','Meine',own.length],['shared','Geteilt mit mir',shared.length],['sharedByMe','Geteilt von mir',sharedByMe.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)',
|
||||
@@ -751,18 +814,28 @@ export function SavedList({ toast, mobile }) {
|
||||
</div>
|
||||
|
||||
{/* Aktionen */}
|
||||
<div style={{ display:'flex', gap:6, marginTop:10 }}>
|
||||
{!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 style={{ display:'flex', gap:6, marginTop:10, flexDirection:'column' }}>
|
||||
{/* Datei-Downloads */}
|
||||
{item.has_files && (
|
||||
<ModelFiles calcId={item.id} toast={toast}/>
|
||||
)}
|
||||
<div style={{ display:'flex', gap:6 }}>
|
||||
{!item.is_shared && tab !== 'sharedByMe' ? (
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
) : tab === 'sharedByMe' ? (
|
||||
<div style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:10, padding:'4px 0' }}>
|
||||
→ {item.shared_with_name}
|
||||
</div>
|
||||
) : (
|
||||
<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>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user