update
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { getAllSchemas, getAllTasks, toggleTask } from '../services/api'
|
||||
import { getSchemas, getTasks, toggleTask } from '../services/api'
|
||||
import CategoryBadge from '../components/CategoryBadge.vue'
|
||||
import Icon from '../components/Icon.vue'
|
||||
|
||||
@@ -13,8 +13,8 @@ const mode = ref('schemas')
|
||||
async function fetchData(showLoading = true) {
|
||||
if (showLoading) loading.value = true
|
||||
items.value = mode.value === 'schemas'
|
||||
? await getAllSchemas()
|
||||
: await getAllTasks()
|
||||
? await getSchemas()
|
||||
: await getTasks()
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
@@ -29,17 +29,29 @@ function formatDate(dateStr) {
|
||||
})
|
||||
}
|
||||
|
||||
function getDateKey(item) {
|
||||
if (!item.date) return null
|
||||
return item.date.split('T')[0]
|
||||
}
|
||||
|
||||
const groupedItems = computed(() => {
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
const noDate = items.value.filter(i => !i.deadline)
|
||||
const current = items.value.filter(i => i.deadline && i.deadline >= today)
|
||||
const past = items.value.filter(i => i.deadline && i.deadline < today)
|
||||
const noDate = items.value.filter(i => !getDateKey(i))
|
||||
const current = items.value.filter(i => {
|
||||
const d = getDateKey(i)
|
||||
return d && d >= today
|
||||
})
|
||||
const past = items.value.filter(i => {
|
||||
const d = getDateKey(i)
|
||||
return d && d < today
|
||||
})
|
||||
|
||||
const monthMap = {}
|
||||
const months = []
|
||||
for (const item of past) {
|
||||
const d = new Date(item.deadline + 'T00:00:00')
|
||||
const sortKey = item.deadline.substring(0, 7)
|
||||
const dateKey = getDateKey(item)
|
||||
const d = new Date(dateKey + 'T00:00:00')
|
||||
const sortKey = dateKey.substring(0, 7)
|
||||
const label = d.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' })
|
||||
if (!monthMap[sortKey]) {
|
||||
monthMap[sortKey] = { sortKey, label, items: [] }
|
||||
@@ -53,7 +65,7 @@ const groupedItems = computed(() => {
|
||||
})
|
||||
|
||||
async function handleToggle(item) {
|
||||
await toggleTask(item.schemaId, item.deadline)
|
||||
await toggleTask(item.id)
|
||||
await fetchData(false)
|
||||
}
|
||||
|
||||
@@ -108,11 +120,11 @@ onMounted(fetchData)
|
||||
@click="handleToggle(item)"
|
||||
>
|
||||
<div class="task-left">
|
||||
<span class="task-name" :class="{ 'task--completed': item.status === 'erledigt' }">{{ item.name }}</span>
|
||||
<span class="task-name" :class="{ 'task--completed': item.status === 'done' }">{{ item.name }}</span>
|
||||
<CategoryBadge :category="item.category" />
|
||||
</div>
|
||||
<div class="task-right" @click.stop>
|
||||
<button @click="router.push({ name: 'task-detail', params: { id: item.taskId } })">Anzeigen</button>
|
||||
<button @click="router.push({ name: 'task-detail', params: { id: item.id } })">Anzeigen</button>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -125,12 +137,12 @@ onMounted(fetchData)
|
||||
@click="handleToggle(item)"
|
||||
>
|
||||
<div class="task-left">
|
||||
<span class="task-name" :class="{ 'task--completed': item.status === 'erledigt' }">{{ item.name }}</span>
|
||||
<span class="task-name" :class="{ 'task--completed': item.status === 'done' }">{{ item.name }}</span>
|
||||
<CategoryBadge :category="item.category" />
|
||||
</div>
|
||||
<div class="task-right" @click.stop>
|
||||
<span class="task-date">{{ formatDate(item.deadline) }}</span>
|
||||
<button @click="router.push({ name: 'task-detail', params: { id: item.taskId } })">Anzeigen</button>
|
||||
<span class="task-date">{{ formatDate(getDateKey(item)) }}</span>
|
||||
<button @click="router.push({ name: 'task-detail', params: { id: item.id } })">Anzeigen</button>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -144,10 +156,10 @@ onMounted(fetchData)
|
||||
@click="handleToggle(item)"
|
||||
>
|
||||
<div class="task-left">
|
||||
<span class="task-name" :class="{ 'task--completed': item.status === 'erledigt' }">{{ item.name }}</span>
|
||||
<span class="task-name" :class="{ 'task--completed': item.status === 'done' }">{{ item.name }}</span>
|
||||
<CategoryBadge :category="item.category" />
|
||||
</div>
|
||||
<span class="task-date">{{ formatDate(item.deadline) }}</span>
|
||||
<span class="task-date">{{ formatDate(getDateKey(item)) }}</span>
|
||||
</li>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useCategoriesStore } from '../stores/categories'
|
||||
import { getWeekTasks, toggleTask } from '../services/api'
|
||||
import { getTasks, toggleTask } from '../services/api'
|
||||
import DayColumn from '../components/DayColumn.vue'
|
||||
import TaskCard from '../components/TaskCard.vue'
|
||||
import Icon from '../components/Icon.vue'
|
||||
@@ -10,41 +10,63 @@ import Icon from '../components/Icon.vue'
|
||||
const router = useRouter()
|
||||
const categoriesStore = useCategoriesStore()
|
||||
|
||||
const rawDays = ref([])
|
||||
const rawTasksWithoutDeadline = ref([])
|
||||
const allTasks = ref([])
|
||||
const loading = ref(true)
|
||||
const showCompleted = ref(true)
|
||||
|
||||
function filterTasks(tasks) {
|
||||
if (showCompleted.value) return tasks
|
||||
return tasks.filter(t => t.status !== 'erledigt')
|
||||
function getWeekDates() {
|
||||
const now = new Date()
|
||||
const day = now.getDay()
|
||||
const monday = new Date(now)
|
||||
monday.setDate(now.getDate() - ((day + 6) % 7))
|
||||
const dates = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const d = new Date(monday)
|
||||
d.setDate(monday.getDate() + i)
|
||||
dates.push(d.toISOString().split('T')[0])
|
||||
}
|
||||
return dates
|
||||
}
|
||||
|
||||
const tasksWithoutDeadline = computed(() => filterTasks(rawTasksWithoutDeadline.value))
|
||||
const days = computed(() => rawDays.value.map(d => ({
|
||||
...d,
|
||||
tasks: filterTasks(d.tasks),
|
||||
})))
|
||||
const weekDates = getWeekDates()
|
||||
|
||||
async function fetchWeek(showLoading = true) {
|
||||
function filterTasks(tasks) {
|
||||
if (showCompleted.value) return tasks
|
||||
return tasks.filter(t => t.status !== 'done')
|
||||
}
|
||||
|
||||
const tasksWithoutDeadline = computed(() => {
|
||||
const tasks = allTasks.value.filter(t => t.date === null)
|
||||
return filterTasks(tasks)
|
||||
})
|
||||
|
||||
const days = computed(() => {
|
||||
return weekDates.map(date => {
|
||||
const tasks = allTasks.value.filter(t => {
|
||||
if (!t.date) return false
|
||||
return t.date.split('T')[0] === date
|
||||
})
|
||||
return { date, tasks: filterTasks(tasks) }
|
||||
})
|
||||
})
|
||||
|
||||
async function fetchTasks(showLoading = true) {
|
||||
if (showLoading) loading.value = true
|
||||
try {
|
||||
const data = await getWeekTasks()
|
||||
rawDays.value = data.days
|
||||
rawTasksWithoutDeadline.value = data.tasksWithoutDeadline
|
||||
allTasks.value = await getTasks()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleToggle(taskId, date) {
|
||||
await toggleTask(taskId, date)
|
||||
await fetchWeek(false)
|
||||
async function handleToggle(taskId) {
|
||||
await toggleTask(taskId)
|
||||
await fetchTasks(false)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
categoriesStore.fetchCategories()
|
||||
fetchWeek()
|
||||
fetchTasks()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -17,18 +17,20 @@ const isEdit = computed(() => !!route.params.id)
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const error = ref(null)
|
||||
const deleteTasksOnRemove = ref(false)
|
||||
|
||||
const form = ref({
|
||||
name: '',
|
||||
categoryId: null,
|
||||
status: 'aktiv',
|
||||
taskType: 'einzel',
|
||||
deadline: null,
|
||||
status: 'active',
|
||||
type: 'single',
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
weekdays: [],
|
||||
monthDays: [],
|
||||
yearDays: [],
|
||||
days: {
|
||||
week: [],
|
||||
month: [],
|
||||
year: [],
|
||||
},
|
||||
})
|
||||
|
||||
function formatDateForInput(dateStr) {
|
||||
@@ -44,13 +46,14 @@ async function loadTask() {
|
||||
name: task.name,
|
||||
categoryId: task.category?.id ?? null,
|
||||
status: task.status,
|
||||
taskType: task.taskType,
|
||||
deadline: formatDateForInput(task.deadline),
|
||||
type: task.type,
|
||||
startDate: formatDateForInput(task.startDate),
|
||||
endDate: formatDateForInput(task.endDate),
|
||||
weekdays: task.weekdays || [],
|
||||
monthDays: task.monthDays || [],
|
||||
yearDays: task.yearDays || [],
|
||||
days: {
|
||||
week: task.days?.week || [],
|
||||
month: task.days?.month || [],
|
||||
year: task.days?.year || [],
|
||||
},
|
||||
}
|
||||
setDynamicLabel(task.name)
|
||||
} finally {
|
||||
@@ -64,23 +67,25 @@ function buildPayload() {
|
||||
name: f.name,
|
||||
categoryId: f.categoryId,
|
||||
status: f.status,
|
||||
taskType: f.taskType,
|
||||
deadline: null,
|
||||
type: f.type,
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
weekdays: null,
|
||||
monthDays: null,
|
||||
yearDays: null,
|
||||
days: null,
|
||||
}
|
||||
|
||||
if (f.taskType === 'einzel') {
|
||||
payload.deadline = f.deadline
|
||||
} else {
|
||||
if (f.type === 'single') {
|
||||
payload.days = { year: f.days.year }
|
||||
} else if (f.type === 'daily') {
|
||||
payload.startDate = f.startDate
|
||||
payload.endDate = f.endDate
|
||||
if (f.taskType === 'woechentlich') payload.weekdays = f.weekdays
|
||||
if (f.taskType === 'monatlich') payload.monthDays = f.monthDays
|
||||
if (f.taskType === 'multi' || f.taskType === 'jaehrlich') payload.yearDays = f.yearDays
|
||||
} else if (f.type === 'custom') {
|
||||
payload.startDate = f.startDate
|
||||
payload.endDate = f.endDate
|
||||
const days = {}
|
||||
if (f.days.week.length) days.week = f.days.week
|
||||
if (f.days.month.length) days.month = f.days.month
|
||||
if (f.days.year.length) days.year = f.days.year
|
||||
payload.days = Object.keys(days).length ? days : null
|
||||
}
|
||||
|
||||
return payload
|
||||
@@ -105,8 +110,8 @@ async function save() {
|
||||
}
|
||||
|
||||
async function remove() {
|
||||
if (!confirm('Aufgabe wirklich löschen?')) return
|
||||
await deleteSchema(route.params.id)
|
||||
if (!confirm('Schema wirklich löschen?')) return
|
||||
await deleteSchema(route.params.id, deleteTasksOnRemove.value)
|
||||
router.back()
|
||||
}
|
||||
|
||||
@@ -151,9 +156,8 @@ onMounted(() => {
|
||||
<div v-if="isEdit" class="form-group">
|
||||
<label for="status">Status</label>
|
||||
<select id="status" v-model="form.status">
|
||||
<option value="aktiv">Aktiv</option>
|
||||
<option value="erledigt">Erledigt</option>
|
||||
<option value="inaktiv">Inaktiv</option>
|
||||
<option value="active">Aktiv</option>
|
||||
<option value="disabled">Inaktiv</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -162,34 +166,25 @@ onMounted(() => {
|
||||
<label>Aufgabentyp</label>
|
||||
<div class="radio-group">
|
||||
<label class="radio-label">
|
||||
<input type="radio" v-model="form.taskType" value="einzel" /> Einzel
|
||||
<input type="radio" v-model="form.type" value="single" /> Einzel
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<input type="radio" v-model="form.taskType" value="taeglich" /> Täglich
|
||||
<input type="radio" v-model="form.type" value="daily" /> Täglich
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<input type="radio" v-model="form.taskType" value="multi" /> Multi
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<input type="radio" v-model="form.taskType" value="woechentlich" /> Wöchentlich
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<input type="radio" v-model="form.taskType" value="monatlich" /> Monatlich
|
||||
</label>
|
||||
<label class="radio-label">
|
||||
<input type="radio" v-model="form.taskType" value="jaehrlich" /> Jährlich
|
||||
<input type="radio" v-model="form.type" value="custom" /> Benutzerdefiniert
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Einzel: Datum -->
|
||||
<div v-if="form.taskType === 'einzel'" class="form-group">
|
||||
<label for="deadline">Datum (optional)</label>
|
||||
<input id="deadline" v-model="form.deadline" type="date" />
|
||||
<!-- Einzel: Jahresansicht -->
|
||||
<div v-if="form.type === 'single'" class="form-group">
|
||||
<label>Tage auswählen</label>
|
||||
<YearPicker v-model="form.days.year" />
|
||||
</div>
|
||||
|
||||
<!-- Alle außer Einzel: Start + Enddatum nebeneinander -->
|
||||
<template v-if="form.taskType !== 'einzel'">
|
||||
<!-- Daily + Custom: Start + Enddatum -->
|
||||
<template v-if="form.type === 'daily' || form.type === 'custom'">
|
||||
<div class="form-row">
|
||||
<div class="form-group flex-1">
|
||||
<label for="startDate">Startdatum (leer = heute)</label>
|
||||
@@ -202,30 +197,36 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Wöchentlich -->
|
||||
<div v-if="form.taskType === 'woechentlich'" class="form-group">
|
||||
<label>Wochentage</label>
|
||||
<WeekdayPicker v-model="form.weekdays" />
|
||||
</div>
|
||||
<!-- Custom: Alle Picker -->
|
||||
<template v-if="form.type === 'custom'">
|
||||
<div class="form-group">
|
||||
<label>Wochentage</label>
|
||||
<WeekdayPicker v-model="form.days.week" />
|
||||
</div>
|
||||
|
||||
<!-- Monatlich -->
|
||||
<div v-if="form.taskType === 'monatlich'" class="form-group">
|
||||
<label>Monatstage</label>
|
||||
<MonthdayPicker v-model="form.monthDays" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Monatstage</label>
|
||||
<MonthdayPicker v-model="form.days.month" />
|
||||
</div>
|
||||
|
||||
<!-- Multi + Jährlich -->
|
||||
<div v-if="form.taskType === 'multi' || form.taskType === 'jaehrlich'" class="form-group">
|
||||
<label>Jahresansicht</label>
|
||||
<YearPicker v-model="form.yearDays" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Jahresansicht</label>
|
||||
<YearPicker v-model="form.days.year" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<p v-if="error" class="error">{{ error }}</p>
|
||||
|
||||
<div v-if="isEdit" class="form-actions">
|
||||
<button type="button" class="btn-danger" @click="remove">
|
||||
<Icon name="trash" />
|
||||
</button>
|
||||
<div class="delete-section">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" v-model="deleteTasksOnRemove" />
|
||||
Aufgaben löschen
|
||||
</label>
|
||||
<button type="button" class="btn-danger" @click="remove">
|
||||
<Icon name="trash" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -266,6 +267,21 @@ onMounted(() => {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.delete-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: var(--text);
|
||||
|
||||
@@ -13,7 +13,7 @@ const categoriesStore = useCategoriesStore()
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const occurrence = ref(null)
|
||||
const form = ref({ name: '', status: 'aktiv', date: '', categoryId: null })
|
||||
const form = ref({ name: '', status: 'active', date: '', categoryId: null })
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
@@ -85,8 +85,8 @@ onMounted(() => {
|
||||
<div class="form-group flex-1">
|
||||
<label for="status">Status</label>
|
||||
<select id="status" v-model="form.status">
|
||||
<option value="aktiv">Aktiv</option>
|
||||
<option value="erledigt">Erledigt</option>
|
||||
<option value="active">Aktiv</option>
|
||||
<option value="done">Erledigt</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group flex-1">
|
||||
|
||||
Reference in New Issue
Block a user