Phase 6.3: SettingsForms on RHF + Zod
- SettingsCurrencies: RHF+Zod (code required max 3, name required, symbol required max 5) - SettingsTaxes: RHF+Zod (name required, rate numeric 0-100, country max 2) - SettingsSequences: RHF+Zod (name required, padding numeric 1-10) - SettingsUsers: RHF+Zod (name required, email valid, password min 8) - SettingsRoles: RHF+Zod for create form (name required) - SettingsGroups: RHF+Zod for create form (name required, description optional) - Error display under each field - Preserved all existing functionality: CRUD, permissions, members - Added 2 validation tests for SettingsCurrencies
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
useGroups,
|
||||
useCreateGroup,
|
||||
@@ -81,9 +84,19 @@ export function SettingsGroupsPage() {
|
||||
const updateGroupMutation = useUpdateGroup();
|
||||
const deleteGroupMutation = useDeleteGroup();
|
||||
|
||||
// ── Zod Schema for create form ──
|
||||
const groupCreateSchema = z.object({
|
||||
name: z.string().min(1, 'required'),
|
||||
description: z.string().optional().default(''),
|
||||
});
|
||||
type GroupCreateFormData = z.infer<typeof groupCreateSchema>;
|
||||
|
||||
const { register: registerGroup, handleSubmit: handleSubmitGroup, reset: resetGroup, formState: { errors: groupErrors } } = useForm<GroupCreateFormData>({
|
||||
resolver: zodResolver(groupCreateSchema),
|
||||
defaultValues: { name: '', description: '' },
|
||||
});
|
||||
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [newGroupName, setNewGroupName] = useState('');
|
||||
const [newGroupDescription, setNewGroupDescription] = useState('');
|
||||
const [newGroupPermissions, setNewGroupPermissions] = useState<Record<string, any>>({});
|
||||
const [newGroupDenied, setNewGroupDenied] = useState<string[]>([]);
|
||||
const [editingGroup, setEditingGroup] = useState<EditingGroup | null>(null);
|
||||
@@ -117,22 +130,17 @@ export function SettingsGroupsPage() {
|
||||
return groups;
|
||||
}, [allPermissions]);
|
||||
|
||||
const handleCreateGroup = async () => {
|
||||
if (!newGroupName.trim()) {
|
||||
toast.error(t('validation.required', 'Pflichtfeld'));
|
||||
return;
|
||||
}
|
||||
const handleCreateGroup = async (data: GroupCreateFormData) => {
|
||||
try {
|
||||
await createGroupMutation.mutateAsync({
|
||||
name: newGroupName.trim(),
|
||||
description: newGroupDescription.trim() || null,
|
||||
name: data.name.trim(),
|
||||
description: data.description.trim() || null,
|
||||
permissions: newGroupPermissions,
|
||||
denied_permissions: newGroupDenied,
|
||||
field_permissions: {},
|
||||
});
|
||||
toast.success(t('settings.groupCreated', 'Gruppe erstellt'));
|
||||
setNewGroupName('');
|
||||
setNewGroupDescription('');
|
||||
resetGroup({ name: '', description: '' });
|
||||
setNewGroupPermissions({});
|
||||
setNewGroupDenied([]);
|
||||
setCreateOpen(false);
|
||||
@@ -243,7 +251,7 @@ export function SettingsGroupsPage() {
|
||||
<h1 className="text-2xl font-bold text-secondary-900">
|
||||
{t('settings.groups', 'Gruppen')}
|
||||
</h1>
|
||||
<Button onClick={() => setCreateOpen(true)} data-testid="create-group-btn">
|
||||
<Button onClick={() => { resetGroup({ name: '', description: '' }); setCreateOpen(true); }} data-testid="create-group-btn">
|
||||
{t('settings.createGroup', 'Gruppe erstellen')}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -317,18 +325,17 @@ export function SettingsGroupsPage() {
|
||||
title={t('settings.createGroup', 'Gruppe erstellen')}
|
||||
size="lg"
|
||||
>
|
||||
<div className="space-y-4" data-testid="create-group-form">
|
||||
<form onSubmit={handleSubmitGroup(handleCreateGroup)} className="space-y-4" data-testid="create-group-form">
|
||||
<Input
|
||||
label={t('settings.groupName', 'Gruppenname')}
|
||||
value={newGroupName}
|
||||
onChange={(e) => setNewGroupName(e.target.value)}
|
||||
{...registerGroup('name')}
|
||||
error={groupErrors.name?.message === 'required' ? t('validation.required') : undefined}
|
||||
placeholder={t('settings.groupName', 'Gruppenname')}
|
||||
data-testid="new-group-name"
|
||||
/>
|
||||
<Input
|
||||
label={t('settings.groupDescription', 'Beschreibung')}
|
||||
value={newGroupDescription}
|
||||
onChange={(e) => setNewGroupDescription(e.target.value)}
|
||||
{...registerGroup('description')}
|
||||
placeholder={t('settings.groupDescription', 'Beschreibung')}
|
||||
data-testid="new-group-description"
|
||||
/>
|
||||
@@ -384,14 +391,14 @@ export function SettingsGroupsPage() {
|
||||
<div className="flex justify-end gap-3">
|
||||
<Button variant="secondary" onClick={() => setCreateOpen(false)}>{t('common.cancel')}</Button>
|
||||
<Button
|
||||
onClick={handleCreateGroup}
|
||||
type="submit"
|
||||
isLoading={createGroupMutation.isPending}
|
||||
data-testid="save-group-btn"
|
||||
>
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
{/* Edit Group Modal */}
|
||||
|
||||
Reference in New Issue
Block a user