geraete/app/(app)/users/UsersHeaderClient.tsx
2025-11-26 15:00:05 +01:00

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>
</>
);
}