Repository: ssciwr/mondey
Pull request #352: 346 quick changes
Author: login: jmsssc
Created: 2025-05-04T14:11:46Z | Merged: 2025-05-06T09:13:12Z

Changes to complete #346 


Diff:
diff --git a/frontend/src/app.css b/frontend/src/app.css
index 11fc96d8..20191f32 100644
--- a/frontend/src/app.css
+++ b/frontend/src/app.css
@@ -82,6 +82,10 @@
 		@apply text-2xl;
 	}
 
+	.z-1000 {
+		z-index: 1000;
+	}
+
 	@media only screen and (min-device-width: 320px) and (max-device-width: 480px) {
 		.btn {
 			min-width: 100%;
diff --git a/frontend/src/lib/components/DataDisplay/GalleryDisplay.svelte b/frontend/src/lib/components/DataDisplay/GalleryDisplay.svelte
index 20eec4ac..33e90d48 100644
--- a/frontend/src/lib/components/DataDisplay/GalleryDisplay.svelte
+++ b/frontend/src/lib/components/DataDisplay/GalleryDisplay.svelte
@@ -70,7 +70,7 @@ let filteredComponentProps = $derived(
 	{/if}
 
 	{#if withSearch}
-		<form class="m-2 flex w-full rounded p-4">
+		<form class="m-2 w-full rounded p-4">
 			{#if searchData.length > 1}
 				<!-- after example: https://flowbite-svelte.com/docs/forms/search-input#Search_with_dropdown -->
 				<div class="relative">
@@ -105,8 +105,8 @@ let filteredComponentProps = $derived(
 				/>
 			{:else}
 				<Search
-					size="md"
-					class="rounded py-2.5"
+					size="lg"
+					class="rounded py-2.5 min-w-full"
 					placeholder={searchPlaceHolder}
 					bind:value={searchTerm}
 				/>
diff --git a/frontend/src/lib/components/DataInput/DataInput.svelte b/frontend/src/lib/components/DataInput/DataInput.svelte
index 65c265af..ae06e169 100644
--- a/frontend/src/lib/components/DataInput/DataInput.svelte
+++ b/frontend/src/lib/components/DataInput/DataInput.svelte
@@ -1,6 +1,7 @@
 <svelte:options runes={true} />
 
 <script lang="ts">
+import { i18n } from "$lib/i18n.svelte";
 import { Label, Textarea } from "flowbite-svelte";
 
 // variables
@@ -55,6 +56,10 @@ let showTextField: boolean = $derived.by(
 	},
 );
 let highlight = $derived(!valid && required === true);
+
+let realPlaceholder = $derived(
+	i18n.tr ? i18n.tr.misc.selectPlaceholder : undefined,
+);
 </script>
 
 {#if label}
@@ -75,7 +80,7 @@ let highlight = $derived(!valid && required === true);
 				componentClass
 			: componentClass}
 		bind:value = {value}
-		{placeholder}
+		{realPlaceholder}
 		{items}
 		{required}
 		{disabled}
diff --git a/frontend/src/lib/components/FrontPageAbout.svelte b/frontend/src/lib/components/FrontPageAbout.svelte
index c0af4fbe..83557182 100644
--- a/frontend/src/lib/components/FrontPageAbout.svelte
+++ b/frontend/src/lib/components/FrontPageAbout.svelte
@@ -10,7 +10,7 @@ import { authCookieLogin, registerRegister } from "$lib/client/sdk.gen";
 import AlertMessage from "$lib/components/AlertMessage.svelte";
 import { i18n } from "$lib/i18n.svelte";
 import { refresh } from "$lib/utils/login";
-import { Button, Card } from "flowbite-svelte";
+import { Button, Card, Tooltip } from "flowbite-svelte";
 
 let showAlert = $state(false);
 let alertMessage = $state(i18n.tr.registration.alertMessageError);
@@ -78,6 +78,7 @@ const registerTestUser = async () => {
         <button disabled={registerTestUserButtonDisabled} class="btn-primary" data-testid="anonymousLogin" onclick={registerTestUser}>
           {i18n.tr.frontpageAbout.tryDemo}
         </button>
+        <Tooltip>{i18n.tr.frontpageAbout.tryDemoTooltip}</Tooltip>
       </div>
     </Card>
   </div>
diff --git a/frontend/src/lib/components/UserlandSidebar.svelte b/frontend/src/lib/components/UserlandSidebar.svelte
index 31104f7f..6760e402 100644
--- a/frontend/src/lib/components/UserlandSidebar.svelte
+++ b/frontend/src/lib/components/UserlandSidebar.svelte
@@ -14,6 +14,7 @@ import {
 import { goto } from "$app/navigation";
 import DarkModeChooser from "$lib/components/DarkModeChooser.svelte";
 import LocaleChooser from "$lib/components/LocaleChooser.svelte";
+import { alertStore } from "$lib/stores/alertStore.svelte";
 import { currentChild } from "$lib/stores/childrenStore.svelte";
 import { user } from "$lib/stores/userStore.svelte";
 import {
@@ -78,8 +79,12 @@ let { setHideDrawer } = $props();
             <SidebarItem label = {i18n.tr.login.profileButtonLabelLogout} onclick = {async () => {
                     const response = await user.logout();
                     if (response.error) {
-                        alertMessage = i18n.tr.login.alertMessageTitle;
-                        showAlert = true;
+                       alertStore.showAlert(
+                        i18n.tr.login.alertMessageTitle,
+                        "",
+                        true,
+                        true
+                    );
                     } else {
                         user.data = null;
                         currentChild.id = null;
diff --git a/frontend/src/lib/translations.ts b/frontend/src/lib/translations.ts
index b21dc131..41c9c55a 100644
--- a/frontend/src/lib/translations.ts
+++ b/frontend/src/lib/translations.ts
@@ -16,6 +16,8 @@ export const translationIds = {
 		answer3Text: "Zuverlässig",
 		answer3Desc:
 			"Das Kind zeigt das Verhalten mehrmals sicher und genau wie beschrieben.",
+		"answer-1Text": "Später bewerten",
+		"answer-1Desc": "Falls Sie noch nicht wissen, ob das Kind das tun könnt.",
 		milestones: "Meilensteine",
 		observation: "Beobachtungshinweise",
 		help: "Fördern",
@@ -60,6 +62,9 @@ export const translationIds = {
 		disableHelp: "Ausblenden",
 		noFeedback:
 			"Kein Feedback verfügbar. Sie müssen ein Kind länger als eine Woche beobachten und regelmäßig Meilensteine bewerten um Feedback zu erhalten.",
+		feedbackLoadingSummaryError:
+			"Beim Laden der Zusammenfassung des Feedbacks ist ein Fehler aufgetreten.",
+		feedbackLoadingError: "Beim Laden das Feedback ist ein Fehler aufgetreten.",
 		current: "Aktuell",
 		toTheMilestone: "Zum Meilenstein",
 		older: "Weiter zurück",
@@ -213,7 +218,7 @@ export const translationIds = {
 	},
 	userData: {
 		label: "Persönliche Daten",
-		heading: "Benutzerdaten eingeben",
+		heading: "Daten zur eigenen Person eingeben",
 		alertMessageTitle: "Fehler",
 		alertMessageMissing:
 			"Bitte füllen Sie die benötigten Felder (hervorgehoben) aus.",
@@ -251,7 +256,7 @@ export const translationIds = {
 		newChildHeadingLong: "Neues Kind registrieren",
 		overviewLabel: "Kinder",
 		overviewSummary:
-			"Wählen sie ein Kind zur Beobachtung aus oder legen melden sie ein neues Kind an.",
+			"Wählen sie ein Kind zur Beobachtung aus oder legen sie ein neues Kind an.",
 		alertMessageMissing:
 			"Bitte füllen Sie die benötigten Felder (hervorgehoben) aus.",
 		alertMessageUserNotFound:
@@ -313,6 +318,7 @@ export const translationIds = {
 		latest: "Aktuelles",
 		downloads: "Downloads",
 		contact: "Kontakt",
+		selectPlaceholder: "Option wählen",
 	},
 	frontpage: {
 		heading: "Möchten Sie die Entwicklung von Kindern begleiten und fördern?",
@@ -329,6 +335,8 @@ export const translationIds = {
 			"Sie bewerten, wie gut das Kind bestimmte Alltagshandlungen ausführen kann. Auf dieser Basis bietet MONDEY Ihnen Feedback zum Entwicklungsstand des Kindes.",
 		alt: "Ein kleines Kind hält ein Gänseblümchen in der Hand",
 		tryDemo: "Ausprobieren",
+		tryDemoTooltip:
+			"Sie können das Programm ausprobieren, ohne dass Ihre Daten gespeichert werden; sie lassen sich dann aber auch nicht für die Feedback-Funktion nutzen",
 	},
 	frontpageBookmarks: {
 		title: "Welche Entwicklungsbereiche werden erfasst?",
diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte
index 4ca80667..993c9625 100644
--- a/frontend/src/routes/+layout.svelte
+++ b/frontend/src/routes/+layout.svelte
@@ -70,7 +70,7 @@ const asAlert = false;
 </svelte:head>
 
 <div style="position:fixed;padding-top:1.1rem;padding-bottom:1rem;left:0px;right:0px;border-bottom:1px solid lightgray"
-	 class="bg-white dark:bg-gray-800 md:hidden w-full text-center flex justify-center items-center shadow-lg">
+	 class="bg-white z-1000 dark:bg-gray-800 md:hidden w-full text-center flex justify-center items-center shadow-lg">
 	<a href="/#">
 		<img src={logo_light} class="block h-10 dark:hidden" alt="MONDEY Logo" />
 		<img src={logo_dark} class="hidden h-10 dark:block" alt="MONDEY Logo" />
@@ -80,7 +80,7 @@ const asAlert = false;
 <div class="hidden md:block">
 	<Navbar>
 		<div>
-			<NavBrand href="/#">
+			<NavBrand href="/#" class="z-1000">
 				<img src={logo_light} class="mt-6 block h-12 dark:hidden" alt="MONDEY Logo" />
 				<img src={logo_dark} class="mt-6 hidden h-12 dark:block" alt="MONDEY Logo" />
 			</NavBrand>
@@ -122,13 +122,13 @@ const asAlert = false;
 
 	{#if false === isUserLand}
 		<!-- Mobile Log in -->
-		<div class="z-10 fixed" style="top:0.8rem;left:1rem;">
+		<div class="z-1000 fixed" style="top:0.8rem;left:1rem;">
 			<a class="btn-primary btn-icon" href="/login">
 				<UserOutline />
 			</a>
 		</div>
 		<!-- Mobile Navigation -->
-		<div class="z-10 fixed" style="top:0.8rem;right:1rem;">
+		<div class="z-1000 fixed" style="top:0.8rem;right:1rem;">
 			<div class="btn-primary btn-icon" onclick={toggleDrawer} >
 				<BarsOutline class="cursor-pointer" />
 			</div>
diff --git a/frontend/src/routes/stylesPreview/+page.svelte b/frontend/src/routes/stylesPreview/+page.svelte
deleted file mode 100644
index 8fa1fa1f..00000000
--- a/frontend/src/routes/stylesPreview/+page.svelte
+++ /dev/null
@@ -1,63 +0,0 @@
-<script lang="ts">
-import { goto } from "$app/navigation";
-import { Button } from "flowbite-svelte";
-import { BarsOutline } from "flowbite-svelte-icons";
-// Example of primary button
-function handleSignup() {
-	goto("/signup");
-}
-
-// Example of a danger button with icon
-function handleDelete() {
-	// Delete logic here
-}
-</script>
-
-<!-- Primary Button -->
-<Button
-        class="btn-primary"
-        on:click={handleSignup}
->
-    Sign Up
-</Button>
-
-<!-- Secondary Button -->
-<Button
-        class="btn-secondary"
-        on:click={() => console.log('Secondary action')}
->
-    Learn More
-</Button>
-
-<!-- Danger Button -->
-<Button
-        class="btn-danger"
-        on:click={handleDelete}
->
-    <BarsOutline />
-</Button>
-
-<!-- Icon Button -->
-<Button
-        class="btn-icon"
-        on:click={() => console.log('Icon action')}
->
-    <BarsOutline />
-</Button>
-
-<!-- Outlined Button with Icon -->
-<Button
-        outline
-        class="btn-primary"
->
-    <BarsOutline /> Edit Profile
-</Button>
-
-<!-- Gradient Button -->
-<Button
-        gradient
-        color="greenToBlue"
-        class="btn-secondary"
->
-    Gradient Action
-</Button>
diff --git a/frontend/src/routes/userLand/children/feedback/+page.svelte b/frontend/src/routes/userLand/children/feedback/+page.svelte
index f616b93e..54cd0f4e 100644
--- a/frontend/src/routes/userLand/children/feedback/+page.svelte
+++ b/frontend/src/routes/userLand/children/feedback/+page.svelte
@@ -317,7 +317,7 @@ async function loadData(aid: number | null): Promise<void> {
 
 	const summary_ = await loadSummaryFeedbackFor(aid);
 	if (summary_ === null) {
-		alertMessage = `Something went wrong when loading the summary feedback for ${aid}`;
+		alertMessage = i18n.tr.milestone.feedbackLoadingSummaryError;
 		alertStore.showAlert(
 			i18n.tr.childData.alertMessageTitle,
 			alertMessage,
@@ -329,7 +329,7 @@ async function loadData(aid: number | null): Promise<void> {
 
 	const detailed_ = await loadDetailedFeedbackFor(aid);
 	if (detailed_ === null) {
-		alertMessage = `Something went wrong when loading the detailed feedback for ${aid}`;
+		alertMessage = i18n.tr.milestone.feedbackLoadingError;
 		alertStore.showAlert(
 			i18n.tr.childData.alertMessageTitle,
 			alertMessage,
@@ -408,7 +408,7 @@ async function generateReport(): Promise<string | null> {
 		// iterate over all answersessions with aid:key
 		for (let aid of allSessionkeys) {
 			const min = Math.min(...(Object.values(summary[aid]) as number[]));
-			report += `<h2>${i18n.tr.milestone.timeperiod}: ${makeTitle(Number(aid))}</h2> \n`;
+			report += `<h2>${i18n.tr.milestone.timeperiod}: ${formatDate(answerSessions[aid].created_at)}</h2> \n`;
 			report += `<strong>${i18n.tr.milestone.summaryScore}:</strong> ${min === 1 ? i18n.tr.milestone.recommendOk : min === 0 ? i18n.tr.milestone.recommendWatch : min === -1 ? i18n.tr.milestone.recommendHelp : i18n.tr.milestone.notEnoughDataYet} \n\n`;
 
 			for (let [mid, score] of Object.entries(summary[aid])) {
@@ -509,20 +509,21 @@ async function printReport(): Promise<void> {
 
 <!--element of the detailed evaluation which shows how the child fared in each milestonegroup-->
 {#snippet evaluationElement(symbol: any, milestone_or_group: MilestonePublic | MilestoneGroupPublic | undefined, color: string, isMilestone: boolean = false)}
-    <div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 items-center justify-center m-2 p-6">
-        <svelte:component this={symbol} size="xl" class={`${color}`} />
+    <div class="flex flex-col items-center justify-center p-2 w-full">
+        <div class="flex justify-center w-full mb-2">
+            <svelte:component this={symbol} size="xl" class={`${color}`} />
+        </div>
         {#if color !== "gray"}
 			<div class = {`font-bold ${isMilestone? "text-white dark:text-white": ""}`} >
 				{milestone_or_group?.text[i18n.locale].title}
 			</div>
         {/if}
-        <Hr class="w-full my-1"/>
     </div>
 {/snippet}
 
 <!--Snippet defining how the evaluation for each milestonegroup is shown. 'grade' is the evaluation we get from the backend-->
 {#snippet evaluation( milestone_or_group: MilestonePublic | MilestoneGroupPublic | undefined, grade: number, isMilestone: boolean,)}
-    <div class={`rounded-lg space-x-2 space-y-2 justify-center p-10 m-2 flex flex-col text-sm text-white ${(grade === 0 || grade === -1) && isMilestone=== true ? "bg-milestone-600" : "bg-green-700"}`}>
+    <div class={`rounded-lg space-x-2 space-y-2 justify-center p-2 pt-0 pb-0 m-2 flex flex-col text-sm text-white bg-milestone-600`}>
         {#if grade === 1}
             {@render evaluationElement(CheckCircleSolid, milestone_or_group, "text-feedback-0", isMilestone)}
         {:else if grade === 0}
@@ -636,8 +637,8 @@ async function printReport(): Promise<void> {
             {:else}
                 {#each relevant_sessionkeys.slice(0, Math.min(numShownAnswersessions, relevant_sessionkeys.length)) as aid}
                     <TabItem defaultClass="font-bold m-1 p-0"
-                             activeClasses="font-bold m-1 p-4 w-full group-first:rounded-s-lg group-last:rounded-e-lg text-white dark:text-white bg-additional-color-600 dark:bg-additional-color-600 border-1"
-                             inactiveClasses="font-bold m-1 p-4 w-full group-first:rounded-s-lg group-last:rounded-e-lg text-white dark:text-white bg-additional-color-500 dark:bg-additional-color-500 hover:bg-additional-color-400 dark:hover:bg-additional-color-600 border-additional-color-600 dark:border-additional-color-600 border-1"
+                             activeClasses="font-bold m-1 p-2 w-full group-first:rounded-s-lg group-last:rounded-e-lg text-white dark:text-white bg-additional-color-600 dark:bg-additional-color-600 border-1"
+                             inactiveClasses="font-bold m-1 p-2 w-full group-first:rounded-s-lg group-last:rounded-e-lg text-white dark:text-white bg-additional-color-500 dark:bg-additional-color-500 hover:bg-additional-color-400 dark:hover:bg-additional-color-600 border-additional-color-600 dark:border-additional-color-600 border-1"
                              title={makeTitle(aid)}
                              open={aid === relevant_sessionkeys[0]}
                     >
diff --git a/frontend/src/routes/userLand/children/registration/+page.svelte b/frontend/src/routes/userLand/children/registration/+page.svelte
index 9107f084..2142fbeb 100644
--- a/frontend/src/routes/userLand/children/registration/+page.svelte
+++ b/frontend/src/routes/userLand/children/registration/+page.svelte
@@ -393,7 +393,7 @@ async function submitData(): Promise<void> {
                                     placeholder={i18n.tr.childData.pleaseEnterNumber}
                                     disabled={disableEdit}
                                     id="child_birthyear"
-                                    kwargs = {{type: "number", min: 2007, step: '1'}}
+                                    kwargs = {{type: "number", min: 2007, step: '1', max: new Date().getFullYear()}}
                             />
 
                             <DataInput
diff --git a/frontend/src/routes/userLand/milestone/+page.svelte b/frontend/src/routes/userLand/milestone/+page.svelte
index d2fb5318..704284e3 100644
--- a/frontend/src/routes/userLand/milestone/+page.svelte
+++ b/frontend/src/routes/userLand/milestone/+page.svelte
@@ -15,7 +15,7 @@ import { i18n } from "$lib/i18n.svelte";
 import { alertStore } from "$lib/stores/alertStore.svelte";
 import { currentChild } from "$lib/stores/childrenStore.svelte";
 import { contentStore } from "$lib/stores/contentStore.svelte";
-import { Accordion, AccordionItem, Button } from "flowbite-svelte";
+import { Accordion, AccordionItem, Button, Modal } from "flowbite-svelte";
 import {
 	ArrowLeftOutline,
 	ArrowRightOutline,
@@ -84,7 +84,7 @@ async function nextMilestone() {
 		currentMilestoneIndex + 1 ===
 		contentStore.milestoneGroupData.milestones.length
 	) {
-		goto("/userLand/milestone/overview");
+		goto("/userLand/milestone/group");
 		return;
 	}
 	currentMilestoneIndex += 1;
@@ -140,6 +140,7 @@ let milestoneAnswerSession = $state(
 );
 let currentMilestoneIndex = $state(contentStore.milestoneIndex);
 let currentMilestone = $state(undefined as MilestonePublic | undefined);
+let modalImagePreviewOpen = $state(false);
 let selectedAnswer = $derived(
 	milestoneAnswerSession?.answers?.[`${currentMilestone?.id}`]?.answer,
 );
@@ -199,8 +200,22 @@ const breadcrumbdata = $derived([
                                 class={` min-w-20 absolute top-0 left-0 w-full h-full object-cover transition duration-1000 ease-in-out ${imageIndex === currentImageIndex ? 'opacity-100' : 'opacity-0'}`}
                                 src={`${import.meta.env.VITE_MONDEY_API_URL}/static/m/${image.id}.webp`}
                                 alt=""
+                                on:click={() => {
+                                    modalImagePreviewOpen = true;
+                                }}
                         />
                     {/each}
+                    <Modal bind:open={modalImagePreviewOpen} size="xl" autoclose outsideclose>
+                        <div class="p-2" on:click={()=>{modalImagePreviewOpen=false}}>
+                            {#if currentMilestone?.images && currentMilestone.images.length > 0}
+                                <img
+                                        class="w-full rounded-lg"
+                                        src={`${import.meta.env.VITE_MONDEY_API_URL}/static/m/${currentMilestone.images[currentImageIndex].id}.webp`}
+                                        alt=""
+                                />
+                            {/if}
+                        </div>
+                    </Modal>
                 </div>
                 <div class="m-2 md:m-4 grow">
                     <h2 class="mb-2 text-2xl font-bold text-gray-700 dark:text-gray-400">
@@ -240,13 +255,13 @@ const breadcrumbdata = $derived([
                     </Accordion>
                 </div>
                 <div class="m-1 mt-0 flex flex-col justify-items-stretch rounded-lg">
-                    {#each [0, 1, 2, 3] as answerIndex}
+                    {#each [0, 1, 2, 3, -1] as answerIndex}
                         <MilestoneButton
                                 index={answerIndex}
                                 selected={selectedAnswer === answerIndex}
-                                onClick={() => {
-							selectAnswer(answerIndex);
-						}}
+                                onClick={()=>{
+							        selectAnswer(answerIndex);}
+                                }
                                 tooltip={i18n.tr.milestone[`answer${answerIndex}Desc`]}
                         >
                             {i18n.tr.milestone[`answer${answerIndex}Text`]}
@@ -262,15 +277,6 @@ const breadcrumbdata = $derived([
                             <ArrowLeftOutline class="me-2 h-5 w-5 text-gray-700 dark:text-gray-400" />
                             {i18n.tr.milestone.prev}
                         </Button>
-                        <Button
-                                color="light"
-                                disabled={selectedAnswer === null || selectedAnswer === undefined || selectedAnswer < 0}
-                                on:click={nextMilestone}
-                                class="m-1 mt-4 text-gray-700 dark:text-gray-400"
-                        >
-                            {i18n.tr.milestone.next}
-                            <ArrowRightOutline class="ms-2 h-5 w-5 text-gray-700 dark:text-gray-400" />
-                        </Button>
                     </div>
                 </div>
             </div>
diff --git a/frontend/src/routes/userLand/milestone/overview/+page.svelte b/frontend/src/routes/userLand/milestone/overview/+page.svelte
index 3a922492..b50e4463 100644
--- a/frontend/src/routes/userLand/milestone/overview/+page.svelte
+++ b/frontend/src/routes/userLand/milestone/overview/+page.svelte
@@ -154,7 +154,7 @@ function createStyle(data: any[]) {
 		return {
 			card: {
 				class:
-					"m-2 max-w-prose dark:text-white text-white hover:cursor-pointer bg-milestone-300 dark:bg-milestone-300 hover:bg-milestone-500 dark:hover:bg-milestone-500",
+					"text-center m-2 max-w-prose dark:text-white text-white hover:cursor-pointer bg-milestone-300 dark:bg-milestone-300 hover:bg-milestone-500 dark:hover:bg-milestone-500",
 			},
 			auxilliary: {
 				class: "w-14 h-14",
diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts
index de5fa2e7..71e6f02d 100644
--- a/frontend/tailwind.config.ts
+++ b/frontend/tailwind.config.ts
@@ -15,6 +15,7 @@ export default {
 		"bg-milestone-answer-1",
 		"bg-milestone-answer-2",
 		"bg-milestone-answer-3",
+		"bg-milestone-answer--1",
 	],
 	theme: {
 		extend: {
@@ -69,6 +70,7 @@ export default {
 					1: "#cdd993",
 					2: "#a7c957",
 					3: "#6a994e",
+					"-1": "#6C7BB0",
 				},
 				feedback: {
 					0: "#98CB6A",
diff --git a/frontend/tests/e2e/feedbackpageadmin.spec.ts b/frontend/tests/e2e/feedbackpageadmin.spec.ts
index 15937c7a..6a7f248c 100644
--- a/frontend/tests/e2e/feedbackpageadmin.spec.ts
+++ b/frontend/tests/e2e/feedbackpageadmin.spec.ts
@@ -17,7 +17,9 @@ test("/userLand/children/gallery - The gallery of children includes feedback wit
 	).toBeVisible();
 
 	await page.locator('h5:has-text("Emma Johnson")').click();
-	await expect(page.getByText("Januar 2025")).toBeVisible();
+	await expect(page.getByText("2025", { exact: false }).first()).toBeVisible(); //  we don't test the month because it's variable in a
+	// complicated way in the SQL which inserts the children (importMIlestoneAnswer.sql) to keep them around 3 months
+	// old at time of test.
 
 	await page.locator('button:has-text("Feedback zur Entwicklung")').click();
 
diff --git a/mondey_backend/src/mondey_backend/routers/users.py b/mondey_backend/src/mondey_backend/routers/users.py
index 62d71611..3c36a1b9 100644
--- a/mondey_backend/src/mondey_backend/routers/users.py
+++ b/mondey_backend/src/mondey_backend/routers/users.py
@@ -1,5 +1,7 @@
 from __future__ import annotations
 
+from datetime import datetime
+
 from fastapi import APIRouter
 from fastapi import HTTPException
 from fastapi import Query
@@ -63,6 +65,9 @@ def create_child(
         current_active_user: CurrentActiveUserDep,
         child: ChildCreate,
     ):
+        if datetime(child.birth_year, child.birth_month, 1) > datetime.now():
+            raise HTTPException(400, "Birth date must be in the past")
+
         db_child = Child.model_validate(
             child, update={"user_id": current_active_user.id, "has_image": False}
         )
diff --git a/mondey_backend/tests/routers/test_users.py b/mondey_backend/tests/routers/test_users.py
index d426cba5..d5662a35 100644
--- a/mondey_backend/tests/routers/test_users.py
+++ b/mondey_backend/tests/routers/test_users.py
@@ -119,6 +119,21 @@ def test_create_update_and_delete_child(user_client: TestClient):
     assert len(user_client.get("/users/children/").json()) == 2
 
 
+def test_create_future_child_fails(user_client: TestClient):
+    assert len(user_client.get("/users/children/").json()) == 2
+    response_create = user_client.post(
+        "/users/children/",
+        json={
+            "name": "new child with future birth date",
+            "birth_year": 2099,
+            "birth_month": 3,
+            "color": "#000000",
+        },
+    )
+    assert response_create.status_code == 400
+    assert len(user_client.get("/users/children/").json()) == 2
+
+
 def test_delete_dry_run_does_not_delete_child(user_client: TestClient):
     assert len(user_client.get("/users/children/").json()) == 2
     response_create = user_client.post(


Reviews:
user: login: lkeegan
body: thanks looks good! made a couple of minor suggestions, also I pushed a commit to make sure backend & frontend stayed in sync when the 'answer later' button is used.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-05T14:31:37Z
user: login: jmsssc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-06T07:52:19Z

Review comments:
diff_hunk: @@ -199,8 +200,22 @@ const breadcrumbdata = $derived([
                                 class={` min-w-20 absolute top-0 left-0 w-full h-full object-cover transition duration-1000 ease-in-out ${imageIndex === currentImageIndex ? 'opacity-100' : 'opacity-0'}`}
                                 src={`${import.meta.env.VITE_MONDEY_API_URL}/static/m/${image.id}.webp`}
                                 alt=""
+                                on:click={() => {
+                                    modalImagePreviewOpen = true;
+                                }}
                         />
                     {/each}
+                    <Modal bind:open={modalImagePreviewOpen} size="xl" autoclose outsideclose>
+                        <div class="p-2">
path: frontend/src/routes/userLand/milestone/+page.svelte
user: login: lkeegan
body: on (emulated) mobile I struggled to close this modal, as the close button was hidden under the navbar (due to its high z index) and clicking outside didn't seem to work. Do you see the same issue?

![image](https://github.com/user-attachments/assets/3ad15510-45df-4523-a40e-07b8476252c4)

Maybe we could also make this modal close when it is clicked on, e.g.?
```suggestion
                        <div class="p-2" on:click={()=>{modalImagePreviewOpen=false}}>
```
created_at: 2025-05-05T14:20:58Z
author_association: MEMBER
diff_hunk: @@ -69,6 +70,7 @@ export default {
 					1: "#cdd993",
 					2: "#a7c957",
 					3: "#6a994e",
+					"-1": "#274e88",
path: frontend/tailwind.config.ts
user: login: lkeegan
body: For me it's a bit hard to read the mouseover text on this blue, maybe the blue should be a bit lighter?
![image](https://github.com/user-attachments/assets/c9f485b6-57c7-405a-a807-7418c0799cd8)

created_at: 2025-05-05T14:24:17Z
author_association: MEMBER
diff_hunk: @@ -63,6 +65,13 @@ def create_child(
         current_active_user: CurrentActiveUserDep,
         child: ChildCreate,
     ):
+        current_month = int(datetime.now().strftime("%m"))  # 01 for Jan
+        current_year = int(datetime.now().strftime("%Y"))
+        if (
+            child.birth_month > current_month and child.birth_year >= current_year
+        ) or child.birth_year >= current_year:
path: mondey_backend/src/mondey_backend/routers/users.py
user: login: lkeegan
body: here we can avoid doing the logic (which is easy to get wrong) ourselves by comparing the dates:
```suggestion
        if datetime(child.birth_year,child.birth_month,1) > datetime.now():
```
created_at: 2025-05-05T14:29:53Z
author_association: MEMBER
diff_hunk: @@ -63,6 +65,13 @@ def create_child(
         current_active_user: CurrentActiveUserDep,
         child: ChildCreate,
     ):
+        current_month = int(datetime.now().strftime("%m"))  # 01 for Jan
+        current_year = int(datetime.now().strftime("%Y"))
+        if (
+            child.birth_month > current_month and child.birth_year >= current_year
+        ) or child.birth_year >= current_year:
path: mondey_backend/src/mondey_backend/routers/users.py
user: login: jmsssc
body: Sounds good
created_at: 2025-05-06T07:52:19Z
author_association: COLLABORATOR

PR comments:
user: login: codecov-commenter
created_at: 2025-05-04T14:14:31Z
body: ## [Codecov](https://app.codecov.io/gh/ssciwr/mondey/pull/352?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr) Report
Attention: Patch coverage is `7.31707%` with `38 lines` in your changes missing coverage. Please review.
> Project coverage is 26.02%. Comparing base [(`1867356`)](https://app.codecov.io/gh/ssciwr/mondey/commit/1867356b98912c51161a79f97b50fdf3a978939c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr) to head [(`7f79e6a`)](https://app.codecov.io/gh/ssciwr/mondey/commit/7f79e6a776de1be2e2a11503ceb26d3121989a08?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr).

| [Files with missing lines](https://app.codecov.io/gh/ssciwr/mondey/pull/352?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr) | Patch % | Lines |
|---|---|---|
| [...rontend/src/routes/userLand/milestone/+page.svelte](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&filepath=frontend%2Fsrc%2Froutes%2FuserLand%2Fmilestone%2F%2Bpage.svelte&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr#diff-ZnJvbnRlbmQvc3JjL3JvdXRlcy91c2VyTGFuZC9taWxlc3RvbmUvK3BhZ2Uuc3ZlbHRl) | 0.00% | [12 Missing :warning: ](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr) |
| [frontend/src/lib/translations.ts](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&filepath=frontend%2Fsrc%2Flib%2Ftranslations.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr#diff-ZnJvbnRlbmQvc3JjL2xpYi90cmFuc2xhdGlvbnMudHM=) | 0.00% | [10 Missing :warning: ](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr) |
| [frontend/src/lib/components/UserlandSidebar.svelte](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&filepath=frontend%2Fsrc%2Flib%2Fcomponents%2FUserlandSidebar.svelte&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr#diff-ZnJvbnRlbmQvc3JjL2xpYi9jb21wb25lbnRzL1VzZXJsYW5kU2lkZWJhci5zdmVsdGU=) | 0.00% | [5 Missing :warning: ](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr) |
| [...src/routes/userLand/children/feedback/+page.svelte](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&filepath=frontend%2Fsrc%2Froutes%2FuserLand%2Fchildren%2Ffeedback%2F%2Bpage.svelte&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr#diff-ZnJvbnRlbmQvc3JjL3JvdXRlcy91c2VyTGFuZC9jaGlsZHJlbi9mZWVkYmFjay8rcGFnZS5zdmVsdGU=) | 0.00% | [4 Missing :warning: ](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr) |
| [...tend/src/lib/components/DataInput/DataInput.svelte](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&filepath=frontend%2Fsrc%2Flib%2Fcomponents%2FDataInput%2FDataInput.svelte&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr#diff-ZnJvbnRlbmQvc3JjL2xpYi9jb21wb25lbnRzL0RhdGFJbnB1dC9EYXRhSW5wdXQuc3ZlbHRl) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr) |
| [frontend/tailwind.config.ts](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&filepath=frontend%2Ftailwind.config.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr#diff-ZnJvbnRlbmQvdGFpbHdpbmQuY29uZmlnLnRz) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr) |
| [frontend/src/lib/components/FrontPageAbout.svelte](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&filepath=frontend%2Fsrc%2Flib%2Fcomponents%2FFrontPageAbout.svelte&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr#diff-ZnJvbnRlbmQvc3JjL2xpYi9jb21wb25lbnRzL0Zyb250UGFnZUFib3V0LnN2ZWx0ZQ==) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr) |
| [...routes/userLand/children/registration/+page.svelte](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&filepath=frontend%2Fsrc%2Froutes%2FuserLand%2Fchildren%2Fregistration%2F%2Bpage.svelte&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr#diff-ZnJvbnRlbmQvc3JjL3JvdXRlcy91c2VyTGFuZC9jaGlsZHJlbi9yZWdpc3RyYXRpb24vK3BhZ2Uuc3ZlbHRl) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr) |
| [...rc/routes/userLand/milestone/overview/+page.svelte](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&filepath=frontend%2Fsrc%2Froutes%2FuserLand%2Fmilestone%2Foverview%2F%2Bpage.svelte&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr#diff-ZnJvbnRlbmQvc3JjL3JvdXRlcy91c2VyTGFuZC9taWxlc3RvbmUvb3ZlcnZpZXcvK3BhZ2Uuc3ZlbHRl) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/ssciwr/mondey/pull/352?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #352      +/-   ##
==========================================
- Coverage   26.04%   26.02%   -0.02%     
==========================================
  Files         129      128       -1     
  Lines        6274     6286      +12     
  Branches      114      113       -1     
==========================================
+ Hits         1634     1636       +2     
- Misses       4628     4638      +10     
  Partials       12       12              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/ssciwr/mondey/pull/352?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ssciwr).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: jmsssc
created_at: 2025-05-05T12:41:44Z
body: Not ready yet sorry. The favicon showed as a green checkmark so I thought the tests passed (as they did locally), but apparently not.
author_association: COLLABORATOR
user: login: sonarqubecloud[bot]
created_at: 2025-05-06T09:06:23Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=ssciwr_mondey&pullRequest=352) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=ssciwr_mondey&pullRequest=352&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=ssciwr_mondey&pullRequest=352&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=ssciwr_mondey&pullRequest=352&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=ssciwr_mondey&pullRequest=352&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=ssciwr_mondey&pullRequest=352&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=ssciwr_mondey&pullRequest=352)


author_association: NONE