import React, { useState, useEffect, useCallback, useRef } from 'react'; import { useAuth } from '../context/AuthContext'; import { toast } from 'react-toastify'; /* ── helpers ── */ const authFetch = (url, opts = {}) => { const token = localStorage.getItem('token'); return fetch(url, { ...opts, headers: { ...(opts.headers || {}), Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, }); }; const API = process.env.REACT_APP_API_URL || '/api'; const TYPE_ICONS = { notebook: 'πŸ’»', monitor: 'πŸ–₯️', headset: '🎧', drucker: 'πŸ–¨οΈ', sonstiges: 'πŸ“¦', }; const getTypeIcon = (type) => { if (!type) return 'πŸ“¦'; const t = type.toLowerCase(); return TYPE_ICONS[t] || 'πŸ“¦'; }; const STATUS_LABELS = { verfuegbar: 'VerfΓΌgbar', zugewiesen: 'Zugewiesen', inaktiv: 'Inaktiv', beschaedigt: 'BeschΓ€digt', }; const STATUS_CSS = { verfuegbar: 'ap-badge-available', zugewiesen: 'ap-badge-assigned', inaktiv: 'ap-badge-inactive', beschaedigt: 'ap-badge-damaged', }; const fmtDate = (d) => { if (!d) return 'β€”'; try { return new Date(d).toLocaleDateString('de-DE'); } catch { return d; } }; const fmtCurrency = (n) => { if (n === null || n === undefined || n === '') return 'β€”'; return parseFloat(n).toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €'; }; const calcBookValue = (asset) => { if (!asset.purchase_price || !asset.purchase_date || !asset.useful_life_years) return null; const price = parseFloat(asset.purchase_price); const residual = parseFloat(asset.residual_value || 0); const lifeMonths = parseFloat(asset.useful_life_years) * 12; const depPerMonth = (price - residual) / lifeMonths; const months = Math.max(0, (Date.now() - new Date(asset.purchase_date).getTime()) / (1000 * 60 * 60 * 24 * 30.44)); const depreciated = Math.min(price - residual, depPerMonth * months); return Math.max(residual, price - depreciated); }; const calcDepPercent = (asset) => { if (!asset.purchase_price || !asset.purchase_date || !asset.useful_life_years) return 0; const price = parseFloat(asset.purchase_price); const residual = parseFloat(asset.residual_value || 0); const lifeMonths = parseFloat(asset.useful_life_years) * 12; const depPerMonth = (price - residual) / lifeMonths; const months = Math.max(0, (Date.now() - new Date(asset.purchase_date).getTime()) / (1000 * 60 * 60 * 24 * 30.44)); const depreciated = Math.min(price - residual, depPerMonth * months); return Math.round((depreciated / (price - residual || 1)) * 100); }; const calcMonthlyDep = (asset) => { if (!asset.purchase_price || !asset.useful_life_years) return null; const price = parseFloat(asset.purchase_price); const residual = parseFloat(asset.residual_value || 0); const lifeMonths = parseFloat(asset.useful_life_years) * 12; return (price - residual) / lifeMonths; }; const relativeTime = (d) => { if (!d) return 'β€”'; const diff = Date.now() - new Date(d).getTime(); const mins = Math.floor(diff / 60000); if (mins < 2) return 'gerade eben'; if (mins < 60) return `vor ${mins} Min`; const h = Math.floor(mins / 60); if (h < 24) return `vor ${h} Std`; return `vor ${Math.floor(h / 24)} Tag(en)`; }; const isOnline = (agent) => { if (!agent || !agent.last_seen) return false; const diff = Date.now() - new Date(agent.last_seen).getTime(); return diff < 5 * 60 * 1000; }; const initials = (name) => { if (!name) return '??'; const parts = name.trim().split(' '); if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); return name.slice(0, 2).toUpperCase(); }; /* ═══════════════════════════════════════════════════════════════ EDIT MODAL ═══════════════════════════════════════════════════════════════ */ const EditModal = ({ asset, onClose, onSaved }) => { const [form, setForm] = useState({ name: asset.name || '', type: asset.type || '', model: asset.model || '', serial_number: asset.serial_number || '', manufacturer: asset.manufacturer || '', os: asset.os || '', ip_address: asset.ip_address || '', inventory_number: asset.inventory_number || '', department: asset.department || '', purchase_date: asset.purchase_date ? asset.purchase_date.slice(0, 10) : '', purchase_price: asset.purchase_price || '', useful_life_years: asset.useful_life_years || '', residual_value: asset.residual_value || '', next_maintenance_date: asset.next_maintenance_date ? asset.next_maintenance_date.slice(0, 10) : '', maintenance_interval_months: asset.maintenance_interval_months || '', maintenance_notes: asset.maintenance_notes || '', teamviewer_id: asset.teamviewer_id || '', description: asset.description || '', }); const [saving, setSaving] = useState(false); const set = (k, v) => setForm(f => ({ ...f, [k]: v })); const save = async () => { setSaving(true); try { const res = await authFetch(`${API}/assets/${asset.id}`, { method: 'PUT', body: JSON.stringify(form), }); if (!res.ok) throw new Error(await res.text()); toast.success('Asset gespeichert'); onSaved(); } catch (e) { toast.error('Fehler beim Speichern: ' + e.message); } finally { setSaving(false); } }; const Field = ({ label, k, type = 'text', placeholder }) => (
set(k, e.target.value)} />
); return (
e.stopPropagation()}>
✏️ Asset bearbeiten β€” {asset.name}
GerΓ€tedaten
Finanzen & Wartung