feat: enhance accessibility and testing with data-test attributes and improve error handling
This commit is contained in:
@@ -83,6 +83,7 @@ export function CreateProtocolForm({
|
||||
<FormLabel>Titel *</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
data-test="protocol-title-input"
|
||||
placeholder="z.B. Vorstandssitzung März 2026"
|
||||
{...field}
|
||||
/>
|
||||
@@ -100,7 +101,11 @@ export function CreateProtocolForm({
|
||||
<FormItem>
|
||||
<FormLabel>Sitzungsdatum *</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="date" {...field} />
|
||||
<Input
|
||||
data-test="protocol-date-input"
|
||||
type="date"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -115,6 +120,7 @@ export function CreateProtocolForm({
|
||||
<FormLabel>Sitzungsart *</FormLabel>
|
||||
<FormControl>
|
||||
<select
|
||||
data-test="protocol-type-select"
|
||||
{...field}
|
||||
className="border-input bg-background flex h-10 w-full rounded-md border px-3 py-2 text-sm"
|
||||
>
|
||||
@@ -136,7 +142,11 @@ export function CreateProtocolForm({
|
||||
<FormItem>
|
||||
<FormLabel>Ort</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="z.B. Vereinsheim" {...field} />
|
||||
<Input
|
||||
data-test="protocol-location-input"
|
||||
placeholder="z.B. Vereinsheim"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -223,10 +233,19 @@ export function CreateProtocolForm({
|
||||
|
||||
{/* Submit */}
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" onClick={() => router.back()}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
data-test="protocol-cancel-btn"
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button type="submit" disabled={isPending}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
data-test="protocol-submit-btn"
|
||||
>
|
||||
{isPending ? 'Wird gespeichert...' : 'Protokoll erstellen'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
interface MeetingsTabNavigationProps {
|
||||
@@ -10,9 +11,9 @@ interface MeetingsTabNavigationProps {
|
||||
}
|
||||
|
||||
const TABS = [
|
||||
{ id: 'overview', label: 'Übersicht', path: '' },
|
||||
{ id: 'protocols', label: 'Protokolle', path: '/protocols' },
|
||||
{ id: 'tasks', label: 'Offene Aufgaben', path: '/tasks' },
|
||||
{ id: 'overview', i18nKey: 'meetings:nav.overview', path: '' },
|
||||
{ id: 'protocols', i18nKey: 'meetings:nav.protocols', path: '/protocols' },
|
||||
{ id: 'tasks', i18nKey: 'meetings:nav.tasks', path: '/tasks' },
|
||||
] as const;
|
||||
|
||||
export function MeetingsTabNavigation({
|
||||
@@ -25,7 +26,7 @@ export function MeetingsTabNavigation({
|
||||
<div className="mb-6 border-b">
|
||||
<nav
|
||||
className="-mb-px flex space-x-1 overflow-x-auto"
|
||||
aria-label="Sitzungsprotokolle Navigation"
|
||||
aria-label="Meeting Protocols Navigation"
|
||||
>
|
||||
{TABS.map((tab) => {
|
||||
const isActive = tab.id === activeTab;
|
||||
@@ -34,6 +35,7 @@ export function MeetingsTabNavigation({
|
||||
<Link
|
||||
key={tab.id}
|
||||
href={`${basePath}${tab.path}`}
|
||||
data-test={`meetings-tab-${tab.id}`}
|
||||
className={cn(
|
||||
'border-b-2 px-4 py-2.5 text-sm font-medium whitespace-nowrap transition-colors',
|
||||
isActive
|
||||
@@ -41,7 +43,7 @@ export function MeetingsTabNavigation({
|
||||
: 'text-muted-foreground hover:border-muted-foreground/30 hover:text-foreground border-transparent',
|
||||
)}
|
||||
>
|
||||
{tab.label}
|
||||
<Trans i18nKey={tab.i18nKey} />
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -174,6 +174,7 @@ export function OpenTasksView({
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
data-test="tasks-prev-btn"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={page <= 1}
|
||||
@@ -182,6 +183,7 @@ export function OpenTasksView({
|
||||
Zurück
|
||||
</Button>
|
||||
<Button
|
||||
data-test="tasks-next-btn"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={page >= totalPages}
|
||||
|
||||
@@ -141,6 +141,7 @@ export function ProtocolItemsList({
|
||||
</td>
|
||||
<td className="p-3 text-center">
|
||||
<Badge
|
||||
data-test="item-status-toggle"
|
||||
variant={
|
||||
(ITEM_STATUS_COLORS[item.status] as
|
||||
| 'default'
|
||||
@@ -156,6 +157,7 @@ export function ProtocolItemsList({
|
||||
</td>
|
||||
<td className="p-3 text-right">
|
||||
<Button
|
||||
data-test="item-delete-btn"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
|
||||
@@ -69,16 +69,16 @@ export function ProtocolsDataTable({
|
||||
);
|
||||
|
||||
const handleSearch = useCallback(
|
||||
(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
(formEvent: React.FormEvent) => {
|
||||
formEvent.preventDefault();
|
||||
updateParams({ q: form.getValues('search') });
|
||||
},
|
||||
[form, updateParams],
|
||||
);
|
||||
|
||||
const handleTypeChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
updateParams({ type: e.target.value });
|
||||
(changeEvent: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
updateParams({ type: changeEvent.target.value });
|
||||
},
|
||||
[updateParams],
|
||||
);
|
||||
@@ -96,17 +96,24 @@ export function ProtocolsDataTable({
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<form onSubmit={handleSearch} className="flex gap-2">
|
||||
<Input
|
||||
data-test="protocols-search-input"
|
||||
placeholder="Protokoll suchen..."
|
||||
className="w-64"
|
||||
{...form.register('search')}
|
||||
/>
|
||||
<Button type="submit" variant="outline" size="sm">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
data-test="protocols-search-btn"
|
||||
>
|
||||
Suchen
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<select
|
||||
data-test="protocols-type-filter"
|
||||
value={currentType}
|
||||
onChange={handleTypeChange}
|
||||
className="border-input bg-background flex h-9 rounded-md border px-3 py-1 text-sm shadow-sm"
|
||||
@@ -119,7 +126,7 @@ export function ProtocolsDataTable({
|
||||
</select>
|
||||
|
||||
<Link href={`/home/${account}/meetings/protocols/new`}>
|
||||
<Button size="sm">
|
||||
<Button size="sm" data-test="protocols-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neues Protokoll
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user