feat: initial commit
This commit is contained in:
268
src/components/AdminPanel.jsx
Normal file
268
src/components/AdminPanel.jsx
Normal file
@@ -0,0 +1,268 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { databases, DATABASE_ID } from '../lib/appwrite';
|
||||
import { ID, Query } from 'appwrite';
|
||||
import Header from './Header';
|
||||
import Toast from './Toast';
|
||||
import { useToast } from '../hooks/useToast';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import LagerstandortManager from './LagerstandortManager';
|
||||
import { useLagerstandorte } from '../hooks/useLagerstandorte';
|
||||
|
||||
export default function AdminPanel() {
|
||||
const { user, userMeta } = useAuth();
|
||||
const { toast, showToast } = useToast();
|
||||
const locationId = userMeta?.locationId || '';
|
||||
const { lagerstandorte, addLagerstandort, toggleLagerstandort, deleteLagerstandort } = useLagerstandorte(locationId);
|
||||
|
||||
const [stats, setStats] = useState({ users: 0, locations: 0, assets: 0, lagerstandorte: 0 });
|
||||
const [locations, setLocations] = useState([]);
|
||||
const [usersList, setUsersList] = useState([]);
|
||||
const [showLsManager, setShowLsManager] = useState(false);
|
||||
|
||||
const [newFiliale, setNewFiliale] = useState({ name: '', address: '' });
|
||||
const [addingFiliale, setAddingFiliale] = useState(false);
|
||||
const [editingId, setEditingId] = useState(null);
|
||||
const [editForm, setEditForm] = useState({ name: '', address: '' });
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
try {
|
||||
const [locsRes, usersRes, assetsRes, lsRes] = await Promise.all([
|
||||
databases.listDocuments(DATABASE_ID, 'locations', [Query.limit(100)]),
|
||||
databases.listDocuments(DATABASE_ID, 'users_meta', [Query.limit(200)]),
|
||||
databases.listDocuments(DATABASE_ID, 'assets', [Query.limit(1)]),
|
||||
databases.listDocuments(DATABASE_ID, 'lagerstandorte', [Query.limit(1)]),
|
||||
]);
|
||||
setLocations(locsRes.documents);
|
||||
setUsersList(usersRes.documents);
|
||||
setStats({
|
||||
users: usersRes.total,
|
||||
locations: locsRes.total,
|
||||
assets: assetsRes.total,
|
||||
lagerstandorte: lsRes.total,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Admin-Daten laden fehlgeschlagen:', err);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => { loadData(); }, [loadData]);
|
||||
|
||||
async function handleAddFiliale(e) {
|
||||
e.preventDefault();
|
||||
if (!newFiliale.name.trim()) return;
|
||||
setAddingFiliale(true);
|
||||
try {
|
||||
const doc = await databases.createDocument(DATABASE_ID, 'locations', ID.unique(), {
|
||||
name: newFiliale.name.trim(),
|
||||
address: newFiliale.address.trim(),
|
||||
isActive: true,
|
||||
});
|
||||
setLocations((prev) => [...prev, doc]);
|
||||
setStats((s) => ({ ...s, locations: s.locations + 1 }));
|
||||
setNewFiliale({ name: '', address: '' });
|
||||
showToast(`Filiale "${doc.name}" erstellt`);
|
||||
} catch (err) {
|
||||
showToast('Fehler beim Erstellen: ' + (err.message || err), '#C62828');
|
||||
} finally {
|
||||
setAddingFiliale(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleToggleFiliale(id) {
|
||||
const loc = locations.find((l) => l.$id === id);
|
||||
if (!loc) return;
|
||||
try {
|
||||
const updated = await databases.updateDocument(DATABASE_ID, 'locations', id, {
|
||||
isActive: !loc.isActive,
|
||||
});
|
||||
setLocations((prev) => prev.map((l) => l.$id === id ? updated : l));
|
||||
showToast(`Filiale "${loc.name}" ${updated.isActive ? 'aktiviert' : 'deaktiviert'}`);
|
||||
} catch (err) {
|
||||
showToast('Fehler: ' + (err.message || err), '#C62828');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteFiliale(id) {
|
||||
const loc = locations.find((l) => l.$id === id);
|
||||
if (!window.confirm(`Filiale "${loc?.name}" wirklich löschen?`)) return;
|
||||
try {
|
||||
await databases.deleteDocument(DATABASE_ID, 'locations', id);
|
||||
setLocations((prev) => prev.filter((l) => l.$id !== id));
|
||||
setStats((s) => ({ ...s, locations: s.locations - 1 }));
|
||||
showToast(`Filiale "${loc.name}" gelöscht`, '#607D8B');
|
||||
} catch (err) {
|
||||
showToast('Fehler beim Löschen: ' + (err.message || err), '#C62828');
|
||||
}
|
||||
}
|
||||
|
||||
function startEdit(loc) {
|
||||
setEditingId(loc.$id);
|
||||
setEditForm({ name: loc.name, address: loc.address || '' });
|
||||
}
|
||||
|
||||
async function handleSaveEdit() {
|
||||
if (!editForm.name.trim()) return;
|
||||
try {
|
||||
const updated = await databases.updateDocument(DATABASE_ID, 'locations', editingId, {
|
||||
name: editForm.name.trim(),
|
||||
address: editForm.address.trim(),
|
||||
});
|
||||
setLocations((prev) => prev.map((l) => l.$id === editingId ? updated : l));
|
||||
setEditingId(null);
|
||||
showToast(`Filiale "${updated.name}" gespeichert`);
|
||||
} catch (err) {
|
||||
showToast('Fehler beim Speichern: ' + (err.message || err), '#C62828');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header showToast={showToast} />
|
||||
<div className="panel-page">
|
||||
<div className="panel-title-bar">
|
||||
<h1>Admin Panel</h1>
|
||||
<p>System-Übersicht und Verwaltung</p>
|
||||
</div>
|
||||
|
||||
<div className="panel-stats">
|
||||
<div className="panel-stat-card">
|
||||
<div className="panel-stat-number">{stats.users}</div>
|
||||
<div className="panel-stat-label">Benutzer</div>
|
||||
</div>
|
||||
<div className="panel-stat-card">
|
||||
<div className="panel-stat-number">{stats.locations}</div>
|
||||
<div className="panel-stat-label">Filialen</div>
|
||||
</div>
|
||||
<div className="panel-stat-card">
|
||||
<div className="panel-stat-number">{stats.assets}</div>
|
||||
<div className="panel-stat-label">Assets gesamt</div>
|
||||
</div>
|
||||
<div className="panel-stat-card">
|
||||
<div className="panel-stat-number">{stats.lagerstandorte}</div>
|
||||
<div className="panel-stat-label">Lagerstandorte</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="panel-grid">
|
||||
<div className="panel-card">
|
||||
<h2>Filialen verwalten</h2>
|
||||
|
||||
<form className="filiale-add-form" onSubmit={handleAddFiliale}>
|
||||
<input
|
||||
type="text"
|
||||
className="filiale-input"
|
||||
value={newFiliale.name}
|
||||
onChange={(e) => setNewFiliale((f) => ({ ...f, name: e.target.value }))}
|
||||
placeholder="Filialname (z.B. Kaiserslautern)"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
className="filiale-input"
|
||||
value={newFiliale.address}
|
||||
onChange={(e) => setNewFiliale((f) => ({ ...f, address: e.target.value }))}
|
||||
placeholder="Adresse (optional)"
|
||||
/>
|
||||
<button type="submit" className="btn-panel-action" disabled={addingFiliale || !newFiliale.name.trim()}>
|
||||
{addingFiliale ? '...' : 'Filiale hinzufügen'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="panel-list" style={{ marginTop: 16 }}>
|
||||
{locations.length === 0 && <p className="panel-empty">Keine Filialen vorhanden</p>}
|
||||
{locations.map((loc) => (
|
||||
<div key={loc.$id} className={`filiale-admin-item ${loc.isActive ? '' : 'inactive'}`}>
|
||||
{editingId === loc.$id ? (
|
||||
<div className="filiale-edit-row">
|
||||
<input
|
||||
type="text"
|
||||
className="filiale-input"
|
||||
value={editForm.name}
|
||||
onChange={(e) => setEditForm((f) => ({ ...f, name: e.target.value }))}
|
||||
placeholder="Filialname"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
className="filiale-input"
|
||||
value={editForm.address}
|
||||
onChange={(e) => setEditForm((f) => ({ ...f, address: e.target.value }))}
|
||||
placeholder="Adresse"
|
||||
/>
|
||||
<div className="filiale-edit-btns">
|
||||
<button className="btn-action btn-status" onClick={handleSaveEdit}>Speichern</button>
|
||||
<button className="btn-action btn-info" onClick={() => setEditingId(null)}>Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="filiale-admin-info">
|
||||
<strong>{loc.name}</strong>
|
||||
{loc.address && <span className="panel-list-sub">{loc.address}</span>}
|
||||
<span className={`panel-badge ${loc.isActive ? 'active' : 'inactive'}`}>
|
||||
{loc.isActive ? 'Aktiv' : 'Inaktiv'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="filiale-admin-actions">
|
||||
<button className="btn-action btn-info" onClick={() => startEdit(loc)}>Bearbeiten</button>
|
||||
<button className="btn-action btn-status" onClick={() => handleToggleFiliale(loc.$id)}>
|
||||
{loc.isActive ? 'Deaktivieren' : 'Aktivieren'}
|
||||
</button>
|
||||
<button className="btn-action btn-delete" onClick={() => handleDeleteFiliale(loc.$id)}>Löschen</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="panel-card">
|
||||
<h2>Benutzer</h2>
|
||||
<div className="panel-list">
|
||||
{usersList.length === 0 && <p className="panel-empty">Keine Benutzer vorhanden</p>}
|
||||
{usersList.map((u) => {
|
||||
const loc = locations.find((l) => l.$id === u.locationId);
|
||||
return (
|
||||
<div key={u.$id} className="panel-list-item">
|
||||
<div>
|
||||
<strong>{u.userName || u.userId}</strong>
|
||||
<span className="panel-list-sub">{u.role}</span>
|
||||
</div>
|
||||
<span className="panel-list-sub">{loc?.name || '–'}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="panel-card">
|
||||
<h2>Lagerstandorte</h2>
|
||||
<button className="btn-panel-action" onClick={() => setShowLsManager(true)}>
|
||||
Lagerstandorte verwalten
|
||||
</button>
|
||||
<div className="panel-list" style={{ marginTop: 12 }}>
|
||||
{lagerstandorte.map((l) => (
|
||||
<div key={l.$id} className="panel-list-item">
|
||||
<span>{l.name}</span>
|
||||
<span className={`panel-badge ${l.isActive ? 'active' : 'inactive'}`}>
|
||||
{l.isActive ? 'Aktiv' : 'Inaktiv'}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showLsManager && (
|
||||
<LagerstandortManager
|
||||
lagerstandorte={lagerstandorte}
|
||||
onAdd={addLagerstandort}
|
||||
onToggle={toggleLagerstandort}
|
||||
onDelete={deleteLagerstandort}
|
||||
onClose={() => setShowLsManager(false)}
|
||||
/>
|
||||
)}
|
||||
<Toast message={toast.message} color={toast.color} visible={toast.visible} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user