feat: Allow primary users to access user management and create sub-users
Frontend: - Extend UserManagementPage for both ADMIN and primary users - Admins see all primary users (excluding other admins) - Primary users see only their sub-users - Add create sub-user form for primary users - Adjust UI text based on user role - Fix table columns (hide company column for primary users)
This commit is contained in:
@@ -3,10 +3,9 @@ import { apiGet, apiPost } from "../lib/api";
|
||||
import { useSession } from "../lib/session";
|
||||
import type { UserRow } from "../lib/types";
|
||||
|
||||
interface PrimaryUserRow {
|
||||
interface SubUserRow {
|
||||
id: string;
|
||||
displayName: string;
|
||||
companyName: string | null;
|
||||
email: string | null;
|
||||
active: boolean;
|
||||
updatedAt: string;
|
||||
@@ -14,27 +13,49 @@ interface PrimaryUserRow {
|
||||
|
||||
export default function UserManagementPage() {
|
||||
const { user } = useSession();
|
||||
const [users, setUsers] = useState<PrimaryUserRow[]>([]);
|
||||
const [users, setUsers] = useState<SubUserRow[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
const [showCreateForm, setShowCreateForm] = useState(false);
|
||||
const isAdmin = user?.role === "ADMIN";
|
||||
const isPrimaryUser = user?.primaryUser === true;
|
||||
const canManageUsers = isAdmin || isPrimaryUser;
|
||||
|
||||
// Form state for creating new sub-user
|
||||
const [newUserName, setNewUserName] = useState("");
|
||||
const [newUserEmail, setNewUserEmail] = useState("");
|
||||
const [newUserPassword, setNewUserPassword] = useState("");
|
||||
const [creating, setCreating] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadUsers() {
|
||||
try {
|
||||
const response = await apiGet<UserRow[]>("/portal/users");
|
||||
// Nur Hauptnutzer (primaryUser=true) anzeigen, aber Admin ausblenden
|
||||
if (isAdmin) {
|
||||
// Admin sieht alle Hauptnutzer (außer andere Admins)
|
||||
const primaryUsers = response
|
||||
.filter((u) => u.primaryUser && u.role !== "ADMIN")
|
||||
.map((u) => ({
|
||||
id: u.id,
|
||||
displayName: u.displayName,
|
||||
companyName: u.companyName,
|
||||
email: u.email,
|
||||
active: u.active,
|
||||
updatedAt: u.updatedAt,
|
||||
}));
|
||||
setUsers(primaryUsers);
|
||||
} else {
|
||||
// Hauptnutzer sieht alle seine Unterbenutzer (nicht-primary)
|
||||
const subUsers = response
|
||||
.filter((u) => !u.primaryUser)
|
||||
.map((u) => ({
|
||||
id: u.id,
|
||||
displayName: u.displayName,
|
||||
email: u.email,
|
||||
active: u.active,
|
||||
updatedAt: u.updatedAt,
|
||||
}));
|
||||
setUsers(subUsers);
|
||||
}
|
||||
} catch (error) {
|
||||
setMessage((error as Error).message);
|
||||
} finally {
|
||||
@@ -43,7 +64,7 @@ export default function UserManagementPage() {
|
||||
}
|
||||
|
||||
void loadUsers();
|
||||
}, []);
|
||||
}, [isAdmin]);
|
||||
|
||||
async function toggleUserStatus(userId: string, newStatus: boolean) {
|
||||
try {
|
||||
@@ -65,14 +86,54 @@ export default function UserManagementPage() {
|
||||
}.`
|
||||
);
|
||||
|
||||
// Nachricht nach 3 Sekunden ausblenden
|
||||
setTimeout(() => setMessage(null), 3000);
|
||||
} catch (error) {
|
||||
setMessage((error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
// Formatierungsfunktion für das Datum
|
||||
async function handleCreateUser(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
if (!newUserName.trim() || !newUserEmail.trim() || !newUserPassword.trim()) {
|
||||
setMessage("Bitte alle Felder ausfüllen.");
|
||||
return;
|
||||
}
|
||||
|
||||
setCreating(true);
|
||||
try {
|
||||
await apiPost("/portal/users", {
|
||||
displayName: newUserName.trim(),
|
||||
email: newUserEmail.trim(),
|
||||
password: newUserPassword,
|
||||
});
|
||||
|
||||
// Reload users
|
||||
const response = await apiGet<UserRow[]>("/portal/users");
|
||||
const subUsers = response
|
||||
.filter((u) => !u.primaryUser)
|
||||
.map((u) => ({
|
||||
id: u.id,
|
||||
displayName: u.displayName,
|
||||
email: u.email,
|
||||
active: u.active,
|
||||
updatedAt: u.updatedAt,
|
||||
}));
|
||||
setUsers(subUsers);
|
||||
|
||||
// Reset form
|
||||
setNewUserName("");
|
||||
setNewUserEmail("");
|
||||
setNewUserPassword("");
|
||||
setShowCreateForm(false);
|
||||
setMessage(`Benutzer "${newUserName}" wurde erstellt.`);
|
||||
setTimeout(() => setMessage(null), 3000);
|
||||
} catch (error) {
|
||||
setMessage((error as Error).message);
|
||||
} finally {
|
||||
setCreating(false);
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(value: string) {
|
||||
return new Intl.DateTimeFormat("de-DE", {
|
||||
dateStyle: "medium",
|
||||
@@ -80,13 +141,12 @@ export default function UserManagementPage() {
|
||||
}).format(new Date(value));
|
||||
}
|
||||
|
||||
// Nicht-Admin Ansicht (sollte nicht passieren, da Route geschützt ist)
|
||||
if (!isAdmin) {
|
||||
if (!canManageUsers) {
|
||||
return (
|
||||
<div className="page-stack">
|
||||
<section className="section-card">
|
||||
<div className="alert alert--error">
|
||||
Zugriff verweigert. Diese Seite ist nur für Administratoren.
|
||||
Zugriff verweigert. Diese Seite ist nur für Administratoren und Hauptbenutzer.
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -99,10 +159,11 @@ export default function UserManagementPage() {
|
||||
<section className="hero-card admin-hero">
|
||||
<div>
|
||||
<p className="eyebrow">Benutzerverwaltung</p>
|
||||
<h3>Hauptnutzer freigeben oder sperren</h3>
|
||||
<h3>{isAdmin ? "Hauptnutzer freigeben oder sperren" : "Unterbenutzer verwalten"}</h3>
|
||||
<p className="muted-text">
|
||||
Verwalten Sie den Zugriff von Hauptnutzern auf das System.
|
||||
Gesperrte Benutzer können sich nicht mehr anmelden.
|
||||
{isAdmin
|
||||
? "Verwalten Sie den Zugriff von Hauptnutzern auf das System. Gesperrte Benutzer können sich nicht mehr anmelden."
|
||||
: "Erstellen und verwalten Sie Unterbenutzer für Ihr Konto. Unterbenutzer können Proben registrieren und bearbeiten."}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -111,7 +172,9 @@ export default function UserManagementPage() {
|
||||
{message ? (
|
||||
<div
|
||||
className={
|
||||
message.includes("freigegeben") || message.includes("gesperrt")
|
||||
message.includes("freigegeben") ||
|
||||
message.includes("gesperrt") ||
|
||||
message.includes("erstellt")
|
||||
? "alert alert--success"
|
||||
: "alert alert--error"
|
||||
}
|
||||
@@ -120,26 +183,107 @@ export default function UserManagementPage() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* Tabelle mit Hauptnutzern */}
|
||||
{/* Create Sub-user Form (nur für Hauptnutzer) */}
|
||||
{isPrimaryUser && !isAdmin && (
|
||||
<section className="section-card">
|
||||
{!showCreateForm ? (
|
||||
<div className="section-card__header">
|
||||
<div>
|
||||
<p className="eyebrow">Neuer Unterbenutzer</p>
|
||||
<h3>Benutzer anlegen</h3>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="accent-button"
|
||||
onClick={() => setShowCreateForm(true)}
|
||||
>
|
||||
+ Benutzer anlegen
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="section-card__header">
|
||||
<div>
|
||||
<p className="eyebrow">Neuer Unterbenutzer</p>
|
||||
<h3>Benutzer anlegen</h3>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="ghost-button"
|
||||
onClick={() => setShowCreateForm(false)}
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
<form onSubmit={handleCreateUser} className="field-grid field-grid--2col">
|
||||
<label className="field">
|
||||
<span>Name</span>
|
||||
<input
|
||||
type="text"
|
||||
value={newUserName}
|
||||
onChange={(e) => setNewUserName(e.target.value)}
|
||||
placeholder="Name des Benutzers"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>E-Mail</span>
|
||||
<input
|
||||
type="email"
|
||||
value={newUserEmail}
|
||||
onChange={(e) => setNewUserEmail(e.target.value)}
|
||||
placeholder="email@beispiel.de"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>Passwort</span>
|
||||
<input
|
||||
type="password"
|
||||
value={newUserPassword}
|
||||
onChange={(e) => setNewUserPassword(e.target.value)}
|
||||
placeholder="Passwort"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<div className="field" style={{ display: "flex", alignItems: "flex-end" }}>
|
||||
<button
|
||||
type="submit"
|
||||
className="accent-button"
|
||||
disabled={creating}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{creating ? "Wird erstellt..." : "Benutzer erstellen"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Tabelle mit Benutzern */}
|
||||
<section className="section-card">
|
||||
<div className="section-card__header">
|
||||
<div>
|
||||
<p className="eyebrow">Hauptnutzer</p>
|
||||
<h3>Registrierte Hauptnutzer</h3>
|
||||
<p className="eyebrow">{isAdmin ? "Hauptnutzer" : "Unterbenutzer"}</p>
|
||||
<h3>{isAdmin ? "Registrierte Hauptnutzer" : "Ihre Unterbenutzer"}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="empty-state">Benutzer werden geladen...</div>
|
||||
) : users.length === 0 ? (
|
||||
<div className="empty-state">Keine Hauptnutzer vorhanden.</div>
|
||||
<div className="empty-state">
|
||||
{isAdmin ? "Keine Hauptnutzer vorhanden." : "Keine Unterbenutzer vorhanden."}
|
||||
</div>
|
||||
) : (
|
||||
<div className="table-shell">
|
||||
<table className="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Firma</th>
|
||||
{isAdmin && <th>Firma</th>}
|
||||
<th>E-Mail</th>
|
||||
<th>Status</th>
|
||||
<th>Letzte Änderung</th>
|
||||
@@ -152,7 +296,7 @@ export default function UserManagementPage() {
|
||||
<td>
|
||||
<strong>{entry.displayName}</strong>
|
||||
</td>
|
||||
<td>{entry.companyName ?? "-"}</td>
|
||||
{isAdmin && <td>{(entry as SubUserRow & { companyName?: string }).companyName ?? "-"}</td>}
|
||||
<td>{entry.email ?? "-"}</td>
|
||||
<td>
|
||||
<span
|
||||
@@ -188,9 +332,9 @@ export default function UserManagementPage() {
|
||||
<div className="info-panel">
|
||||
<strong>Hinweis</strong>
|
||||
<p>
|
||||
Hauptnutzer sind die primären Kontoinhaber. Wenn Sie einen Hauptnutzer sperren,
|
||||
können sich dieser und alle zugehörigen Nebennutzer nicht mehr anmelden.
|
||||
Die Daten bleiben erhalten und können durch Freigabe wieder aktiviert werden.
|
||||
{isAdmin
|
||||
? "Hauptnutzer sind die primären Kontoinhaber. Wenn Sie einen Hauptnutzer sperren, können sich dieser und alle zugehörigen Nebennutzer nicht mehr anmelden. Die Daten bleiben erhalten und können durch Freigabe wieder aktiviert werden."
|
||||
: "Unterbenutzer können Proben registrieren und bearbeiten, aber keine neuen Benutzer anlegen. Wenn Sie einen Unterbenutzer sperren, kann sich dieser nicht mehr anmelden."}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user