wip create modal
This commit is contained in:
@@ -18,7 +18,7 @@ export const task = sqliteTable('task', {
|
|||||||
description: text().notNull(),
|
description: text().notNull(),
|
||||||
done: int().notNull(),
|
done: int().notNull(),
|
||||||
estimated_time: int().notNull(),
|
estimated_time: int().notNull(),
|
||||||
due_date: text().notNull(),
|
due_date: text(),
|
||||||
created_at: text().notNull().default(new Date().toISOString()),
|
created_at: text().notNull().default(new Date().toISOString()),
|
||||||
updated_at: text().notNull().default(new Date().toISOString())
|
updated_at: text().notNull().default(new Date().toISOString())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ app.get('/', (req, res) => {
|
|||||||
|
|
||||||
app.get('/tasks', async (req, res) => {
|
app.get('/tasks', async (req, res) => {
|
||||||
const tasks: typeof task.$inferSelect[] = await db.select().from(task)
|
const tasks: typeof task.$inferSelect[] = await db.select().from(task)
|
||||||
|
console.log(tasks)
|
||||||
res.status(200).send(tasks.map<TaskResponse>(task => {
|
res.status(200).send(tasks.map<TaskResponse>(task => {
|
||||||
return { ...task, done: task.done === 1 }
|
return { ...task, done: task.done === 1 }
|
||||||
}));
|
}));
|
||||||
@@ -78,6 +79,7 @@ app.post('/task', async(req, res) => {
|
|||||||
const newTask = req.body
|
const newTask = req.body
|
||||||
newTask.userid = userId
|
newTask.userid = userId
|
||||||
|
|
||||||
|
console.log(newTask)
|
||||||
const returnedTask = await db.insert(task).values(newTask).returning()
|
const returnedTask = await db.insert(task).values(newTask).returning()
|
||||||
console.log(returnedTask)
|
console.log(returnedTask)
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import ListItem from './ListItem.vue';
|
|||||||
import Title1 from './Title1.vue';
|
import Title1 from './Title1.vue';
|
||||||
import type { DropdownMenuItem } from '@nuxt/ui';
|
import type { DropdownMenuItem } from '@nuxt/ui';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import type { USeparator } from '#components';
|
|
||||||
|
|
||||||
const colorMode = useColorMode();
|
const colorMode = useColorMode();
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
const currentTheme = ref<'dark' | 'system' | 'light'>(colorMode.preference as 'dark' | 'system' | 'light');
|
const currentTheme = ref<'dark' | 'system' | 'light'>(colorMode.preference as 'dark' | 'system' | 'light');
|
||||||
|
const showTaskFormModal = ref(false);
|
||||||
|
const taskFormModalInput = ref<Partial<Task>>({});
|
||||||
|
|
||||||
const date = defineModel<DateTime>('date', { required: true })
|
const date = defineModel<DateTime>('date', { required: true })
|
||||||
const tasks = defineModel<Task[]>('tasks', { required: true })
|
const tasks = defineModel<Task[]>('tasks', { required: true })
|
||||||
@@ -76,19 +78,6 @@ const selectedDate = computed({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
type Task = {
|
|
||||||
id: number
|
|
||||||
userid: string
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
done: boolean
|
|
||||||
estimated_time: string
|
|
||||||
due_date: string
|
|
||||||
created_at: string
|
|
||||||
updated_at: string
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function addTask() {
|
function addTask() {
|
||||||
const name = prompt("Todo name:")
|
const name = prompt("Todo name:")
|
||||||
console.log(name)
|
console.log(name)
|
||||||
@@ -97,16 +86,30 @@ function addTask() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function deleteTask(todo: Task) {
|
function deleteTask(todo: Task) {
|
||||||
|
if (todo.id === undefined) {
|
||||||
|
toast.add({
|
||||||
|
title: "Task does not exist anymore"
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
emits('deleteTask', todo.id)
|
emits('deleteTask', todo.id)
|
||||||
}
|
}
|
||||||
function editTask(task: Task) {
|
function editTask(task: Task) {
|
||||||
emits('editTask', task)
|
emits('editTask', task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openTaskFormModal(task: Partial<Task>) {
|
||||||
|
taskFormModalInput.value = task
|
||||||
|
showTaskFormModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UCard class="flex w-64 h-full" :ui="{ body: 'w-full' }">
|
<UCard class="flex w-64 h-full" :ui="{ body: 'w-full' }">
|
||||||
|
<UiTaskFormModal v-model:open="showTaskFormModal" :input="taskFormModalInput" action="create" />
|
||||||
|
|
||||||
<div class="flex flex-col h-full w-full gap-5">
|
<div class="flex flex-col h-full w-full gap-5">
|
||||||
<header class="flex flex-col gap-2">
|
<header class="flex flex-col gap-2">
|
||||||
<Title1>Calendar</Title1>
|
<Title1>Calendar</Title1>
|
||||||
@@ -123,10 +126,10 @@ function editTask(task: Task) {
|
|||||||
<UCheckbox v-model="task.done" @change="() => editTask(task)" />{{ task.title }}
|
<UCheckbox v-model="task.done" @change="() => editTask(task)" />{{ task.title }}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<UButton size="xs" color="neutral" class="flex justify-center" icon="mingcute:pencil-line"
|
<UButton size="xs" color="neutral" class="flex justify-center"
|
||||||
@click="() => editTask(task)"/>
|
icon="mingcute:pencil-line" @click="() => editTask(task)" />
|
||||||
<UButton size="xs" color="primary" class="flex justify-center" icon="octicon:trashcan-16"
|
<UButton size="xs" color="primary" class="flex justify-center"
|
||||||
@click="() => deleteTask(task)" />
|
icon="octicon:trashcan-16" @click="() => deleteTask(task)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@@ -138,8 +141,8 @@ function editTask(task: Task) {
|
|||||||
<UCheckbox v-model="task.done" @change="() => editTask(task)" />{{ task.title }}
|
<UCheckbox v-model="task.done" @change="() => editTask(task)" />{{ task.title }}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<UButton size="xs" color="neutral" class="flex justify-center" icon="mingcute:pencil-line"
|
<UButton size="xs" color="neutral" class="flex justify-center"
|
||||||
@click="() => editTask(task)"/>
|
icon="mingcute:pencil-line" @click="() => editTask(task)" />
|
||||||
<UButton size="xs" color="primary" class="flex justify-center"
|
<UButton size="xs" color="primary" class="flex justify-center"
|
||||||
@click="() => deleteTask(task)" icon="octicon:trashcan-16" />
|
@click="() => deleteTask(task)" icon="octicon:trashcan-16" />
|
||||||
</div>
|
</div>
|
||||||
@@ -148,7 +151,7 @@ function editTask(task: Task) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<UButton size="xl" class="w-full flex justify-center" @click="addTask">
|
<UButton size="xl" class="w-full flex justify-center" @click="() => openTaskFormModal({})">
|
||||||
+
|
+
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
111
web/components/ui/TaskFormModal.vue
Normal file
111
web/components/ui/TaskFormModal.vue
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import * as z from 'zod';
|
||||||
|
|
||||||
|
const open = defineModel<boolean>('open', { required: true })
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'submnitted', event: SimpleTask): void
|
||||||
|
(e: 'canceled'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
action: 'create' | 'edit'
|
||||||
|
input: Partial<Task>,
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const titleField = ref('')
|
||||||
|
const descriptionField = ref('')
|
||||||
|
const estimatedTimeField = ref(0)
|
||||||
|
const dueTimeField = ref('')
|
||||||
|
const dueDateField = ref('')
|
||||||
|
|
||||||
|
const modalTitle = computed(() => {
|
||||||
|
return props.action === 'create' ? 'Create Task' : 'Edit Task'
|
||||||
|
})
|
||||||
|
|
||||||
|
const modalDescription = computed(() => {
|
||||||
|
return props.action === 'create' ? 'Create task with description, due date and name' : 'Edit description, due date and name'
|
||||||
|
})
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
titleField.value = props.input.title ?? ''
|
||||||
|
descriptionField.value = props.input.description ?? ''
|
||||||
|
estimatedTimeField.value = (((props.input.estimated_time) ?? 0) / 60)
|
||||||
|
dueDateField.value = props.input.due_date?.toFormat('yyyy-MM-dd') ?? ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
title: z.string().trim().min(1, { message: 'Title is required' }),
|
||||||
|
description: z.string().default(''),
|
||||||
|
estimated_time: z.number().min(1, { message: 'Estimated time is required and cant be 0' }),
|
||||||
|
due_date: z.string().datetime({ message: 'Invalid due date', local: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
const form = formSchema.safeParse({
|
||||||
|
title: titleField.value,
|
||||||
|
description: descriptionField.value,
|
||||||
|
estimated_time: estimatedTimeField.value * 60,
|
||||||
|
due_date: dateStringFromFields(dueDateField.value, dueTimeField.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (form.error) {
|
||||||
|
for (const error of form.error.errors) {
|
||||||
|
toast.add({
|
||||||
|
title: error.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('submnitted', {
|
||||||
|
id: props.input.id,
|
||||||
|
title: form.data.title,
|
||||||
|
done: props.input.done ?? false,
|
||||||
|
description: form.data.description,
|
||||||
|
estimated_time: form.data.estimated_time,
|
||||||
|
due_date: DateTime.fromISO(form.data.due_date)
|
||||||
|
})
|
||||||
|
open.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function dateStringFromFields(date: string, time: string) {
|
||||||
|
return `${date}T${time}:00`
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel() {
|
||||||
|
emit('canceled')
|
||||||
|
open.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UModal v-model:open="open" :title="modalTitle" :description="modalDescription">
|
||||||
|
<template #body>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<UInput type="text" placeholder="Name" v-model="titleField" required />
|
||||||
|
<div class="flex flex-row gap-2">
|
||||||
|
<UInput class="grow" placeholder="2025-06-16" v-model="dueDateField" icon="i-lucide-calendar"
|
||||||
|
required />
|
||||||
|
<UInput class="grow" placeholder="15:34" v-model="dueTimeField" icon="i-lucide-clock" required />
|
||||||
|
</div>
|
||||||
|
<UTextarea type="text" placeholder="Description" v-model="descriptionField" required />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<UButton variant="solid" @click="submit">
|
||||||
|
{{ action === 'create' ? 'Create' : 'Edit' }}
|
||||||
|
</UButton>
|
||||||
|
<UButton variant="soft" @click="cancel">
|
||||||
|
Cancel
|
||||||
|
</UButton>
|
||||||
|
</template>
|
||||||
|
</UModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -5,41 +5,26 @@ import MainContent from '~/components/ui/MainContent.vue';
|
|||||||
import Sidebar from '~/components/ui/Sidebar.vue';
|
import Sidebar from '~/components/ui/Sidebar.vue';
|
||||||
import { Event, type SerializableEvent } from '~/utils/event';
|
import { Event, type SerializableEvent } from '~/utils/event';
|
||||||
|
|
||||||
const todos = ["Staistics", "Computer Graphics", "Webdev"]
|
|
||||||
const date = ref<DateTime>(DateTime.now())
|
const date = ref<DateTime>(DateTime.now())
|
||||||
const events = ref<Event[]>([])
|
const events = ref<Event[]>([])
|
||||||
|
|
||||||
const { data: eventsResponse } = await useAsyncData<SerializableEvent[]>(
|
const { data: eventsResponse } = await useAsyncData<SerializableEvent[]>(
|
||||||
'events',
|
'events',
|
||||||
() => axios.get('/events').then(res => res.data)
|
() => axios.get<SerializableEvent[]>('/events').then(res => res.data)
|
||||||
|
);
|
||||||
|
|
||||||
|
const {data: tasksResponse, refresh} = await useAsyncData<SerializableTask[]>(
|
||||||
|
'tasks',
|
||||||
|
() => axios.get<SerializableTask[]>('/tasks').then(res => res.data)
|
||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
events.value = eventsResponse.value?.map(Event.fromSerializable) ?? []
|
events.value = eventsResponse.value?.map(Event.fromSerializable) ?? []
|
||||||
})
|
})
|
||||||
|
|
||||||
type Task = {
|
const tasks = computed(() => {
|
||||||
id: number
|
return tasksResponse.value?.map(Task.fromSerializable) ?? []
|
||||||
userid: string
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
done: boolean
|
|
||||||
estimated_time: string
|
|
||||||
due_date: string
|
|
||||||
created_at: string
|
|
||||||
updated_at: string
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const { data: tasks, refresh } = await useAsyncData<Task[]>(
|
|
||||||
'tasks',
|
|
||||||
() => {
|
|
||||||
return axios.get("/tasks").then(result => {
|
|
||||||
console.log(result.data)
|
|
||||||
return result.data
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
async function postEvent(event: Event) {
|
async function postEvent(event: Event) {
|
||||||
console.log('posting Event')
|
console.log('posting Event')
|
||||||
@@ -52,7 +37,7 @@ async function postTask(name: string) {
|
|||||||
title: name,
|
title: name,
|
||||||
description: "",
|
description: "",
|
||||||
done: false,
|
done: false,
|
||||||
estimated_time: (new Date()).toISOString(), //TODO
|
estimated_time: 0,
|
||||||
due_date: (new Date()).toISOString(),
|
due_date: (new Date()).toISOString(),
|
||||||
})
|
})
|
||||||
await refresh()
|
await refresh()
|
||||||
|
|||||||
56
web/utils/task.ts
Normal file
56
web/utils/task.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { DateTime } from "luxon"
|
||||||
|
|
||||||
|
export class Task {
|
||||||
|
constructor(
|
||||||
|
public id: number | undefined,
|
||||||
|
public title: string,
|
||||||
|
public description: string,
|
||||||
|
public done: boolean,
|
||||||
|
public estimated_time: number,
|
||||||
|
public due_date: DateTime | undefined
|
||||||
|
) { }
|
||||||
|
|
||||||
|
static fromSimpleTask(simpleTask: SimpleTask) {
|
||||||
|
return new Task(
|
||||||
|
simpleTask.id,
|
||||||
|
simpleTask.title,
|
||||||
|
simpleTask.description,
|
||||||
|
simpleTask.done,
|
||||||
|
simpleTask.estimated_time,
|
||||||
|
simpleTask.due_date
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromSerializable(serializableTask: SerializableTask) {
|
||||||
|
console.log('dings', serializableTask.due_date)
|
||||||
|
return new Task(
|
||||||
|
serializableTask.id,
|
||||||
|
serializableTask.title,
|
||||||
|
serializableTask.description,
|
||||||
|
serializableTask.done,
|
||||||
|
serializableTask.estimated_time,
|
||||||
|
DateTime.now()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SimpleTask = {
|
||||||
|
id: number | undefined
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
done: boolean
|
||||||
|
estimated_time: number
|
||||||
|
due_date: DateTime | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SerializableTask = {
|
||||||
|
id: number | undefined
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
done: boolean
|
||||||
|
estimated_time: number
|
||||||
|
due_date: string | undefined
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
userid: string
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user