338 lines
11 KiB
TypeScript
338 lines
11 KiB
TypeScript
// app/(app)/users/UsersHeaderClient.tsx
|
|
'use client';
|
|
|
|
import { useState, FormEvent } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import Modal from '@/components/ui/Modal';
|
|
import Button from '@/components/ui/Button';
|
|
import { PlusIcon } from '@heroicons/react/24/outline';
|
|
import UsersCsvImportButton from './UsersCsvImportButton';
|
|
import Switch from '@/components/ui/Switch';
|
|
|
|
type SimpleGroup = {
|
|
id: string;
|
|
name: string;
|
|
};
|
|
|
|
type Props = {
|
|
groups: SimpleGroup[];
|
|
};
|
|
|
|
export default function UsersHeaderClient({ groups }: Props) {
|
|
const router = useRouter();
|
|
|
|
const [personModalOpen, setUserModalOpen] = useState(false);
|
|
const [groupModalOpen, setGroupModalOpen] = useState(false);
|
|
|
|
// User-Form State (angepasst an neues Schema)
|
|
const [arbeitsname, setArbeitsname] = useState('');
|
|
const [firstName, setFirstName] = useState('');
|
|
const [lastName, setLastName] = useState('');
|
|
const [personGroupId, setUserGroupId] = useState<'none' | string>('none');
|
|
const [savingUser, setSavingUser] = useState(false);
|
|
const [personError, setUserError] = useState<string | null>(null);
|
|
|
|
// Gruppen-Form State
|
|
const [groupName, setGroupName] = useState('');
|
|
const [savingGroup, setSavingGroup] = useState(false);
|
|
const [groupError, setGroupError] = useState<string | null>(null);
|
|
|
|
const [groupCanEditDevices, setGroupCanEditDevices] = useState<boolean>(false);
|
|
|
|
async function handleCreateUser(e: FormEvent) {
|
|
e.preventDefault();
|
|
setSavingUser(true);
|
|
setUserError(null);
|
|
|
|
try {
|
|
const res = await fetch('/api/users', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
arbeitsname,
|
|
firstName,
|
|
lastName,
|
|
groupId: personGroupId === 'none' ? null : personGroupId,
|
|
}),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const data = await res.json().catch(() => null);
|
|
throw new Error(
|
|
data?.error ?? `Fehler beim Anlegen (HTTP ${res.status})`,
|
|
);
|
|
}
|
|
|
|
setArbeitsname('');
|
|
setFirstName('');
|
|
setLastName('');
|
|
setUserGroupId('none');
|
|
setUserModalOpen(false);
|
|
router.refresh();
|
|
} catch (err: any) {
|
|
console.error('Error creating person', err);
|
|
setUserError(
|
|
err instanceof Error
|
|
? err.message
|
|
: 'Fehler beim Anlegen der User.',
|
|
);
|
|
} finally {
|
|
setSavingUser(false);
|
|
}
|
|
}
|
|
|
|
async function handleCreateGroup(e: FormEvent) {
|
|
e.preventDefault();
|
|
setSavingGroup(true);
|
|
setGroupError(null);
|
|
|
|
try {
|
|
const res = await fetch('/api/user-groups', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
name: groupName,
|
|
canEditDevices: groupCanEditDevices, // 👈 NEU
|
|
}),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const data = await res.json().catch(() => null);
|
|
throw new Error(
|
|
data?.error ?? `Fehler beim Anlegen (HTTP ${res.status})`,
|
|
);
|
|
}
|
|
|
|
setGroupName('');
|
|
setGroupCanEditDevices(false); // Reset
|
|
setGroupModalOpen(false);
|
|
router.refresh();
|
|
} catch (err: any) {
|
|
console.error('Error creating group', err);
|
|
setGroupError(
|
|
err instanceof Error
|
|
? err.message
|
|
: 'Fehler beim Anlegen der Gruppe.',
|
|
);
|
|
} finally {
|
|
setSavingGroup(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
<div>
|
|
<h1 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
|
Personen
|
|
</h1>
|
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
Personen verwalten und in Gruppen einteilen.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap gap-2 items-start">
|
|
<Button
|
|
type="button"
|
|
variant="soft"
|
|
tone="indigo"
|
|
size="lg"
|
|
icon={<PlusIcon className="size-5" />}
|
|
onClick={() => setUserModalOpen(true)}
|
|
>
|
|
Neue Person
|
|
</Button>
|
|
<Button
|
|
type="button"
|
|
variant="soft"
|
|
tone="indigo"
|
|
size="lg"
|
|
icon={<PlusIcon className="size-5" />}
|
|
onClick={() => setGroupModalOpen(true)}
|
|
>
|
|
Neue Gruppe
|
|
</Button>
|
|
|
|
{/* Neuer CSV-Import als eigene Komponente */}
|
|
<UsersCsvImportButton groups={groups} />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Modal: Neue Person */}
|
|
<Modal
|
|
open={personModalOpen}
|
|
onClose={() => setUserModalOpen(false)}
|
|
title="Neue Person anlegen"
|
|
tone="info"
|
|
variant="centered"
|
|
size="md"
|
|
primaryAction={{
|
|
label: savingUser ? 'Speichere …' : 'User anlegen',
|
|
onClick: () => {
|
|
const form = document.getElementById(
|
|
'new-person-form',
|
|
) as HTMLFormElement | null;
|
|
form?.requestSubmit();
|
|
},
|
|
variant: 'primary',
|
|
}}
|
|
secondaryAction={{
|
|
label: 'Abbrechen',
|
|
onClick: () => setUserModalOpen(false),
|
|
variant: 'secondary',
|
|
}}
|
|
>
|
|
<form
|
|
id="new-person-form"
|
|
className="space-y-3 text-sm"
|
|
onSubmit={handleCreateUser}
|
|
>
|
|
{/* Arbeitsname */}
|
|
<div>
|
|
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300">
|
|
Arbeitsname *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
className="mt-1 block w-full rounded-md border border-gray-300 bg-white px-2.5 py-1.5 text-sm text-gray-900 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100"
|
|
value={arbeitsname}
|
|
onChange={(e) => setArbeitsname(e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
{/* Vorname / Nachname */}
|
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
<div>
|
|
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300">
|
|
Vorname *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
className="mt-1 block w-full rounded-md border border-gray-300 bg-white px-2.5 py-1.5 text-sm text-gray-900 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100"
|
|
value={firstName}
|
|
onChange={(e) => setFirstName(e.target.value)}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300">
|
|
Nachname *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
className="mt-1 block w-full rounded-md border border-gray-300 bg-white px-2.5 py-1.5 text-sm text-gray-900 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100"
|
|
value={lastName}
|
|
onChange={(e) => setLastName(e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Gruppe */}
|
|
<div>
|
|
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300">
|
|
Gruppe
|
|
</label>
|
|
<select
|
|
className="mt-1 block w-full rounded-md border border-gray-300 bg-white px-2.5 py-1.5 text-xs text-gray-900 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100"
|
|
value={personGroupId}
|
|
onChange={(e) =>
|
|
setUserGroupId(e.target.value as 'none' | string)
|
|
}
|
|
>
|
|
<option value="none">Ohne Gruppe</option>
|
|
{groups.map((g) => (
|
|
<option key={g.id} value={g.id}>
|
|
{g.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{personError && (
|
|
<p className="text-xs text-red-600 dark:text-red-400">
|
|
{personError}
|
|
</p>
|
|
)}
|
|
|
|
<p className="text-[11px] text-gray-500 dark:text-gray-400">
|
|
Felder mit * sind Pflichtfelder.
|
|
</p>
|
|
</form>
|
|
</Modal>
|
|
|
|
{/* Modal: Neue Gruppe */}
|
|
<Modal
|
|
open={groupModalOpen}
|
|
onClose={() => setGroupModalOpen(false)}
|
|
title="Neue Gruppe anlegen"
|
|
tone="info"
|
|
variant="centered"
|
|
size="sm"
|
|
primaryAction={{
|
|
label: savingGroup ? 'Speichere …' : 'Gruppe anlegen',
|
|
onClick: () => {
|
|
const form = document.getElementById(
|
|
'new-group-form',
|
|
) as HTMLFormElement | null;
|
|
form?.requestSubmit();
|
|
},
|
|
variant: 'primary',
|
|
}}
|
|
secondaryAction={{
|
|
label: 'Abbrechen',
|
|
onClick: () => setGroupModalOpen(false),
|
|
variant: 'secondary',
|
|
}}
|
|
>
|
|
<form
|
|
id="new-group-form"
|
|
className="space-y-4 text-sm"
|
|
onSubmit={handleCreateGroup}
|
|
>
|
|
<div>
|
|
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300">
|
|
Gruppenname *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
className="mt-1 block w-full rounded-md border border-gray-300 bg-white px-2.5 py-1.5 text-sm text-gray-900 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100"
|
|
value={groupName}
|
|
onChange={(e) => setGroupName(e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div className="flex flex-col">
|
|
<span className="text-xs font-medium text-gray-700 dark:text-gray-300">
|
|
Darf Geräte bearbeiten
|
|
</span>
|
|
<span className="text-[11px] text-gray-500 dark:text-gray-400">
|
|
Mitglieder dieser Gruppe können Geräte anlegen, bearbeiten und löschen.
|
|
</span>
|
|
</div>
|
|
|
|
|
|
{/* 👇 Hier deine Switch-Komponente */}
|
|
<Switch
|
|
id="group-can-edit-devices"
|
|
name="group-can-edit-devices"
|
|
checked={groupCanEditDevices}
|
|
onChange={setGroupCanEditDevices}
|
|
ariaLabel="Gruppe darf Geräte bearbeiten"
|
|
/>
|
|
</div>
|
|
|
|
{groupError && (
|
|
<p className="text-xs text-red-600 dark:text-red-400">
|
|
{groupError}
|
|
</p>
|
|
)}
|
|
</form>
|
|
</Modal>
|
|
</>
|
|
);
|
|
}
|