From 8520678a487e03544ee6c52e96362967aef43be0 Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Thu, 27 Aug 2026 10:38:06 +0200 Subject: [PATCH] =?UTF-8?q?Volk=20=C3=9Cberarbeittung=20und=20Englisch=20V?= =?UTF-8?q?araiblen=20ersetzt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ApiCore-Backend/prisma/schema.prisma | 43 +++-- .../src/colonies/dto/create-colony.dto.ts | 26 +-- ApiCore-Backend/test/jest-e2e.json | 3 +- ApiCore-Frontend/public/sw.js | 16 +- .../(app)/colonies/[id]/colony-actions.tsx | 12 +- .../(app)/colonies/[id]/colony-edit-card.tsx | 157 +++++++++++++----- .../src/app/(app)/colonies/new/page.tsx | 12 +- .../src/app/(app)/colonies/page.spec.tsx | 2 +- .../src/app/(app)/colonies/page.tsx | 20 ++- ApiCore-Frontend/src/app/globals.css | 30 ++++ ApiCore-Frontend/src/lib/colonies/types.ts | 14 +- 11 files changed, 231 insertions(+), 104 deletions(-) diff --git a/ApiCore-Backend/prisma/schema.prisma b/ApiCore-Backend/prisma/schema.prisma index 7ba5a76..73aee3e 100644 --- a/ApiCore-Backend/prisma/schema.prisma +++ b/ApiCore-Backend/prisma/schema.prisma @@ -65,15 +65,15 @@ model RolePermission { } model User { - id String @id @default(cuid()) - email String @unique + id String @id @default(cuid()) + email String @unique passwordHash String name String roleId String - role Role @relation(fields: [roleId], references: [id]) - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + role Role @relation(fields: [roleId], references: [id]) + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt deactivatedAt DateTime? // TOTP-Secret AES-256-GCM-verschluesselt gespeichert (siehe @@ -209,24 +209,33 @@ enum ColonyStatus { MERGED } +// Fuenfstufige Zustandsskala, geteilt von Brut-, Futter- und Gesundheitsstatus. +enum ColonyCondition { + EXCELLENT + GOOD + AVERAGE + WEAK + CRITICAL +} + model Colony { - id String @id @default(cuid()) - code String @unique + id String @id @default(cuid()) + code String @unique apiaryId String - apiary Apiary @relation(fields: [apiaryId], references: [id]) + apiary Apiary @relation(fields: [apiaryId], references: [id]) hiveType String? originText String? colonyType ColonyType - status ColonyStatus @default(ACTIVE) + status ColonyStatus @default(ACTIVE) strength Int? - broodStatus String? - feedStatus String? - healthStatus String? + broodStatus ColonyCondition? + feedStatus ColonyCondition? + healthStatus ColonyCondition? notes String? - currentQueenId String? @unique - currentQueen Queen? @relation("ColonyCurrentQueen", fields: [currentQueenId], references: [id]) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + currentQueenId String? @unique + currentQueen Queen? @relation("ColonyCurrentQueen", fields: [currentQueenId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt deactivatedAt DateTime? apiaryHistory ColonyApiaryHistory[] diff --git a/ApiCore-Backend/src/colonies/dto/create-colony.dto.ts b/ApiCore-Backend/src/colonies/dto/create-colony.dto.ts index 97b7e2a..daea247 100644 --- a/ApiCore-Backend/src/colonies/dto/create-colony.dto.ts +++ b/ApiCore-Backend/src/colonies/dto/create-colony.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsEnum, IsInt, IsOptional, IsString } from 'class-validator'; -import { ColonyType } from '../../../generated/prisma'; +import { IsEnum, IsInt, IsOptional, IsString, Max, Min } from 'class-validator'; +import { ColonyCondition, ColonyType } from '../../../generated/prisma'; export class CreateColonyDto { @ApiProperty() @@ -25,25 +25,27 @@ export class CreateColonyDto { @IsString() originText?: string; - @ApiPropertyOptional() + @ApiPropertyOptional({ minimum: 1, maximum: 10 }) @IsOptional() @IsInt() + @Min(1) + @Max(10) strength?: number; - @ApiPropertyOptional() + @ApiPropertyOptional({ enum: ColonyCondition }) @IsOptional() - @IsString() - broodStatus?: string; + @IsEnum(ColonyCondition) + broodStatus?: ColonyCondition; - @ApiPropertyOptional() + @ApiPropertyOptional({ enum: ColonyCondition }) @IsOptional() - @IsString() - feedStatus?: string; + @IsEnum(ColonyCondition) + feedStatus?: ColonyCondition; - @ApiPropertyOptional() + @ApiPropertyOptional({ enum: ColonyCondition }) @IsOptional() - @IsString() - healthStatus?: string; + @IsEnum(ColonyCondition) + healthStatus?: ColonyCondition; @ApiPropertyOptional() @IsOptional() diff --git a/ApiCore-Backend/test/jest-e2e.json b/ApiCore-Backend/test/jest-e2e.json index 91d397b..84ea1aa 100644 --- a/ApiCore-Backend/test/jest-e2e.json +++ b/ApiCore-Backend/test/jest-e2e.json @@ -7,5 +7,6 @@ "maxWorkers": 4, "transform": { "^.+\\.(t|j)s$": "ts-jest" - } + }, + "transformIgnorePatterns": ["node_modules/(?!(otplib|@otplib|@scure|@noble)/)"] } diff --git a/ApiCore-Frontend/public/sw.js b/ApiCore-Frontend/public/sw.js index dc253e0..8ef9e48 100644 --- a/ApiCore-Frontend/public/sw.js +++ b/ApiCore-Frontend/public/sw.js @@ -1,4 +1,4 @@ -const CACHE_NAME = "apicore-shell-v1"; +const CACHE_NAME = "apicore-shell-v2"; self.addEventListener("install", () => { self.skipWaiting(); @@ -27,12 +27,22 @@ self.addEventListener("fetch", (event) => { return; } + // Page navigations must never be answered with a synthetic Response.error() - + // Chrome/Edge render that as a native "This page couldn't load" interstitial + // instead of the app. Let the browser handle navigations directly; only + // static assets (JS/CSS/images) go through the offline-cache fallback below. + if (request.mode === "navigate") { + return; + } + event.respondWith( fetch(request) .then((response) => { const responseClone = response.clone(); - caches.open(CACHE_NAME).then((cache) => cache.put(request, responseClone)); - return response; + return caches + .open(CACHE_NAME) + .then((cache) => cache.put(request, responseClone)) + .then(() => response); }) .catch(() => caches.match(request).then((cached) => cached || Response.error())), ); diff --git a/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-actions.tsx b/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-actions.tsx index 5f305cc..97e9758 100644 --- a/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-actions.tsx +++ b/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-actions.tsx @@ -11,6 +11,7 @@ import { splitColony, } from "@/lib/colonies/api"; import { Colony, ColonyType } from "@/lib/colonies/types"; +import { COLONY_TYPES, COLONY_TYPE_LABELS } from "@/lib/colonies/labels"; import { fetchApiaries } from "@/lib/apiaries/api"; import { fetchColonies } from "@/lib/colonies/api"; import { Button } from "@/components/ui/button"; @@ -33,15 +34,6 @@ import { SelectValue, } from "@/components/ui/select"; -const COLONY_TYPES: ColonyType[] = [ - "PRODUCTION_COLONY", - "NUCLEUS_COLONY", - "YOUNG_COLONY", - "CARETAKER_COLONY", - "MATING_NUCLEUS", - "RESERVE_COLONY", -]; - function invalidateColony( queryClient: ReturnType, colonyId: string, @@ -169,7 +161,7 @@ function SplitDialog({ colony }: { colony: Colony }) { {COLONY_TYPES.map((type) => ( - {type} + {COLONY_TYPE_LABELS[type]} ))} diff --git a/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-edit-card.tsx b/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-edit-card.tsx index b3a4aaa..a65d592 100644 --- a/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-edit-card.tsx +++ b/ApiCore-Frontend/src/app/(app)/colonies/[id]/colony-edit-card.tsx @@ -4,13 +4,28 @@ import { FormEvent, useState } from "react"; import { Layers } from "lucide-react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { updateColony } from "@/lib/colonies/api"; -import { Colony } from "@/lib/colonies/types"; +import { Colony, ColonyCondition, ColonyType } from "@/lib/colonies/types"; +import { + COLONY_CONDITIONS, + COLONY_CONDITION_LABELS, + COLONY_CONDITION_TONE_CLASS, + COLONY_STATUS_LABELS, + COLONY_STRENGTH_OPTIONS, + COLONY_TYPES, + COLONY_TYPE_LABELS, +} from "@/lib/colonies/labels"; import { statusTone } from "@/lib/status-tone"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; -import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { Card, CardContent, @@ -18,21 +33,67 @@ import { CardTitle, } from "@/components/ui/card"; +function ConditionSelect({ + id, + label, + value, + onChange, +}: { + id: string; + label: string; + value: ColonyCondition | undefined; + onChange: (value: ColonyCondition) => void; +}) { + return ( +
+ + +
+ ); +} + export function ColonyEditCard({ colony }: { colony: Colony }) { const queryClient = useQueryClient(); + const [colonyType, setColonyType] = useState(colony.colonyType); const [strength, setStrength] = useState(colony.strength?.toString() ?? ""); - const [broodStatus, setBroodStatus] = useState(colony.broodStatus ?? ""); - const [feedStatus, setFeedStatus] = useState(colony.feedStatus ?? ""); - const [healthStatus, setHealthStatus] = useState(colony.healthStatus ?? ""); + const [broodStatus, setBroodStatus] = useState( + colony.broodStatus ?? undefined, + ); + const [feedStatus, setFeedStatus] = useState( + colony.feedStatus ?? undefined, + ); + const [healthStatus, setHealthStatus] = useState( + colony.healthStatus ?? undefined, + ); const [notes, setNotes] = useState(colony.notes ?? ""); const updateMutation = useMutation({ mutationFn: () => updateColony(colony.id, { + colonyType, strength: strength ? Number(strength) : undefined, - broodStatus: broodStatus || undefined, - feedStatus: feedStatus || undefined, - healthStatus: healthStatus || undefined, + broodStatus, + feedStatus, + healthStatus, notes: notes || undefined, }), onSuccess: () => { @@ -58,43 +119,61 @@ export function ColonyEditCard({ colony }: { colony: Colony }) { {colony.code} - {colony.status} + {COLONY_STATUS_LABELS[colony.status]}
+
+ + +
- setStrength(event.target.value)} - /> -
-
- - setBroodStatus(event.target.value)} - /> -
-
- - setFeedStatus(event.target.value)} - /> -
-
- - setHealthStatus(event.target.value)} - /> +
+ + +