Add HelpPanel and TodoItemTouch components, extend task commands, and refactor task and view logic

- Introduced `HelpPanel.vue` for displaying keyboard shortcuts and command descriptions.
- Added `TodoItemTouch.vue`, a mobile-friendly task item component with updated bindings and improved actions.
- Extended task commands with support for tagging, due date parsing, and dynamic text formatting.
- Implemented `useActions` utility for parsing and executing command-based task modifications.
- Streamlined task editing and creation in `useTasks` for consistency and API integration.
- Updated `ListScreen` to support collapsible, categorized task lists with visual enhancements.
- Refactored `App.vue` for adaptive input handling on mobile versus desktop views.
- Enhanced API communication in `useApi` with cleaner header generation and error handling.
This commit is contained in:
2026-02-23 16:34:52 +01:00
parent ec76a52fdd
commit 56f89b6669
21 changed files with 1347 additions and 214 deletions

View File

@@ -1,35 +0,0 @@
<script setup lang="ts">
import type { Task } from '../types.ts'
import { useTasks } from '../composables/useTasks.ts'
import { router } from '../router.ts'
const { createTask, tasks } = useTasks()
async function handleSubmit(e: Event) {
const data = new FormData(e.target as HTMLFormElement)
const task: Partial<Task> = Object.fromEntries(data)
await createTask(task)
await router.push('/')
}
</script>
<template>
<div>
<form @submit.prevent="handleSubmit">
<fieldset class="fieldset">
<legend class="fieldset-legend">
What is your name?
</legend>
<input type="text" class="input" name="title" placeholder="Type here">
</fieldset>
<button class="btn btn-primary">
Submit
</button>
</form>
</div>
</template>
<style scoped>
</style>

View File

@@ -1,6 +1,5 @@
<script setup lang="ts">
import type { Task } from '../types.ts'
import { PhCaretDown, PhCaretUp } from '@phosphor-icons/vue'
import { computed, onMounted, ref } from 'vue'
import TodoItem from '../components/TodoItem.vue'
@@ -14,12 +13,21 @@ onMounted(async () => {
const visibleTasks = computed<Task[]>(() => tasks.value.filter(task => !task.archived).sort((a, b) => a.id_ - b.id_))
const categorizedTasks = computed(() => visibleTasks.value.reduce((acc, task) => {
const tag = task.tag ?? '@uncategorized'
acc[tag] = acc[tag] ?? []
acc[tag].push(task)
return acc
}, {} as Record<string, Task[]>))
const categorizedTasks = computed(() => {
const unordered: Record<string, Task[]> = visibleTasks.value.reduce((acc, task) => {
const tag = task.tag ?? '@uncategorized'
acc[tag] = acc[tag] ?? []
acc[tag].push(task)
return acc
}, {} as Record<string, Task[]>)
return Object.keys(unordered).sort().reduce(
(obj, key) => {
obj[key] = unordered[key]
return obj
},
{} as Record<string, Task[]>,
)
})
const collapsed = ref<string[]>([])
</script>
@@ -27,22 +35,15 @@ const collapsed = ref<string[]>([])
<template>
<div>
<div class="flex flex-col gap-4">
<div v-for="(tasks, category) in categorizedTasks" :key="category" class="m-4 rounded-box border border-neutral-100 shadow-md">
<div class="m-4 flex justify-between items-center">
<div class="badge badge-xl badge-primary">
{{ category }}
</div>
<button
class="btn btn-square btn-sm"
@click="collapsed.includes(category) ? collapsed.splice(collapsed.indexOf(category), 1) : collapsed.push(category)"
>
<PhCaretDown v-if="collapsed.includes(category)" :size="20" />
<PhCaretUp v-else :size="20" />
<div v-for="(catTasks, category) in categorizedTasks" :key="category" class="m-4 ">
<div class="mb-4">
<button class="px-4 py-1 rounded bg-neutral-200 font-mono text-sm font-bold" @click="collapsed.includes(category) ? collapsed.splice(collapsed.indexOf(category), 1) : collapsed.push(category)">
{{ category }} [{{ catTasks.length }}]
</button>
</div>
<Transition name="fade">
<ul v-if="!collapsed.includes(category)" class="list bg-base-100 rounded-box">
<TodoItem v-for="task in tasks.sort((a, b) => a.id_ - b.id_)" :key="task.id" :task />
<ul v-if="!collapsed.includes(category)" class="">
<TodoItem v-for="task in catTasks" :key="task.id" :task />
</ul>
</Transition>
</div>