update schema module
This commit is contained in:
@@ -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 -> ""
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
27
backend/src/Helper/DateHelper.php
Normal file
27
backend/src/Helper/DateHelper.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helper;
|
||||
|
||||
class DateHelper
|
||||
{
|
||||
public static function isInRange(\DateTimeImmutable $date): bool
|
||||
{
|
||||
$today = new \DateTimeImmutable('today');
|
||||
return $date >= $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);
|
||||
}
|
||||
}
|
||||
@@ -24,11 +24,10 @@ class TaskSchemaRepository extends ServiceEntityRepository
|
||||
}
|
||||
|
||||
/** @return list<TaskSchema> */
|
||||
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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 ''
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
<option value="none">Keine (Einmalig)</option>
|
||||
<option value="daily">Täglich</option>
|
||||
<option value="weekly">Wöchentlich</option>
|
||||
<option value="2week">2-Wöchentlich</option>
|
||||
<option value="4week">4-Wöchentlich</option>
|
||||
<option value="monthly">Monatlich</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -102,7 +108,7 @@ async function onSave() {
|
||||
<input id="date" v-model="form.date" type="date" />
|
||||
</div>
|
||||
|
||||
<div v-if="form.repeatType === 'weekly'" class="field">
|
||||
<div v-if="['weekly', '2week', '4week'].includes(form.repeatType)" class="field">
|
||||
<label>Wochentage</label>
|
||||
<div class="day-grid">
|
||||
<label v-for="(day, i) in weekdays" :key="i" class="day-label" :class="{ selected: form.weekly[i] }">
|
||||
|
||||
@@ -34,6 +34,8 @@ function detectRepeatType(schema) {
|
||||
if (!schema.repeat) return 'none'
|
||||
if (schema.repeat.daily) return 'daily'
|
||||
if (schema.repeat.weekly) return 'weekly'
|
||||
if (schema.repeat['2week']) return '2week'
|
||||
if (schema.repeat['4week']) return '4week'
|
||||
if (schema.repeat.monthly) return 'monthly'
|
||||
return 'none'
|
||||
}
|
||||
@@ -48,7 +50,10 @@ function loadFromSchema(schema) {
|
||||
date: toFormDate(schema.date),
|
||||
start: toFormDate(schema.start),
|
||||
end: toFormDate(schema.end),
|
||||
weekly: schema.repeat?.weekly ? [...schema.repeat.weekly] : Array(7).fill(false),
|
||||
weekly: schema.repeat?.weekly ? [...schema.repeat.weekly]
|
||||
: schema.repeat?.['2week'] ? [...schema.repeat['2week']]
|
||||
: schema.repeat?.['4week'] ? [...schema.repeat['4week']]
|
||||
: Array(7).fill(false),
|
||||
monthly: schema.repeat?.monthly ? [...schema.repeat.monthly] : Array(31).fill(false),
|
||||
}
|
||||
}
|
||||
@@ -70,6 +75,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] }
|
||||
}
|
||||
@@ -139,6 +148,8 @@ function onReset() {
|
||||
<option value="none">Keine (Einmalig)</option>
|
||||
<option value="daily">Täglich</option>
|
||||
<option value="weekly">Wöchentlich</option>
|
||||
<option value="2week">2-Wöchentlich</option>
|
||||
<option value="4week">4-Wöchentlich</option>
|
||||
<option value="monthly">Monatlich</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -148,7 +159,7 @@ function onReset() {
|
||||
<input id="date" v-model="form.date" type="date" />
|
||||
</div>
|
||||
|
||||
<div v-if="form.repeatType === 'weekly'" class="field">
|
||||
<div v-if="['weekly', '2week', '4week'].includes(form.repeatType)" class="field">
|
||||
<label>Wochentage</label>
|
||||
<div class="day-grid">
|
||||
<label v-for="(day, i) in weekdays" :key="i" class="day-label" :class="{ selected: form.weekly[i] }">
|
||||
|
||||
150
module.md
150
module.md
@@ -1,115 +1,49 @@
|
||||
# Datei
|
||||
Implementierungs-Schritte als Feature-Module - WIE es gebaut wird
|
||||
|
||||
# Setup module
|
||||
## Backend
|
||||
- Setup Symfony ./backend
|
||||
## Frontend
|
||||
- Setup Vue ./frontend, router, pinia
|
||||
- App.vue - no content
|
||||
## App
|
||||
- Setup Kotlin ./app, navigation compose
|
||||
- MainScreen.kt - no content
|
||||
## Features
|
||||
- Symfony, Vue and Kotlin minimal setup, no content
|
||||
# Setup module
|
||||
- Symfony ./backend
|
||||
- Vue ./frontend
|
||||
- Kotlin ./app
|
||||
- Kotlin copy vue changes
|
||||
- Symfony, Vue and Kotlin can start
|
||||
|
||||
# Base module
|
||||
## Backend
|
||||
- nothing
|
||||
## Frontend
|
||||
- App.vue - layout: breadcrumb, main area
|
||||
- router - / start page route
|
||||
- Startpage.vue - start page, no content
|
||||
## App
|
||||
- MainScreen.kt - layout: breadcrumb, main area
|
||||
- StartScreen.kt - start page, no content
|
||||
## Features
|
||||
- Standard layout for all pages: breadcrumb, main area
|
||||
- layout: Breadcrumb, main area
|
||||
- start page: /, empty
|
||||
|
||||
# Task module
|
||||
## Backend
|
||||
- Task - Task entity
|
||||
- id, name, date (due), status
|
||||
- TaskStatus - Enum for task status
|
||||
- active, done, inactive
|
||||
- TaskController - Task routes
|
||||
- index, show, create, update, delete, toggle (active/done)
|
||||
- TaskManager - Task CRUD
|
||||
- create, update, delete, toggle
|
||||
- TaskRepository - Default task queries
|
||||
- currentTasks()
|
||||
- TaskDto - Dto for create and update task
|
||||
## Frontend
|
||||
- Startpage.vue - quader button for tasks
|
||||
- App.vue - register task routes in breadcrumb.
|
||||
- router - tasks routes /tasks, /tasks/all, /tasks/create, /tasks/:id
|
||||
- Task.vue
|
||||
- Display current tasks (now to +2 weeks and without date) as list with name (done strikethrough), onclick toggle status, order by date (no-date then date asc, hide inactive)
|
||||
- top right nav - list icon (all tasks), + icon (create), eye icon (toggle task visibility by active/done)
|
||||
- TaskAll.vue
|
||||
- Display all tasks as list with name (done strikethrough, past faded), pencil icon (edit), bin icon (delete), onclick toggle status, order by date (no-date then date asc)
|
||||
- top right nav - + icon (create)
|
||||
- TaskCreate.vue - Display form with name-text, date-date, status-select, save-button, abort-button
|
||||
- TaskEdit.vue
|
||||
- Display form with name-text, date-date, status-select, update-button, reset-button, abort-button, use current values
|
||||
- api.js - API routes to symfony
|
||||
## App
|
||||
- StartScreen.kt - quader button for tasks
|
||||
- MainScreen.kt - register task routes in breadcrumb.
|
||||
- NavHost - tasks routes /tasks, /tasks/all, /tasks/create, /tasks/:id
|
||||
- TaskScreen.kt
|
||||
- Display current tasks (now to +2 weeks and without date) as list with name (done strikethrough), onclick toggle status, order by date (no-date then date asc, hide inactive)
|
||||
- top right nav - list icon (all tasks), + icon (create), eye icon (toggle task visibility by active/done)
|
||||
- TaskAllScreen.kt
|
||||
- Display all tasks as list with name (done strikethrough, past faded), pencil icon (edit), bin icon (delete), onclick toggle status, order by date (no-date then date asc)
|
||||
- top right nav - + icon (create)
|
||||
- TaskCreateScreen.kt - Display form with name-text, date-date, status-select, save-button, abort-button
|
||||
- TaskEditScreen.kt
|
||||
- Display form with name-text, date-date, status-select, update-button, reset-button, abort-button, use current values
|
||||
- TaskApi.kt - API calls to symfony
|
||||
## Features
|
||||
- Start page: task button
|
||||
- Task page: current tasks ordered by date, filter done
|
||||
- TaskAll page: all tasks ordered by date, past faded, delete task
|
||||
- TaskCreate page: create task
|
||||
- TaskEdit page: update task
|
||||
- task: id, name, date?, status, schema?
|
||||
- status: active, done, inactive, past (< today)
|
||||
- breadcrumb: /tasks, /tasks/all, /tasks/:id
|
||||
- period: today to +2 weeks
|
||||
- done task: strikethrough, less opacity, default eye hide
|
||||
- inactive task: less opacity, smaller
|
||||
|
||||
# App update module
|
||||
## Backend
|
||||
- public/app/version.json - version info (versionCode, apkFile)
|
||||
- public/app/haushalt.apk - current APK (manually deployed)
|
||||
## App
|
||||
- AppUpdateApi.kt - version check endpoint
|
||||
- AppUpdater.kt - check version, download APK, trigger install
|
||||
- StartScreen.kt - "Update prüfen" button with state feedback
|
||||
- AndroidManifest.xml - REQUEST_INSTALL_PACKAGES, FileProvider
|
||||
- file_paths.xml - cache path for downloaded APK
|
||||
## Features
|
||||
- Manual update check from start screen
|
||||
- Download and install new APK from server
|
||||
- No Play Store required
|
||||
- start page: /tasks button
|
||||
- tasks page: list tasks (name, toggle onclick), only period, group by date, no-date first, hide inactive, navigation (schemas, create, list, toggle, refresh)
|
||||
- all tasks page: list all task (name, toggle onclick, edit, delete), sort by date desc, no-date-first, navigation (schemas, create, refresh)
|
||||
- edit page: form (name, date, status), current values, buttons(save, reset, abort) remove schema on update
|
||||
- navigation: calender icon (schemas), + icon (create), list icon (all tasks), eye icon (toggle), arrow (refresh), pencil icon (edit), bin icon (delete), save icon (save), reset icon (reset), abort icon (abort)
|
||||
|
||||
# AppUpdate module
|
||||
- version: public/app/version.json
|
||||
- apk: public/app/haushalt.apk
|
||||
- start page: button check version, download apk, trigger install
|
||||
|
||||
# TaskSchema module
|
||||
## Backend
|
||||
- Task - add schema (n:1)
|
||||
- TaskSchema - id, name, status, taskStatus, date, repeat (json), start, end
|
||||
- repeat=null → single, repeat={"daily"/"weekly"/"monthly":...} → repeating
|
||||
- TaskSchemaStatus - active, inactive
|
||||
- TaskController - remove create route
|
||||
- TaskManager - remove create
|
||||
- TaskSchemaController - index, show, create, update, delete
|
||||
- TaskSchemaManager - create (single=task only, repeat=schema+generate), update (remove+generate), delete (remove+schema)
|
||||
- TaskGenerator - generateTasks, removeTasks, generateNewTasks (scheduler)
|
||||
- Scheduler - daily at 03:00, messenger:consume via DDEV daemon
|
||||
- Migration - task_schema table + schema_id FK
|
||||
## Frontend
|
||||
- TaskCreate.vue removed, SchemaCreate/SchemaEdit/SchemaAll added
|
||||
- Task.vue + TaskAll.vue - calendar + plus icons → /schemas, /schemas/create
|
||||
- Form: name, (status + taskStatus), repeat, weekday/monthday grid, (start + end)
|
||||
## App
|
||||
- same changes as Frontend
|
||||
## Features
|
||||
- Single schema: task directly, no schema persisted
|
||||
- Repeat schema: tasks for period (max 14 days), scheduler fills daily
|
||||
- Update: remove non-past tasks + regenerate
|
||||
- Delete: remove non-past tasks + schema
|
||||
- task schema: id, name, status, taskStatus, type, date?, repeat?, start?, end?
|
||||
- status: active, inactive
|
||||
- type: single (show date), repeat (show repeat, start, end)
|
||||
- type(single, !date): just create task
|
||||
- type(single, date): create schema, schema creates task
|
||||
- type(repeat, daily/weekly/2weekly/4weekly/monthly): create schema, schema creates tasks
|
||||
- type(repeat, days): + icon (add input:date), add multiple dates, like single+date in bulk, create schema, schema creates tasks
|
||||
- schema: creates tasks in period, schema update and delete affects only tasks in period (no-past), start(today if null), delete schema (if end < today)
|
||||
- schema create: create tasks in period
|
||||
- schema update: remove and create task in period (if task ref schema)
|
||||
- schema delete: remove tasks in period (if task ref schema)
|
||||
- scheduler: execute 3:00, create task (if date=today), remove schemas (if end < today)
|
||||
|
||||
- router - /schemas, /schemas/create, /schemas/:id
|
||||
- list page: list all schemas (name, repeat label, edit, delete), navigation (create, refresh)
|
||||
- create page: form (name, status, taskStatus, type, date, repeat, weekday/monthday, start, end), buttons(save, abort)
|
||||
- edit page: like create but with current values
|
||||
|
||||
|
||||
Reference in New Issue
Block a user