diff --git a/backend/src/tools/dateien/routes.js b/backend/src/tools/dateien/routes.js index e965d06..fab12d3 100644 --- a/backend/src/tools/dateien/routes.js +++ b/backend/src/tools/dateien/routes.js @@ -198,6 +198,48 @@ router.post('/', authenticate, (req, res) => { }); }); +// ── 3D-Modelle Hilfsendpoints ───────────────────────────────────────────────── + +// Stellt sicher dass ein Ordner existiert (Name + optionaler parent), gibt ihn zurück +router.post('/ensure-folder', authenticate, (req, res) => { + const uid = req.user.id; + const { name, parent_id } = req.body; + if (!name?.trim()) return res.status(400).json({ error: 'Name erforderlich' }); + const parentId = parent_id ? parseInt(parent_id) : null; + + // Suche existierenden Ordner + const existing = parentId + ? db.prepare('SELECT * FROM folders WHERE user_id=? AND name=? AND parent_id=?').get(uid, name.trim(), parentId) + : db.prepare('SELECT * FROM folders WHERE user_id=? AND name=? AND parent_id IS NULL').get(uid, name.trim()); + + if (existing) return res.json(enrichFolder(existing)); + + const r = db.prepare('INSERT INTO folders (user_id,name,parent_id) VALUES (?,?,?)').run(uid, name.trim(), parentId); + res.json(enrichFolder(db.prepare('SELECT * FROM folders WHERE id=?').get(r.lastInsertRowid))); +}); + +// Upload 3D-Dateien (ohne Ext-Beschränkung, für den Kalkulator) +const upload3d = multer({ + storage, + limits: { fileSize: parseInt(process.env.MAX_3D_SIZE_MB || '200') * 1024 * 1024 }, +}); + +router.post('/upload-3d', authenticate, (req, res) => { + upload3d.array('files', 20)(req, res, err => { + if (err) return res.status(400).json({ error: err.message }); + if (!req.files?.length) return res.status(400).json({ error: 'Keine Dateien' }); + const uid = req.user.id; + const folderId = req.body.folder_id ? parseInt(req.body.folder_id) : null; + const inserted = []; + for (const f of req.files) { + const r = db.prepare('INSERT INTO files (user_id,filename,originalname,mimetype,size,folder_id) VALUES (?,?,?,?,?,?)') + .run(uid, f.filename, f.originalname, f.mimetype || 'application/octet-stream', f.size, folderId); + inserted.push(r.lastInsertRowid); + } + res.json({ uploaded: inserted.length }); + }); +}); + router.get('/storage', authenticate, (req, res) => { const uid = req.user.id; const used = db.prepare('SELECT COALESCE(SUM(size),0) AS s FROM files WHERE user_id=?').get(uid).s; diff --git a/frontend/src/tools/dateien.jsx b/frontend/src/tools/dateien.jsx index 156069c..97413f2 100644 --- a/frontend/src/tools/dateien.jsx +++ b/frontend/src/tools/dateien.jsx @@ -192,6 +192,11 @@ export default function Dateien({ toast, mobile }) { useEffect(()=>{load();},[load]); + // "3D-Modelle" Ordner beim ersten Laden automatisch anlegen + useEffect(()=>{ + api('/tools/dateien/ensure-folder',{body:{name:'3D-Modelle'}}).catch(()=>{}); + },[]); + const doUpload = async file => { if (!file) return; setUploading(true); diff --git a/frontend/src/tools/kalkulator3d.jsx b/frontend/src/tools/kalkulator3d.jsx index 0e5d814..a2f79d6 100644 --- a/frontend/src/tools/kalkulator3d.jsx +++ b/frontend/src/tools/kalkulator3d.jsx @@ -2,6 +2,8 @@ import { useConfirm } from '../confirm.jsx'; import { useState, useMemo, useEffect, useRef } from 'react'; import { api, S } from '../lib.js'; +const MODEL_EXTENSIONS = ['.stl','.3mf','.f3d','.step','.stp','.obj','.iges','.igs','.fcstd','.amf','.scad']; + const DEFAULTS = { materialpreis_pro_rolle: 10, // €/Rolle (1 kg = 1000 g → 0.01 €/g) stromverbrauch_kw: 0.15, @@ -138,7 +140,9 @@ export default function Kalkulator3D({ toast, editData, onEditDone, mobile, setA const [bemerkung, setBemerkung] = useState(editData?.bemerkung ?? ''); const [image, setImage] = useState(editData?.image ?? null); const [saving, setSaving] = useState(false); - const fileRef = useRef(null); + const [modelFiles, setModelFiles] = useState([]); // Array von File-Objekten + const fileRef = useRef(null); + const modelFileRef = useRef(null); const sv = (k, v) => setVars(p => ({ ...p, [k]: v })); const calc = useMemo(() => { @@ -177,12 +181,36 @@ export default function Kalkulator3D({ toast, editData, onEditDone, mobile, setA preis_normal: calc.grund * 1.5, preis_auftrag: calc.grund * 2.5, image, bemerkung }; + if (editData?.id) { await api(`/tools/kalkulator3d/${editData.id}`, { method:'PUT', body:payload }); toast('Aktualisiert ✓'); onEditDone?.(); } else { await api('/tools/kalkulator3d', { body:payload }); - toast('Gespeichert ✓'); setName(''); setBemerkung(''); setImage(null); + // 3D-Dateien hochladen wenn vorhanden + if (modelFiles.length > 0) { + try { + // 1. "3D-Modelle" Basisordner sicherstellen + const base = await api('/tools/dateien/ensure-folder', { body: { name: '3D-Modelle' } }); + // 2. Unterordner mit Modellnamen anlegen + const sub = await api('/tools/dateien/ensure-folder', { body: { name: name.trim(), parent_id: base.id } }); + // 3. Dateien hochladen + const form = new FormData(); + form.append('folder_id', sub.id); + for (const f of modelFiles) form.append('files', f); + await fetch('/api/tools/dateien/upload-3d', { + method: 'POST', + headers: { Authorization: `Bearer ${localStorage.getItem('sk_token')}` }, + body: form, + }); + toast(`Gespeichert ✓ · ${modelFiles.length} Datei(en) in 3D-Modelle/${name.trim()} abgelegt`); + } catch { + toast('Gespeichert ✓ (Datei-Upload fehlgeschlagen)', 'error'); + } + } else { + toast('Gespeichert ✓'); + } + setName(''); setBemerkung(''); setImage(null); setModelFiles([]); setActive?.('kalkulator3d-saved'); } } catch(e) { toast(e.message, 'error'); } finally { setSaving(false); } @@ -340,6 +368,50 @@ export default function Kalkulator3D({ toast, editData, onEditDone, mobile, setA )} + {/* 3D-Modelldateien – nur beim Neuanlegen */} + {!editData?.id && ( +
+ + setModelFiles(Array.from(e.target.files||[]))} + style={{ display:'none' }} /> + {modelFiles.length > 0 ? ( +
+ {modelFiles.map((f,i) => ( +
+ + 🖨 {f.name} + + + {(f.size/1024/1024).toFixed(1)} MB + +
+ ))} +
+ + +
+
+ ) : ( + + )} +
+ )} +