31 von 45 = ca. 69 %
31 punkter der todo liste abgeabeitet
This commit is contained in:
@@ -1,16 +1,20 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { databases, DATABASE_ID } from '../lib/appwrite';
|
||||
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 { useToast } from '@/hooks/useToast';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import LagerstandortManager from './LagerstandortManager';
|
||||
import { useLagerstandorte } from '../hooks/useLagerstandorte';
|
||||
import { useLagerstandorte } from '@/hooks/useLagerstandorte';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
|
||||
export default function AdminPanel() {
|
||||
const { user, userMeta } = useAuth();
|
||||
const { toast, showToast } = useToast();
|
||||
const { showToast } = useToast();
|
||||
const locationId = userMeta?.locationId || '';
|
||||
const { lagerstandorte, addLagerstandort, toggleLagerstandort, deleteLagerstandort } = useLagerstandorte(locationId);
|
||||
|
||||
@@ -115,141 +119,157 @@ export default function AdminPanel() {
|
||||
}
|
||||
}
|
||||
|
||||
const statItems = [
|
||||
{ label: 'Benutzer', value: stats.users },
|
||||
{ label: 'Filialen', value: stats.locations },
|
||||
{ label: 'Assets gesamt', value: stats.assets },
|
||||
{ label: 'Lagerstandorte', value: stats.lagerstandorte },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header showToast={showToast} />
|
||||
<div className="panel-page">
|
||||
<div className="panel-title-bar">
|
||||
<h1>Admin Panel</h1>
|
||||
<p>System-Übersicht und Verwaltung</p>
|
||||
<div className="mx-auto max-w-7xl p-6">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold tracking-tight">Admin Panel</h1>
|
||||
<p className="mt-1 text-muted-foreground">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 className="mb-8 grid grid-cols-2 gap-4 lg:grid-cols-4">
|
||||
{statItems.map((item) => (
|
||||
<Card key={item.label}>
|
||||
<CardContent className="pt-2">
|
||||
<div className="text-3xl font-bold">{item.value}</div>
|
||||
<p className="text-sm text-muted-foreground">{item.label}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="panel-grid">
|
||||
<div className="panel-card">
|
||||
<h2>Filialen verwalten</h2>
|
||||
<div className="grid gap-6 lg:grid-cols-2">
|
||||
{/* Filialen */}
|
||||
<Card className="lg:col-span-2">
|
||||
<CardHeader>
|
||||
<CardTitle>Filialen verwalten</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form className="flex flex-col gap-3 sm:flex-row" onSubmit={handleAddFiliale}>
|
||||
<Input
|
||||
value={newFiliale.name}
|
||||
onChange={(e) => setNewFiliale((f) => ({ ...f, name: e.target.value }))}
|
||||
placeholder="Filialname (z.B. Kaiserslautern)"
|
||||
/>
|
||||
<Input
|
||||
value={newFiliale.address}
|
||||
onChange={(e) => setNewFiliale((f) => ({ ...f, address: e.target.value }))}
|
||||
placeholder="Adresse (optional)"
|
||||
/>
|
||||
<Button type="submit" disabled={addingFiliale || !newFiliale.name.trim()}>
|
||||
{addingFiliale ? '...' : 'Hinzufügen'}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<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>
|
||||
<Separator className="my-4" />
|
||||
|
||||
<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 className="space-y-3">
|
||||
{locations.length === 0 && (
|
||||
<p className="py-4 text-center text-sm text-muted-foreground">Keine Filialen vorhanden</p>
|
||||
)}
|
||||
{locations.map((loc) => (
|
||||
<div
|
||||
key={loc.$id}
|
||||
className={`flex flex-col gap-3 rounded-lg border p-3 sm:flex-row sm:items-center sm:justify-between ${!loc.isActive ? 'opacity-60' : ''}`}
|
||||
>
|
||||
{editingId === loc.$id ? (
|
||||
<div className="flex w-full flex-col gap-2 sm:flex-row sm:items-center">
|
||||
<Input
|
||||
value={editForm.name}
|
||||
onChange={(e) => setEditForm((f) => ({ ...f, name: e.target.value }))}
|
||||
placeholder="Filialname"
|
||||
className="flex-1"
|
||||
/>
|
||||
<Input
|
||||
value={editForm.address}
|
||||
onChange={(e) => setEditForm((f) => ({ ...f, address: e.target.value }))}
|
||||
placeholder="Adresse"
|
||||
className="flex-1"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" onClick={handleSaveEdit}>Speichern</Button>
|
||||
<Button size="sm" variant="outline" onClick={() => setEditingId(null)}>Abbrechen</Button>
|
||||
</div>
|
||||
</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 className="flex items-center gap-3">
|
||||
<span className="font-medium">{loc.name}</span>
|
||||
{loc.address && <span className="text-sm text-muted-foreground">{loc.address}</span>}
|
||||
<Badge variant={loc.isActive ? 'default' : 'outline'}>
|
||||
{loc.isActive ? 'Aktiv' : 'Inaktiv'}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" variant="outline" onClick={() => startEdit(loc)}>Bearbeiten</Button>
|
||||
<Button size="sm" variant="secondary" onClick={() => handleToggleFiliale(loc.$id)}>
|
||||
{loc.isActive ? 'Deaktivieren' : 'Aktivieren'}
|
||||
</Button>
|
||||
<Button size="sm" variant="destructive" onClick={() => handleDeleteFiliale(loc.$id)}>Löschen</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<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>
|
||||
{/* Benutzer */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Benutzer</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{usersList.length === 0 && (
|
||||
<p className="py-4 text-center text-sm text-muted-foreground">Keine Benutzer vorhanden</p>
|
||||
)}
|
||||
{usersList.map((u) => {
|
||||
const loc = locations.find((l) => l.$id === u.locationId);
|
||||
return (
|
||||
<div key={u.$id} className="flex items-center justify-between rounded-lg border p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">{u.userName || u.userId}</span>
|
||||
<Badge variant="secondary">{u.role}</Badge>
|
||||
</div>
|
||||
<span className="text-sm text-muted-foreground">{loc?.name || '–'}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Lagerstandorte */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Lagerstandorte</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button className="mb-4 w-full" onClick={() => setShowLsManager(true)}>
|
||||
Lagerstandorte verwalten
|
||||
</Button>
|
||||
<div className="space-y-2">
|
||||
{lagerstandorte.map((l) => (
|
||||
<div key={l.$id} className="flex items-center justify-between rounded-lg border p-3">
|
||||
<span className="text-sm">{l.name}</span>
|
||||
<Badge variant={l.isActive ? 'default' : 'outline'}>
|
||||
{l.isActive ? 'Aktiv' : 'Inaktiv'}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -262,7 +282,6 @@ export default function AdminPanel() {
|
||||
onClose={() => setShowLsManager(false)}
|
||||
/>
|
||||
)}
|
||||
<Toast message={toast.message} color={toast.color} visible={toast.visible} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user