feat: add data-test attributes for improved testing in various components
This commit is contained in:
@@ -95,6 +95,7 @@ export function CatchBooksDataTable({
|
||||
<select
|
||||
value={currentYear}
|
||||
onChange={handleYearChange}
|
||||
data-test="catchbooks-year-filter"
|
||||
className="border-input bg-background flex h-9 rounded-md border px-3 py-1 text-sm shadow-sm"
|
||||
>
|
||||
<option value="">Alle Jahre</option>
|
||||
@@ -108,6 +109,7 @@ export function CatchBooksDataTable({
|
||||
<select
|
||||
value={currentStatus}
|
||||
onChange={handleStatusChange}
|
||||
data-test="catchbooks-status-filter"
|
||||
className="border-input bg-background flex h-9 rounded-md border px-3 py-1 text-sm shadow-sm"
|
||||
>
|
||||
{STATUS_OPTIONS.map((opt) => (
|
||||
|
||||
@@ -59,7 +59,7 @@ export function CompetitionsDataTable({
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold">Wettbewerbe ({total})</h2>
|
||||
<Link href={`/home/${account}/fischerei/competitions/new`}>
|
||||
<Button size="sm">
|
||||
<Button size="sm" data-test="competitions-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neuer Wettbewerb
|
||||
</Button>
|
||||
|
||||
@@ -247,10 +247,19 @@ export function CreateSpeciesForm({
|
||||
|
||||
{/* Submit */}
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" onClick={() => router.back()}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => router.back()}
|
||||
data-test="species-cancel-btn"
|
||||
>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button type="submit" disabled={isPending}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
data-test="species-submit-btn"
|
||||
>
|
||||
{isPending
|
||||
? 'Wird gespeichert...'
|
||||
: isEdit
|
||||
|
||||
@@ -86,6 +86,7 @@ export function CreateStockingForm({
|
||||
<FormControl>
|
||||
<select
|
||||
{...field}
|
||||
data-test="stocking-water-select"
|
||||
className="border-input bg-background flex h-10 w-full rounded-md border px-3 py-2 text-sm"
|
||||
>
|
||||
<option value="">— Gewässer wählen —</option>
|
||||
@@ -109,6 +110,7 @@ export function CreateStockingForm({
|
||||
<FormControl>
|
||||
<select
|
||||
{...field}
|
||||
data-test="stocking-species-select"
|
||||
className="border-input bg-background flex h-10 w-full rounded-md border px-3 py-2 text-sm"
|
||||
>
|
||||
<option value="">— Fischart wählen —</option>
|
||||
@@ -253,7 +255,11 @@ export function CreateStockingForm({
|
||||
<Button type="button" variant="outline" onClick={() => router.back()}>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button type="submit" disabled={isPending}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
data-test="stocking-submit-btn"
|
||||
>
|
||||
{isPending ? 'Wird gespeichert...' : 'Besatz eintragen'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -435,10 +435,19 @@ export function CreateWaterForm({
|
||||
|
||||
{/* Submit */}
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" onClick={() => router.back()}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => router.back()}
|
||||
data-test="water-cancel-btn"
|
||||
>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button type="submit" disabled={isPending}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
data-test="water-submit-btn"
|
||||
>
|
||||
{isPending
|
||||
? 'Wird gespeichert...'
|
||||
: isEdit
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
interface FischereiTabNavigationProps {
|
||||
@@ -11,15 +11,27 @@ interface FischereiTabNavigationProps {
|
||||
}
|
||||
|
||||
const TABS = [
|
||||
{ id: 'overview', label: 'Übersicht', path: '' },
|
||||
{ id: 'waters', label: 'Gewässer', path: '/waters' },
|
||||
{ id: 'species', label: 'Fischarten', path: '/species' },
|
||||
{ id: 'stocking', label: 'Besatz', path: '/stocking' },
|
||||
{ id: 'leases', label: 'Pachten', path: '/leases' },
|
||||
{ id: 'catch-books', label: 'Fangbücher', path: '/catch-books' },
|
||||
{ id: 'permits', label: 'Erlaubnisscheine', path: '/permits' },
|
||||
{ id: 'competitions', label: 'Wettbewerbe', path: '/competitions' },
|
||||
{ id: 'statistics', label: 'Statistiken', path: '/statistics' },
|
||||
{ id: 'overview', i18nKey: 'fischerei:nav.overview', path: '' },
|
||||
{ id: 'waters', i18nKey: 'fischerei:nav.waters', path: '/waters' },
|
||||
{ id: 'species', i18nKey: 'fischerei:nav.species', path: '/species' },
|
||||
{ id: 'stocking', i18nKey: 'fischerei:nav.stocking', path: '/stocking' },
|
||||
{ id: 'leases', i18nKey: 'fischerei:nav.leases', path: '/leases' },
|
||||
{
|
||||
id: 'catch-books',
|
||||
i18nKey: 'fischerei:nav.catchBooks',
|
||||
path: '/catch-books',
|
||||
},
|
||||
{ id: 'permits', i18nKey: 'fischerei:nav.permits', path: '/permits' },
|
||||
{
|
||||
id: 'competitions',
|
||||
i18nKey: 'fischerei:nav.competitions',
|
||||
path: '/competitions',
|
||||
},
|
||||
{
|
||||
id: 'statistics',
|
||||
i18nKey: 'fischerei:nav.statistics',
|
||||
path: '/statistics',
|
||||
},
|
||||
] as const;
|
||||
|
||||
export function FischereiTabNavigation({
|
||||
@@ -32,7 +44,7 @@ export function FischereiTabNavigation({
|
||||
<div className="mb-6 border-b">
|
||||
<nav
|
||||
className="-mb-px flex space-x-1 overflow-x-auto"
|
||||
aria-label="Fischerei Navigation"
|
||||
aria-label="Fisheries Navigation"
|
||||
>
|
||||
{TABS.map((tab) => {
|
||||
const isActive = tab.id === activeTab;
|
||||
@@ -41,6 +53,7 @@ export function FischereiTabNavigation({
|
||||
<Link
|
||||
key={tab.id}
|
||||
href={`${basePath}${tab.path}`}
|
||||
data-test={`fischerei-tab-${tab.id}`}
|
||||
className={cn(
|
||||
'border-b-2 px-4 py-2.5 text-sm font-medium whitespace-nowrap transition-colors',
|
||||
isActive
|
||||
@@ -48,7 +61,7 @@ export function FischereiTabNavigation({
|
||||
: 'text-muted-foreground hover:border-muted-foreground/30 hover:text-foreground border-transparent',
|
||||
)}
|
||||
>
|
||||
{tab.label}
|
||||
<Trans i18nKey={tab.i18nKey} />
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -76,15 +76,21 @@ export function SpeciesDataTable({
|
||||
<Input
|
||||
placeholder="Fischart suchen..."
|
||||
className="w-64"
|
||||
data-test="species-search-input"
|
||||
{...form.register('search')}
|
||||
/>
|
||||
<Button type="submit" variant="outline" size="sm">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
data-test="species-search-btn"
|
||||
>
|
||||
Suchen
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<Link href={`/home/${account}/fischerei/species/new`}>
|
||||
<Button size="sm">
|
||||
<Button size="sm" data-test="species-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neue Fischart
|
||||
</Button>
|
||||
|
||||
@@ -65,7 +65,7 @@ export function StockingDataTable({
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold">Besatz ({total})</h2>
|
||||
<Link href={`/home/${account}/fischerei/stocking/new`}>
|
||||
<Button size="sm">
|
||||
<Button size="sm" data-test="stocking-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Besatz eintragen
|
||||
</Button>
|
||||
|
||||
@@ -103,9 +103,15 @@ export function WatersDataTable({
|
||||
<Input
|
||||
placeholder="Gewässer suchen..."
|
||||
className="w-64"
|
||||
data-test="waters-search-input"
|
||||
{...form.register('search')}
|
||||
/>
|
||||
<Button type="submit" variant="outline" size="sm">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
data-test="waters-search-btn"
|
||||
>
|
||||
Suchen
|
||||
</Button>
|
||||
</form>
|
||||
@@ -114,6 +120,7 @@ export function WatersDataTable({
|
||||
<select
|
||||
value={currentType}
|
||||
onChange={handleTypeChange}
|
||||
data-test="waters-type-filter"
|
||||
className="border-input bg-background flex h-9 rounded-md border px-3 py-1 text-sm shadow-sm"
|
||||
>
|
||||
{WATER_TYPE_OPTIONS.map((opt) => (
|
||||
@@ -124,7 +131,7 @@ export function WatersDataTable({
|
||||
</select>
|
||||
|
||||
<Link href={`/home/${account}/fischerei/waters/new`}>
|
||||
<Button size="sm">
|
||||
<Button size="sm" data-test="waters-new-btn">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neues Gewässer
|
||||
</Button>
|
||||
|
||||
@@ -155,6 +155,7 @@ export function ApplicationWorkflow({
|
||||
size="sm"
|
||||
variant="default"
|
||||
disabled={isPending}
|
||||
data-test="application-approve-btn"
|
||||
onClick={() => handleApprove(appId)}
|
||||
>
|
||||
Genehmigen
|
||||
@@ -163,6 +164,7 @@ export function ApplicationWorkflow({
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
disabled={isPending}
|
||||
data-test="application-reject-btn"
|
||||
onClick={() => handleReject(appId)}
|
||||
>
|
||||
Ablehnen
|
||||
|
||||
@@ -145,6 +145,7 @@ export function DuesCategoryManager({
|
||||
<label className="text-sm font-medium">Name *</label>
|
||||
<Input
|
||||
placeholder="z.B. Standardbeitrag"
|
||||
data-test="dues-name-input"
|
||||
{...form.register('name', { required: true })}
|
||||
/>
|
||||
</div>
|
||||
@@ -155,6 +156,7 @@ export function DuesCategoryManager({
|
||||
step="0.01"
|
||||
min="0"
|
||||
placeholder="0.00"
|
||||
data-test="dues-amount-input"
|
||||
{...form.register('amount', {
|
||||
required: true,
|
||||
valueAsNumber: true,
|
||||
@@ -184,7 +186,12 @@ export function DuesCategoryManager({
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-end">
|
||||
<Button type="submit" disabled={isCreating} className="w-full">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isCreating}
|
||||
className="w-full"
|
||||
data-test="dues-create-btn"
|
||||
>
|
||||
{isCreating ? 'Erstelle...' : 'Erstellen'}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -244,6 +251,7 @@ export function DuesCategoryManager({
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
disabled={isDeletePending}
|
||||
data-test="dues-delete-btn"
|
||||
onClick={() => handleDelete(catId, catName)}
|
||||
>
|
||||
Löschen
|
||||
|
||||
@@ -174,6 +174,7 @@ export function MandateManager({
|
||||
<label className="text-sm font-medium">IBAN *</label>
|
||||
<Input
|
||||
placeholder="DE89 3704 0044 0532 0130 00"
|
||||
data-test="mandate-iban-input"
|
||||
{...form.register('iban', { required: true })}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value
|
||||
@@ -185,12 +186,17 @@ export function MandateManager({
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-medium">BIC</label>
|
||||
<Input placeholder="COBADEFFXXX" {...form.register('bic')} />
|
||||
<Input
|
||||
placeholder="COBADEFFXXX"
|
||||
data-test="mandate-bic-input"
|
||||
{...form.register('bic')}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-medium">Kontoinhaber *</label>
|
||||
<Input
|
||||
placeholder="Max Mustermann"
|
||||
data-test="mandate-holder-input"
|
||||
{...form.register('accountHolder', { required: true })}
|
||||
/>
|
||||
</div>
|
||||
@@ -214,7 +220,11 @@ export function MandateManager({
|
||||
</select>
|
||||
</div>
|
||||
<div className="sm:col-span-2 lg:col-span-3">
|
||||
<Button type="submit" disabled={isCreating}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isCreating}
|
||||
data-test="mandate-create-btn"
|
||||
>
|
||||
{isCreating ? 'Erstelle...' : 'Mandat erstellen'}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -282,6 +292,7 @@ export function MandateManager({
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
disabled={isRevoking}
|
||||
data-test="mandate-revoke-btn"
|
||||
onClick={() => handleRevoke(mandateId, reference)}
|
||||
>
|
||||
Widerrufen
|
||||
|
||||
@@ -136,6 +136,7 @@ export function MemberDetailView({
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
data-test="member-edit-btn"
|
||||
onClick={() =>
|
||||
router.push(`/home/${account}/members-cms/${memberId}/edit`)
|
||||
}
|
||||
@@ -152,6 +153,7 @@ export function MemberDetailView({
|
||||
<Button
|
||||
variant="destructive"
|
||||
disabled={isDeleting}
|
||||
data-test="member-terminate-btn"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
{isDeleting ? 'Wird gekündigt...' : 'Kündigen'}
|
||||
|
||||
@@ -110,9 +110,15 @@ export function MembersDataTable({
|
||||
<Input
|
||||
placeholder="Mitglied suchen..."
|
||||
className="w-64"
|
||||
data-test="members-search-input"
|
||||
{...form.register('search')}
|
||||
/>
|
||||
<Button type="submit" variant="outline" size="sm">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
data-test="members-search-btn"
|
||||
>
|
||||
Suchen
|
||||
</Button>
|
||||
</form>
|
||||
@@ -121,6 +127,7 @@ export function MembersDataTable({
|
||||
<select
|
||||
value={currentStatus}
|
||||
onChange={handleStatusChange}
|
||||
data-test="members-status-filter"
|
||||
className="border-input bg-background flex h-9 rounded-md border px-3 py-1 text-sm shadow-sm"
|
||||
>
|
||||
{STATUS_OPTIONS.map((opt) => (
|
||||
@@ -132,6 +139,7 @@ export function MembersDataTable({
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
data-test="members-new-btn"
|
||||
onClick={() => router.push(`/home/${account}/members-cms/new`)}
|
||||
>
|
||||
Neues Mitglied
|
||||
|
||||
@@ -9,8 +9,12 @@ export function computeAge(
|
||||
const birth = new Date(dateOfBirth);
|
||||
const today = new Date();
|
||||
let age = today.getFullYear() - birth.getFullYear();
|
||||
const m = today.getMonth() - birth.getMonth();
|
||||
if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) age--;
|
||||
const monthDifference = today.getMonth() - birth.getMonth();
|
||||
if (
|
||||
monthDifference < 0 ||
|
||||
(monthDifference === 0 && today.getDate() < birth.getDate())
|
||||
)
|
||||
age--;
|
||||
return age;
|
||||
}
|
||||
|
||||
@@ -21,8 +25,12 @@ export function computeMembershipYears(
|
||||
const entry = new Date(entryDate);
|
||||
const today = new Date();
|
||||
let years = today.getFullYear() - entry.getFullYear();
|
||||
const m = today.getMonth() - entry.getMonth();
|
||||
if (m < 0 || (m === 0 && today.getDate() < entry.getDate())) years--;
|
||||
const monthDifference = today.getMonth() - entry.getMonth();
|
||||
if (
|
||||
monthDifference < 0 ||
|
||||
(monthDifference === 0 && today.getDate() < entry.getDate())
|
||||
)
|
||||
years--;
|
||||
return Math.max(0, years);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user