From d576747155c91af4c9c77a7916dd1f90adb7cecf Mon Sep 17 00:00:00 2001 From: Marek Lenczewski Date: Tue, 14 Apr 2026 00:43:47 +0200 Subject: [PATCH] update schema module --- .../haushalt/app/ui/schema/SchemaAllScreen.kt | 2 + .../app/ui/schema/SchemaComponents.kt | 2 +- .../app/ui/schema/SchemaCreateScreen.kt | 2 +- .../app/ui/schema/SchemaCreateViewModel.kt | 2 + .../app/ui/schema/SchemaEditScreen.kt | 2 +- .../app/ui/schema/SchemaEditViewModel.kt | 12 ++ backend/src/Helper/DateHelper.php | 27 ++++ .../src/Repository/TaskSchemaRepository.php | 3 +- backend/src/Service/TaskGenerator.php | 60 ++++--- backend/src/Service/TaskSchemaManager.php | 3 +- frontend/src/views/SchemaAll.vue | 2 + frontend/src/views/SchemaCreate.vue | 8 +- frontend/src/views/SchemaEdit.vue | 15 +- module.md | 150 +++++------------- 14 files changed, 148 insertions(+), 142 deletions(-) create mode 100644 backend/src/Helper/DateHelper.php diff --git a/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaAllScreen.kt b/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaAllScreen.kt index 95b542a..1090141 100644 --- a/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaAllScreen.kt +++ b/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaAllScreen.kt @@ -97,6 +97,8 @@ private fun SchemaRow( schema.repeat == null -> "Einmalig" schema.repeat.containsKey("daily") -> "Täglich" schema.repeat.containsKey("weekly") -> "Wöchentlich" + schema.repeat.containsKey("2week") -> "2-Wöchentlich" + schema.repeat.containsKey("4week") -> "4-Wöchentlich" schema.repeat.containsKey("monthly") -> "Monatlich" else -> "" } diff --git a/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaComponents.kt b/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaComponents.kt index 5734d3d..b3ff08d 100644 --- a/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaComponents.kt +++ b/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaComponents.kt @@ -91,7 +91,7 @@ fun RepeatTypeDropdown( current: String, onChange: (String) -> Unit, ) { - val options = listOf("none" to "Keine (Einmalig)", "daily" to "Täglich", "weekly" to "Wöchentlich", "monthly" to "Monatlich") + val options = listOf("none" to "Keine (Einmalig)", "daily" to "Täglich", "weekly" to "Wöchentlich", "2week" to "2-Wöchentlich", "4week" to "4-Wöchentlich", "monthly" to "Monatlich") var expanded by remember { mutableStateOf(false) } ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }) { OutlinedTextField( diff --git a/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaCreateScreen.kt b/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaCreateScreen.kt index 90db261..efefbbc 100644 --- a/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaCreateScreen.kt +++ b/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaCreateScreen.kt @@ -71,7 +71,7 @@ fun SchemaCreateScreen( ) } - if (viewModel.repeatType == "weekly") { + if (viewModel.repeatType in listOf("weekly", "2week", "4week")) { WeekdaySelector( selected = viewModel.weekly, onChange = { viewModel.weekly = it }, diff --git a/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaCreateViewModel.kt b/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaCreateViewModel.kt index c00dfac..8cd244d 100644 --- a/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaCreateViewModel.kt +++ b/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaCreateViewModel.kt @@ -48,6 +48,8 @@ class SchemaCreateViewModel : ViewModel() { val repeat = when (repeatType) { "daily" -> JsonObject(mapOf("daily" to JsonPrimitive(true))) "weekly" -> JsonObject(mapOf("weekly" to buildJsonArray { weekly.forEach { add(JsonPrimitive(it)) } })) + "2week" -> JsonObject(mapOf("2week" to buildJsonArray { weekly.forEach { add(JsonPrimitive(it)) } })) + "4week" -> JsonObject(mapOf("4week" to buildJsonArray { weekly.forEach { add(JsonPrimitive(it)) } })) "monthly" -> JsonObject(mapOf("monthly" to buildJsonArray { monthly.forEach { add(JsonPrimitive(it)) } })) else -> null } diff --git a/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaEditScreen.kt b/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaEditScreen.kt index 57675e5..492b74f 100644 --- a/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaEditScreen.kt +++ b/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaEditScreen.kt @@ -80,7 +80,7 @@ fun SchemaEditScreen( ) } - if (viewModel.repeatType == "weekly") { + if (viewModel.repeatType in listOf("weekly", "2week", "4week")) { WeekdaySelector( selected = viewModel.weekly, onChange = { viewModel.weekly = it }, diff --git a/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaEditViewModel.kt b/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaEditViewModel.kt index ff22ab6..5b95b6f 100644 --- a/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaEditViewModel.kt +++ b/app/app/src/main/java/de/haushalt/app/ui/schema/SchemaEditViewModel.kt @@ -96,6 +96,16 @@ class SchemaEditViewModel : ViewModel() { weekly = schema.repeat["weekly"]!!.jsonArray.map { it.jsonPrimitive.boolean } monthly = List(31) { false } } + schema.repeat.containsKey("2week") -> { + repeatType = "2week" + weekly = schema.repeat["2week"]!!.jsonArray.map { it.jsonPrimitive.boolean } + monthly = List(31) { false } + } + schema.repeat.containsKey("4week") -> { + repeatType = "4week" + weekly = schema.repeat["4week"]!!.jsonArray.map { it.jsonPrimitive.boolean } + monthly = List(31) { false } + } schema.repeat.containsKey("monthly") -> { repeatType = "monthly" weekly = List(7) { false } @@ -108,6 +118,8 @@ class SchemaEditViewModel : ViewModel() { val repeat = when (repeatType) { "daily" -> JsonObject(mapOf("daily" to JsonPrimitive(true))) "weekly" -> JsonObject(mapOf("weekly" to buildJsonArray { weekly.forEach { add(JsonPrimitive(it)) } })) + "2week" -> JsonObject(mapOf("2week" to buildJsonArray { weekly.forEach { add(JsonPrimitive(it)) } })) + "4week" -> JsonObject(mapOf("4week" to buildJsonArray { weekly.forEach { add(JsonPrimitive(it)) } })) "monthly" -> JsonObject(mapOf("monthly" to buildJsonArray { monthly.forEach { add(JsonPrimitive(it)) } })) else -> null } diff --git a/backend/src/Helper/DateHelper.php b/backend/src/Helper/DateHelper.php new file mode 100644 index 0000000..a14435c --- /dev/null +++ b/backend/src/Helper/DateHelper.php @@ -0,0 +1,27 @@ += $today && $date <= $today->modify('+14 days'); + } + + /** @return array{\DateTimeImmutable, \DateTimeImmutable} */ + public static function getDateRange(?\DateTimeImmutable $start, ?\DateTimeImmutable $end): array + { + $today = new \DateTimeImmutable('today'); + $from = max($today, $start ?? $today); + $to = min($today->modify('+14 days'), $end ?? $today->modify('+14 days')); + + return [$from, $to]; + } + + public static function getWeeksDiff(\DateTimeImmutable $start, \DateTimeImmutable $date): int + { + return (int) ($start->modify('monday this week')->diff($date->modify('monday this week'))->days / 7); + } +} diff --git a/backend/src/Repository/TaskSchemaRepository.php b/backend/src/Repository/TaskSchemaRepository.php index 4a7a28e..48af970 100644 --- a/backend/src/Repository/TaskSchemaRepository.php +++ b/backend/src/Repository/TaskSchemaRepository.php @@ -24,11 +24,10 @@ class TaskSchemaRepository extends ServiceEntityRepository } /** @return list */ - public function findActiveWithRepeat(): array + public function findActive(): array { return $this->createQueryBuilder('s') ->andWhere('s.status = :status') - ->andWhere('s.repeat IS NOT NULL') ->setParameter('status', TaskSchemaStatus::Active) ->getQuery() ->getResult(); diff --git a/backend/src/Service/TaskGenerator.php b/backend/src/Service/TaskGenerator.php index 10ec32f..6a4fb29 100644 --- a/backend/src/Service/TaskGenerator.php +++ b/backend/src/Service/TaskGenerator.php @@ -5,6 +5,7 @@ namespace App\Service; use App\Entity\Task; use App\Entity\TaskSchema; use App\Enum\TaskStatus; +use App\Helper\DateHelper; use App\Repository\TaskSchemaRepository; use Doctrine\ORM\EntityManagerInterface; @@ -18,7 +19,7 @@ class TaskGenerator public function generateNewTasks(): void { - $schemas = $this->schemaRepo->findActiveWithRepeat(); + $schemas = $this->schemaRepo->findActive(); foreach ($schemas as $schema) { $this->removeTasks($schema); @@ -50,38 +51,49 @@ class TaskGenerator } } - /** @return array{\DateTimeImmutable, \DateTimeImmutable} */ - private function getDateRange(TaskSchema $schema): array - { - $today = new \DateTimeImmutable('today'); - $from = max($today, $schema->getStart() ?? $today); - $end = min($today->modify('+14 days'), $schema->getEnd() ?? $today->modify('+14 days')); - - return [$from, $end]; - } - /** @return list<\DateTimeImmutable> */ private function getDates(TaskSchema $schema): array { - [$from, $end] = $this->getDateRange($schema); - $type = $schema->getRepeatType(); - $repeat = $schema->getRepeat(); + if ($schema->getRepeatType() === null) { + $date = $schema->getDate(); + return DateHelper::isInRange($date) ? [$date] : []; + } + [$from, $end] = DateHelper::getDateRange($schema->getStart(), $schema->getEnd()); $dates = []; + for ($date = $from; $date <= $end; $date = $date->modify('+1 day')) { - - if ($type === 'weekly') { - $weekday = (int) $date->format('N') - 1; - if(!$repeat['weekly'][$weekday]) continue; + if ($this->matchesDate($schema, $date)) { + $dates[] = $date; } - if ($type === 'monthly') { - $monthday = (int) $date->format('j') - 1; - if(!$repeat['monthly'][$monthday]) continue; - } - - $dates[] = $date; } return $dates; } + + private function matchesDate(TaskSchema $schema, \DateTimeImmutable $date): bool + { + $type = $schema->getRepeatType(); + $repeat = $schema->getRepeat(); + + if ($type === 'daily') { + return true; + } + + if ($type === 'weekly' || $type === '2week' || $type === '4week') { + $weekday = (int) $date->format('N') - 1; + if (!$repeat[$type][$weekday]) return false; + if ($type === 'weekly') return true; + + $start = $schema->getStart() ?? new \DateTimeImmutable('today'); + return DateHelper::getWeeksDiff($start, $date) % ($type === '2week' ? 2 : 4) === 0; + } + + if ($type === 'monthly') { + $monthday = (int) $date->format('j') - 1; + return $repeat['monthly'][$monthday]; + } + + return false; + } } diff --git a/backend/src/Service/TaskSchemaManager.php b/backend/src/Service/TaskSchemaManager.php index 21e9c7b..d9536df 100644 --- a/backend/src/Service/TaskSchemaManager.php +++ b/backend/src/Service/TaskSchemaManager.php @@ -18,10 +18,9 @@ class TaskSchemaManager public function create(TaskSchemaRequest $req): void { - if ($req->repeat === null) { + if ($req->repeat === null && $req->date === null) { $task = new Task(); $task->setName($req->name); - $task->setDate($req->date); $task->setStatus($req->taskStatus); $this->em->persist($task); $this->em->flush(); diff --git a/frontend/src/views/SchemaAll.vue b/frontend/src/views/SchemaAll.vue index 4df5627..5fe5c44 100644 --- a/frontend/src/views/SchemaAll.vue +++ b/frontend/src/views/SchemaAll.vue @@ -11,6 +11,8 @@ function repeatLabel(schema) { if (!schema.repeat) return 'Einmalig' if (schema.repeat.daily) return 'Täglich' if (schema.repeat.weekly) return 'Wöchentlich' + if (schema.repeat['2week']) return '2-Wöchentlich' + if (schema.repeat['4week']) return '4-Wöchentlich' if (schema.repeat.monthly) return 'Monatlich' return '' } diff --git a/frontend/src/views/SchemaCreate.vue b/frontend/src/views/SchemaCreate.vue index 47258d0..8005d60 100644 --- a/frontend/src/views/SchemaCreate.vue +++ b/frontend/src/views/SchemaCreate.vue @@ -40,6 +40,10 @@ function buildPayload() { data.repeat = { daily: true } } else if (form.value.repeatType === 'weekly') { data.repeat = { weekly: [...form.value.weekly] } + } else if (form.value.repeatType === '2week') { + data.repeat = { '2week': [...form.value.weekly] } + } else if (form.value.repeatType === '4week') { + data.repeat = { '4week': [...form.value.weekly] } } else if (form.value.repeatType === 'monthly') { data.repeat = { monthly: [...form.value.monthly] } } @@ -93,6 +97,8 @@ async function onSave() { + + @@ -102,7 +108,7 @@ async function onSave() { -
+
@@ -148,7 +159,7 @@ function onReset() {
-
+