Merge pull request #10 from quirinecker/feature/task-form-modal
Feature/task form modal
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
"dotenv": "^16.5.0",
|
||||
"drizzle-orm": "^0.44.2",
|
||||
"express": "^5.1.0",
|
||||
"socket.io": "^4.8.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"drizzle-kit": "^0.31.1",
|
||||
@@ -103,10 +104,14 @@
|
||||
|
||||
"@neon-rs/load": ["@neon-rs/load@0.0.4", "", {}, "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw=="],
|
||||
|
||||
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
|
||||
|
||||
"@types/body-parser": ["@types/body-parser@1.19.5", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg=="],
|
||||
|
||||
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
|
||||
|
||||
"@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="],
|
||||
|
||||
"@types/express": ["@types/express@5.0.1", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "*" } }, "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ=="],
|
||||
|
||||
"@types/express-serve-static-core": ["@types/express-serve-static-core@5.0.6", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA=="],
|
||||
@@ -129,6 +134,8 @@
|
||||
|
||||
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||
|
||||
"base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="],
|
||||
|
||||
"body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
@@ -169,6 +176,10 @@
|
||||
|
||||
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
|
||||
|
||||
"engine.io": ["engine.io@6.6.4", "", { "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", "ws": "~8.17.1" } }, "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g=="],
|
||||
|
||||
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
@@ -287,6 +298,12 @@
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"socket.io": ["socket.io@4.8.1", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg=="],
|
||||
|
||||
"socket.io-adapter": ["socket.io-adapter@2.5.5", "", { "dependencies": { "debug": "~4.3.4", "ws": "~8.17.1" } }, "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg=="],
|
||||
|
||||
"socket.io-parser": ["socket.io-parser@4.2.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" } }, "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew=="],
|
||||
|
||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
|
||||
@@ -309,10 +326,24 @@
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"ws": ["ws@8.18.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="],
|
||||
"ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
|
||||
|
||||
"@libsql/isomorphic-ws/ws": ["ws@8.18.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="],
|
||||
|
||||
"engine.io/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
|
||||
|
||||
"engine.io/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"socket.io/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
|
||||
|
||||
"socket.io/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"socket.io-adapter/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"socket.io-parser/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
|
||||
@@ -356,5 +387,17 @@
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
|
||||
|
||||
"engine.io/accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"engine.io/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
|
||||
|
||||
"socket.io/accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"socket.io/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
|
||||
|
||||
"engine.io/accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"socket.io/accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.5.0",
|
||||
"drizzle-orm": "^0.44.2",
|
||||
"express": "^5.1.0"
|
||||
"express": "^5.1.0",
|
||||
"socket.io": "^4.8.1"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "bun run --watch src/main.ts"
|
||||
|
||||
@@ -2,7 +2,7 @@ import { sqliteTable, int, text } from 'drizzle-orm/sqlite-core'
|
||||
|
||||
export const event = sqliteTable('event', {
|
||||
id: int().primaryKey({ autoIncrement: true }),
|
||||
userid: text().notNull(),
|
||||
userid: text().notNull(),
|
||||
title: text().notNull(),
|
||||
description: text().notNull(),
|
||||
from: text().notNull(),
|
||||
@@ -12,13 +12,14 @@ export const event = sqliteTable('event', {
|
||||
})
|
||||
|
||||
export const task = sqliteTable('task', {
|
||||
id: int().primaryKey({ autoIncrement: true }),
|
||||
userid: text().notNull(),
|
||||
id: int().primaryKey({ autoIncrement: true }),
|
||||
userid: text().notNull(),
|
||||
title: text().notNull(),
|
||||
description: text().notNull(),
|
||||
done: int().notNull(),
|
||||
estimated_time: int().notNull(),
|
||||
due_date: text().notNull(),
|
||||
done: int().notNull(),
|
||||
scheduled_at: text(),
|
||||
estimated_time: int().notNull(),
|
||||
due_date: text(),
|
||||
created_at: text().notNull().default(new Date().toISOString()),
|
||||
updated_at: text().notNull().default(new Date().toISOString())
|
||||
})
|
||||
|
||||
@@ -1,141 +1,41 @@
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import { drizzle } from 'drizzle-orm/libsql';
|
||||
import { event, task } from './db/schema';
|
||||
import { eq, ne, gt, gte } from 'drizzle-orm';
|
||||
import http from 'http'
|
||||
import taskRouter from './routers/task';
|
||||
import eventRouter from './routers/event'
|
||||
|
||||
import { Server} from 'socket.io'
|
||||
|
||||
const db = drizzle("file:local.db");
|
||||
const app = express();
|
||||
const userId = "Detlef";
|
||||
|
||||
type Prettify<T> = {
|
||||
[K in keyof T]: T[K];
|
||||
} & {};
|
||||
|
||||
type TaskResponse = Prettify<Omit<typeof task.$inferSelect, 'done'> & { done: boolean }>
|
||||
const server = http.createServer(app)
|
||||
const io = new Server (server, {
|
||||
cors:{
|
||||
origin: "*",
|
||||
},
|
||||
})
|
||||
|
||||
app.use(cors())
|
||||
app.use('/tasks', taskRouter)
|
||||
app.use('/events', eventRouter)
|
||||
app.use(express.json());
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send('Hello World');
|
||||
res.send('Hello World');
|
||||
});
|
||||
|
||||
app.get('/tasks', async (req, res) => {
|
||||
const tasks: typeof task.$inferSelect[] = await db.select().from(task)
|
||||
res.status(200).send(tasks.map<TaskResponse>(task => {
|
||||
return { ...task, done: task.done === 1 }
|
||||
}));
|
||||
io.on('connection', (socket) => {
|
||||
console.log('A user connected:', socket.id);
|
||||
|
||||
socket.on('change', () => {
|
||||
console.log('Message received');
|
||||
socket.broadcast.emit('change')
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('User disconnected:', socket.id);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/events', async(req, res) => {
|
||||
res.status(200).send(await db.select().from(event))
|
||||
});
|
||||
|
||||
app.get('/user/:id', (req, res) => {
|
||||
const id = req.params['id'];
|
||||
|
||||
if (id == null) {
|
||||
res.status(400).send({error: 'Needs an user id'});
|
||||
return;
|
||||
}
|
||||
|
||||
const user = {id: id, name: 'Cracker'} //TODO
|
||||
res.json(user);
|
||||
|
||||
});
|
||||
|
||||
app.get('/task/:id', async(req, res) => {
|
||||
|
||||
const id = parseInt(req.params['id']);
|
||||
|
||||
if (id == null) {
|
||||
res.status(400).send({error: 'Needs an id'});
|
||||
return;
|
||||
}
|
||||
|
||||
const returnedTask = await db.select().from(task).where(eq(task.id, id))
|
||||
//
|
||||
console.log(returnedTask)
|
||||
res.json(returnedTask);
|
||||
});
|
||||
|
||||
app.get('/event/:id', (req, res) => {
|
||||
|
||||
const id = req.params['id'];
|
||||
|
||||
if (id == null) {
|
||||
res.status(400).send({error: 'Needs an id'});
|
||||
return;
|
||||
}
|
||||
|
||||
const event = {id: id, name: 'Pary'} //TODO
|
||||
res.json(event);
|
||||
});
|
||||
|
||||
app.post('/task', async(req, res) => {
|
||||
|
||||
const newTask = req.body
|
||||
newTask.userid = userId
|
||||
|
||||
const returnedTask = await db.insert(task).values(newTask).returning()
|
||||
console.log(returnedTask)
|
||||
|
||||
res.status(201).json(returnedTask);
|
||||
});
|
||||
|
||||
app.post('/event', async(req, res) => {
|
||||
|
||||
const newEvent: typeof event.$inferInsert = req.body
|
||||
newEvent.userid = userId
|
||||
|
||||
const returnedEvent = await db.insert(event).values(newEvent).returning()
|
||||
console.log(returnedEvent)
|
||||
|
||||
res.status(201).json(returnedEvent);
|
||||
});
|
||||
|
||||
app.put('/task', (req, res) => {
|
||||
|
||||
const id = parseInt(req.params['id']);
|
||||
const updatedTask: Partial<typeof task.$inferSelect> = req.body
|
||||
|
||||
if (id == null) {
|
||||
res.status(400).send({error: 'Needs an id'});
|
||||
return;
|
||||
}
|
||||
db.update(task).set(updatedTask).where(eq(task.id, id))
|
||||
|
||||
res.status(200).json(updatedTask);
|
||||
});
|
||||
|
||||
app.put('/event', (req, res) => {
|
||||
|
||||
const id = parseInt(req.params['id']);
|
||||
const updatedEvent: Partial<typeof event.$inferSelect> = req.body
|
||||
|
||||
if (id == null) {
|
||||
res.status(400).send({error: 'Needs an id'});
|
||||
return;
|
||||
}
|
||||
db.update(event).set(updatedEvent).where(eq(event.id, id))
|
||||
|
||||
res.status(200).json(updatedEvent);});
|
||||
|
||||
app.delete('/task/:id', async(req, res) => {
|
||||
const id = parseInt(req.params['id']);
|
||||
|
||||
const success = await db.delete(task).where(eq(task.id, id))
|
||||
res.send("Deleted");
|
||||
});
|
||||
|
||||
app.delete('/event/:id', async(req, res) => {
|
||||
const id = parseInt(req.params['id']);
|
||||
|
||||
const success = await db.delete(event).where(eq(event.id, id))
|
||||
res.send("Deleted");
|
||||
});
|
||||
|
||||
app.listen(8080, () => {
|
||||
console.log('Listening on port 8080');
|
||||
server.listen(8080, () => {
|
||||
console.log('Listening on port 8080');
|
||||
});
|
||||
|
||||
65
backend/src/routers/event.ts
Normal file
65
backend/src/routers/event.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import { drizzle } from 'drizzle-orm/libsql';
|
||||
import { event } from '../db/schema';
|
||||
import { eq, ne, gt, gte } from 'drizzle-orm';
|
||||
import { Router } from "express";
|
||||
|
||||
const db = drizzle("file:local.db");
|
||||
const userId = "Detlef";
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.use(cors())
|
||||
router.use(express.json());
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
res.status(200).send(await db.select().from(event))
|
||||
});
|
||||
|
||||
router.get('/:id', (req, res) => {
|
||||
|
||||
const id = req.params['id'];
|
||||
|
||||
if (id == null) {
|
||||
res.status(400).send({ error: 'Needs an id' });
|
||||
return;
|
||||
}
|
||||
|
||||
const event = { id: id, name: 'Pary' } //TODO
|
||||
res.json(event);
|
||||
});
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
console.log("loll")
|
||||
const newEvent: typeof event.$inferInsert = req.body
|
||||
newEvent.userid = userId
|
||||
|
||||
const returnedEvent = await db.insert(event).values(newEvent).returning()
|
||||
console.log(returnedEvent)
|
||||
|
||||
res.status(201).json(returnedEvent);
|
||||
});
|
||||
|
||||
router.put('/:id', async (req, res) => {
|
||||
|
||||
const id = parseInt(req.params['id']);
|
||||
const updatedEvent: Partial<typeof event.$inferSelect> = req.body
|
||||
|
||||
if (id == null) {
|
||||
res.status(400).send({ error: 'Needs an id' });
|
||||
return;
|
||||
}
|
||||
await db.update(event).set(updatedEvent).where(eq(event.id, id))
|
||||
|
||||
res.status(200).json(updatedEvent);
|
||||
});
|
||||
|
||||
router.delete('/:id', async (req, res) => {
|
||||
const id = parseInt(req.params['id']);
|
||||
|
||||
const success = await db.delete(event).where(eq(event.id, id))
|
||||
res.send("Deleted");
|
||||
});
|
||||
|
||||
export default router
|
||||
78
backend/src/routers/task.ts
Normal file
78
backend/src/routers/task.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import { drizzle } from 'drizzle-orm/libsql';
|
||||
import { task } from '../db/schema';
|
||||
import { eq, ne, gt, gte } from 'drizzle-orm';
|
||||
import { Router } from "express";
|
||||
|
||||
const db = drizzle("file:local.db");
|
||||
const userId = "Detlef";
|
||||
|
||||
type Prettify<T> = {
|
||||
[K in keyof T]: T[K];
|
||||
} & {};
|
||||
|
||||
type TaskResponse = Prettify<Omit<typeof task.$inferSelect, 'done'> & { done: boolean }>
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.use(cors())
|
||||
router.use(express.json());
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
const tasks: typeof task.$inferSelect[] = await db.select().from(task)
|
||||
console.log(tasks)
|
||||
res.status(200).send(tasks.map<TaskResponse>(task => {
|
||||
return { ...task, done: task.done === 1 }
|
||||
}));
|
||||
});
|
||||
|
||||
router.get('/:id', async (req, res) => {
|
||||
|
||||
const id = parseInt(req.params['id']);
|
||||
|
||||
if (id == null) {
|
||||
res.status(400).send({ error: 'Needs an id' });
|
||||
return;
|
||||
}
|
||||
|
||||
const returnedTask = await db.select().from(task).where(eq(task.id, id))
|
||||
//
|
||||
console.log(returnedTask)
|
||||
res.json(returnedTask);
|
||||
});
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
|
||||
const newTask = req.body
|
||||
newTask.userid = userId
|
||||
|
||||
console.log(newTask)
|
||||
const returnedTasks = await db.insert(task).values(newTask).returning()
|
||||
console.log(returnedTasks)
|
||||
|
||||
res.status(201).json(returnedTasks[0]);
|
||||
});
|
||||
|
||||
router.put('/:id', async (req, res) => {
|
||||
|
||||
const id = parseInt(req.params['id']);
|
||||
const updatedTask: Partial<typeof task.$inferSelect> = req.body
|
||||
|
||||
if (id == null) {
|
||||
res.status(400).send({ error: 'Needs an id' });
|
||||
return;
|
||||
}
|
||||
await db.update(task).set(updatedTask).where(eq(task.id, id))
|
||||
|
||||
res.status(200).json(updatedTask);
|
||||
});
|
||||
|
||||
router.delete('/:id', async (req, res) => {
|
||||
const id = parseInt(req.params['id']);
|
||||
|
||||
const success = await db.delete(task).where(eq(task.id, id))
|
||||
res.send("Deleted");
|
||||
});
|
||||
|
||||
export default router
|
||||
21
web/bun.lock
21
web/bun.lock
@@ -18,6 +18,7 @@
|
||||
"moment": "^2.30.1",
|
||||
"nuxt": "^3.17.2",
|
||||
"nuxt-app": "file:",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"typescript": "^5.6.3",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.1",
|
||||
@@ -422,6 +423,8 @@
|
||||
|
||||
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="],
|
||||
|
||||
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
|
||||
|
||||
"@speed-highlight/core": ["@speed-highlight/core@1.2.7", "", {}, "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
|
||||
@@ -940,6 +943,10 @@
|
||||
|
||||
"end-of-stream": ["end-of-stream@1.4.4", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="],
|
||||
|
||||
"engine.io-client": ["engine.io-client@6.6.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", "ws": "~8.17.1", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w=="],
|
||||
|
||||
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
|
||||
|
||||
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
@@ -1778,6 +1785,10 @@
|
||||
|
||||
"smob": ["smob@1.5.0", "", {}, "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig=="],
|
||||
|
||||
"socket.io-client": ["socket.io-client@4.8.1", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ=="],
|
||||
|
||||
"socket.io-parser": ["socket.io-parser@4.2.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" } }, "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew=="],
|
||||
|
||||
"source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
@@ -2026,6 +2037,8 @@
|
||||
|
||||
"xml-name-validator": ["xml-name-validator@4.0.0", "", {}, "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="],
|
||||
|
||||
"xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="],
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
|
||||
@@ -2192,6 +2205,10 @@
|
||||
|
||||
"dir-glob/path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
|
||||
|
||||
"engine.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"engine.io-client/ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="],
|
||||
|
||||
"escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"eslint/find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
@@ -2294,6 +2311,10 @@
|
||||
|
||||
"send/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
|
||||
|
||||
"socket.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"socket.io-parser/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"spdx-correct/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="],
|
||||
|
||||
@@ -6,16 +6,23 @@ import type { DateTime } from 'luxon';
|
||||
|
||||
const events = defineModel<Event[]>('events', { required: true })
|
||||
const date = defineModel<DateTime>('date', { required: true })
|
||||
const draggedTask = defineModel<DraggedTask | undefined>('draggedTask', { required: true })
|
||||
const tasks = defineModel<Task[]>('tasks', { required: true })
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'createEvent', event: Event): void
|
||||
(e: 'edit-task', task: Task): void
|
||||
(e: 'edit-event', event: Event): void
|
||||
(e: 'delete-event', id: number): void
|
||||
}>()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCard class="flex grow" :ui="{ body: 'w-full h-full' }">
|
||||
<Calendar @create="(event) => emits('createEvent', event)"v-model:events="events" v-model:date="date"></Calendar>
|
||||
<Calendar @create="(event) => emits('createEvent', event)" @edit-task="(task) => emits('edit-task', task)" @edit="(event) => emits('edit-event', event)" @delete="(event) => emits('delete-event', event.id ?? -1)" v-model:events="events" v-model:date="date" ,
|
||||
v-model:dragged-task="draggedTask" v-model:tasks="tasks">
|
||||
</Calendar>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -4,19 +4,24 @@ import ListItem from './ListItem.vue';
|
||||
import Title1 from './Title1.vue';
|
||||
import type { DropdownMenuItem } from '@nuxt/ui';
|
||||
import { DateTime } from 'luxon';
|
||||
import type { USeparator } from '#components';
|
||||
|
||||
const colorMode = useColorMode();
|
||||
const toast = useToast()
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
const currentTheme = ref<'dark' | 'system' | 'light'>(colorMode.preference as 'dark' | 'system' | 'light');
|
||||
const showTaskCreateModal = ref(false);
|
||||
const showTaskEditModal = ref(false);
|
||||
const taskFormModalInput = ref<Partial<Task>>({});
|
||||
|
||||
const date = defineModel<DateTime>('date', { required: true })
|
||||
const tasks = defineModel<Task[]>('tasks', { required: true })
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'createTask', name: string): void
|
||||
(e: 'createTask', task: Task): void
|
||||
(e: 'deleteTask', id: number): void
|
||||
(e: 'editTask', task: Task): void
|
||||
(e: 'scheduleTask', task: Task): void
|
||||
}>()
|
||||
|
||||
const isLight = computed(() => currentTheme.value === 'light');
|
||||
@@ -76,37 +81,51 @@ 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(task: Task) {
|
||||
tasks.value.push(task)
|
||||
console.log(tasks.value)
|
||||
emits('createTask', task)
|
||||
}
|
||||
|
||||
|
||||
function addTask() {
|
||||
const name = prompt("Todo name:")
|
||||
console.log(name)
|
||||
if (name !== null) {
|
||||
emits('createTask', name)
|
||||
function deleteTask(task: Task) {
|
||||
if (task.id === undefined) {
|
||||
toast.add({
|
||||
title: "Task does not exist anymore"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
tasks.value = tasks.value.filter(t => t.id !== task.id)
|
||||
|
||||
emits('deleteTask', task.id)
|
||||
}
|
||||
function deleteTask(todo: Task) {
|
||||
emits('deleteTask', todo.id)
|
||||
}
|
||||
|
||||
function editTask(task: Task) {
|
||||
emits('editTask', task)
|
||||
}
|
||||
|
||||
function openTaskFormModal(task: Partial<Task>) {
|
||||
taskFormModalInput.value = task
|
||||
showTaskCreateModal.value = true
|
||||
}
|
||||
|
||||
function openTaskEditModal(task: Task) {
|
||||
taskFormModalInput.value = task
|
||||
showTaskEditModal.value = true
|
||||
}
|
||||
|
||||
function scheduleTask(task: Task) {
|
||||
emits('scheduleTask', task)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCard class="flex w-64 h-full" :ui="{ body: 'w-full' }">
|
||||
<UiTaskFormModal v-model:open="showTaskCreateModal" :input="taskFormModalInput" action="create"
|
||||
@submnitted="addTask" />
|
||||
<UiTaskFormModal v-model:open="showTaskEditModal" :input="taskFormModalInput" action="edit"
|
||||
@submnitted="editTask" />
|
||||
|
||||
<div class="flex flex-col h-full w-full gap-5">
|
||||
<header class="flex flex-col gap-2">
|
||||
<Title1>Calendar</Title1>
|
||||
@@ -114,41 +133,43 @@ function editTask(task: Task) {
|
||||
</header>
|
||||
<div class="flex flex-col grow justify-between">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Title1>Todos</Title1>
|
||||
<Title1>Tasks</Title1>
|
||||
<div class="flex gap-2 flex-col">
|
||||
<ListItem v-for="task in todoTasks">
|
||||
<div class="flex w-full gap-4 items-center">
|
||||
<div class="flex w-full gap-4 items-center" @dragstart="scheduleTask(task)"
|
||||
draggable="true">
|
||||
<span
|
||||
class="grow overflow-scroll py-3 overflow-shadow flex flex-row gap-2 items-center">
|
||||
<UCheckbox v-model="task.done" @change="() => editTask(task)" />{{ task.title }}
|
||||
</span>
|
||||
<div class="flex gap-1">
|
||||
<UButton size="xs" color="neutral" class="flex justify-center" icon="mingcute:pencil-line"
|
||||
@click="() => editTask(task)"/>
|
||||
<UButton size="xs" color="primary" class="flex justify-center" icon="octicon:trashcan-16"
|
||||
@click="() => deleteTask(task)" />
|
||||
<UButton size="xs" color="neutral" class="flex justify-center"
|
||||
icon="mingcute:pencil-line" @click="() => openTaskEditModal(task)" />
|
||||
<UButton size="xs" color="primary" class="flex justify-center"
|
||||
icon="octicon:trashcan-16" @click="() => deleteTask(task)" />
|
||||
</div>
|
||||
</div>
|
||||
</ListItem>
|
||||
<USeparator label="Done" v-if="todoTasks.length !== 0"/>
|
||||
<USeparator label="Done" v-if="todoTasks.length !== 0" />
|
||||
<ListItem v-for="task in doneTasks">
|
||||
<div class="flex w-full gap-4 items-center">
|
||||
<div class="flex w-full gap-4 items-center" @dragstart="scheduleTask(task)"
|
||||
draggable="true">
|
||||
<span
|
||||
class="grow overflow-scroll py-3 overflow-shadow flex flex-row gap-2 items-center">
|
||||
<UCheckbox v-model="task.done" @change="() => editTask(task)" />{{ task.title }}
|
||||
</span>
|
||||
<div class="flex gap-1">
|
||||
<UButton size="xs" color="neutral" class="flex justify-center" icon="mingcute:pencil-line"
|
||||
@click="() => editTask(task)"/>
|
||||
<UButton size="xs" color="neutral" class="flex justify-center"
|
||||
icon="mingcute:pencil-line" @click="() => openTaskEditModal(task)" />
|
||||
<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>
|
||||
</ListItem>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
135
web/components/ui/TaskFormModal.vue
Normal file
135
web/components/ui/TaskFormModal.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<script setup lang="ts">
|
||||
import { UFormField } from '#components';
|
||||
import { DateTime } from 'luxon';
|
||||
import * as z from 'zod';
|
||||
|
||||
const open = defineModel<boolean>('open', { required: true })
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'submnitted', event: Task): 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 scheduledAtDateField = ref('')
|
||||
const scheduledAtTimeField = 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') ?? ''
|
||||
dueTimeField.value = props.input.due_date?.toFormat('HH:mm') ?? ''
|
||||
scheduledAtDateField.value = props.input.scheduled_at?.toFormat('yyyy-MM-dd') ?? ''
|
||||
scheduledAtTimeField.value = props.input.scheduled_at?.toFormat('HH:mm') ?? ''
|
||||
})
|
||||
|
||||
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', Task.fromSimpleTask({
|
||||
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),
|
||||
scheduled_at: props.input.scheduled_at
|
||||
}))
|
||||
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">
|
||||
<div class="flex flex-row gap-2">
|
||||
<UFormField label="Title">
|
||||
<UInput type="text" class="w-full" placeholder="Name" v-model="titleField" required />
|
||||
</UFormField>
|
||||
<UFormField label="Estimated time in hours">
|
||||
<UInput type="number" class="grow" placeholder="estimated time in hours" v-model="estimatedTimeField"
|
||||
icon="mdi:stopwatch-outline" required />
|
||||
</UFormField>
|
||||
</div>
|
||||
<UFormField label="Deadline" :ui="{ container: 'flex flex-row gap-2'}">
|
||||
<UInput type="date" class="grow" placeholder="due data e.g 2025-06-16" v-model="dueDateField"
|
||||
icon="i-lucide-calendar" required />
|
||||
<UInput class="grow" placeholder="due time e.g 15:34" v-model="dueTimeField" icon="i-lucide-clock"
|
||||
required />
|
||||
</UFormField>
|
||||
<UFormField label="Scheduled at" :ui="{ container: 'flex flex-row gap-2'}" v-if="props.input.scheduled_at">
|
||||
<UInput type="date" class="grow" placeholder="schedule data e.g 2025-06-16" v-model="scheduledAtDateField"
|
||||
icon="i-lucide-calendar" required />
|
||||
<UInput class="grow" placeholder="schedule time e.g 15:34" v-model="scheduledAtTimeField" icon="i-lucide-clock"
|
||||
required />
|
||||
</UFormField>
|
||||
<UFormField label="Description">
|
||||
<UTextarea type="text" class="w-full" placeholder="Description" v-model="descriptionField" required />
|
||||
</UFormField>
|
||||
</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>
|
||||
@@ -7,7 +7,9 @@ import { DateTime } from 'luxon';
|
||||
import EventFormModal from '../EventFormModal.vue';
|
||||
|
||||
const events = defineModel<Event[]>('events', { required: true })
|
||||
const tasks = defineModel<Task[]>('tasks', { required: true })
|
||||
const date = defineModel<DateTime>('date', { required: true })
|
||||
const draggedTask = defineModel<DraggedTask | undefined>('draggedTask', { required: true })
|
||||
const draggedEvent = ref<DraggedEvent | undefined>()
|
||||
const createInput = ref<Partial<SimpleEvent>>({})
|
||||
const createModalOpened = ref(false)
|
||||
@@ -34,10 +36,17 @@ function pushEventWithCollisionUpdate(array: CollissionWrapper[], event: Event,
|
||||
}
|
||||
}
|
||||
|
||||
const taskEvents = computed<Event[]>(() => {
|
||||
return tasks.value
|
||||
.filter(task => task.isScheduled())
|
||||
.map(task => task.toEvent())
|
||||
})
|
||||
|
||||
const days = computed<Day[]>(() => {
|
||||
return [1, 2, 3, 4, 5, 6, 7].map((i) => {
|
||||
const eventsToDisplay = [...taskEvents.value, ...events.value]
|
||||
const currentDate = date.value.startOf('week').plus({ day: i - 1 })
|
||||
const filteredEvents = events.value.filter(
|
||||
const filteredEvents = eventsToDisplay.filter(
|
||||
(event) => event.from >= currentDate.startOf('day') && event.to <= currentDate.endOf('day')
|
||||
)
|
||||
|
||||
@@ -81,6 +90,7 @@ const emits = defineEmits<{
|
||||
(e: 'create', event: Event): void
|
||||
(e: 'edit', event: Event): void
|
||||
(e: 'delete', event: Event): void
|
||||
(e: 'edit-task', task: Task): void
|
||||
}>()
|
||||
|
||||
const hour = (num: number) => {
|
||||
@@ -149,7 +159,9 @@ function deleteEvent() {
|
||||
}
|
||||
|
||||
function moveEvent(event: Event) {
|
||||
emits('edit', event)
|
||||
if (event.task !== undefined) {
|
||||
emits('edit-task', event.task)
|
||||
} else emits('edit', event)
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -161,7 +173,8 @@ function moveEvent(event: Event) {
|
||||
<EventFormModal action="edit" @submnitted="event => edit(event)" :input="editInput"
|
||||
v-model:open="editModalOpened" />
|
||||
|
||||
<UModal v-model:open="deleteModalOpened" title="Delete Event" description="Are you sure you want to delete this event?">
|
||||
<UModal v-model:open="deleteModalOpened" title="Delete Event"
|
||||
description="Are you sure you want to delete this event?">
|
||||
<template #footer>
|
||||
<UButton variant="solid" @click="deleteEvent">
|
||||
Delete
|
||||
@@ -176,8 +189,8 @@ function moveEvent(event: Event) {
|
||||
<CalendarHeader :seperators="seperators" />
|
||||
|
||||
<CalendarCollumn v-for="day in days" :seperators="seperators" :day="day.date" :events="day.events"
|
||||
:date="date" v-model:draggedEvent="draggedEvent" @quick-create="openCreateModal"
|
||||
@edit="openEditModal" @delete="openDeleteModal" @moved="moveEvent" />
|
||||
:date="date" v-model:draggedEvent="draggedEvent" @quick-create="openCreateModal" @edit="openEditModal"
|
||||
@delete="openDeleteModal" @moved="moveEvent" @edit-task="(task) => emits('edit-task', task)" v-model:dragged-task="draggedTask" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,7 @@ const emit = defineEmits<{
|
||||
(e: 'edit', event: Event): void
|
||||
(e: 'moved', event: Event): void
|
||||
(e: 'delete', event: Event): void
|
||||
(e: 'edit-task', task: Task): void
|
||||
}>()
|
||||
|
||||
const isDragging = ref(false)
|
||||
@@ -25,6 +26,7 @@ const startY = ref(0)
|
||||
const endY = ref(0)
|
||||
const column = useTemplateRef('column')
|
||||
const draggedEvent = defineModel<DraggedEvent | undefined>('draggedEvent')
|
||||
const draggedTask = defineModel<DraggedTask | undefined>('draggedTask')
|
||||
|
||||
const height = computed(() => {
|
||||
return Math.abs(endY.value - startY.value)
|
||||
@@ -83,6 +85,30 @@ function eventMove(mouseEvent: MouseEvent, event: Event) {
|
||||
}
|
||||
|
||||
function dragover(e: DragEvent) {
|
||||
e.preventDefault()
|
||||
drawDraggedEvent(e)
|
||||
drawDraggedTask(e)
|
||||
}
|
||||
|
||||
function drawDraggedTask(event: DragEvent) {
|
||||
if (draggedTask.value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (draggedTask.value.dragInfo === undefined) {
|
||||
draggedTask.value.dragInfo = {
|
||||
height: (draggedTask.value.target.estimated_time / 60 / 24) * (column.value?.offsetHeight ?? 0),
|
||||
top: absoluteToRelativeY(event.clientY),
|
||||
date: props.day
|
||||
}
|
||||
drawDraggedTask(event)
|
||||
}
|
||||
|
||||
draggedTask.value.dragInfo.top = absoluteToRelativeY(event.clientY)
|
||||
draggedTask.value.dragInfo.date = props.day
|
||||
}
|
||||
|
||||
function drawDraggedEvent(event: DragEvent) {
|
||||
if (draggedEvent.value === undefined) {
|
||||
return
|
||||
}
|
||||
@@ -92,13 +118,33 @@ function dragover(e: DragEvent) {
|
||||
}
|
||||
|
||||
|
||||
draggedEvent.value.top = absoluteToRelativeY(e.clientY) - draggedEvent.value.offset
|
||||
draggedEvent.value.top = absoluteToRelativeY(event.clientY) - draggedEvent.value.offset
|
||||
}
|
||||
|
||||
function dragDrop(_: DragEvent) {
|
||||
draggedEvent.value?.target.updateWithDraggedEvent(draggedEvent.value, column.value?.offsetHeight ?? 0)
|
||||
console.log('dropping')
|
||||
if (draggedEvent.value !== undefined) {
|
||||
updateEventWithDraggedEvent()
|
||||
}
|
||||
|
||||
if (draggedEvent.value === undefined){
|
||||
if (draggedTask.value !== undefined) {
|
||||
console.log('dropping task')
|
||||
updateTaskWithDraggedTask()
|
||||
}
|
||||
}
|
||||
|
||||
function updateEventWithDraggedEvent() {
|
||||
if (draggedEvent.value == undefined) return
|
||||
|
||||
if (draggedEvent.value.target.task !== undefined) {
|
||||
draggedEvent.value.target.task.scheduled_at = draggedEvent.value.date.startOf('day').plus({
|
||||
minutes: draggedEvent.value.top / (column.value?.offsetHeight ?? 1) * 24 * 60
|
||||
})
|
||||
} else {
|
||||
draggedEvent.value?.target.updateWithDraggedEvent(draggedEvent.value, column.value?.offsetHeight ?? 0)
|
||||
}
|
||||
|
||||
if (draggedEvent.value === undefined) {
|
||||
draggedEvent.value = undefined
|
||||
return
|
||||
}
|
||||
@@ -107,6 +153,24 @@ function dragDrop(_: DragEvent) {
|
||||
draggedEvent.value = undefined
|
||||
}
|
||||
|
||||
function updateTaskWithDraggedTask() {
|
||||
if (draggedTask.value === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (draggedTask.value.dragInfo === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
draggedTask.value.target.scheduled_at = draggedTask.value.dragInfo.date.startOf('day').plus({
|
||||
minutes: draggedTask.value.dragInfo.top / (column.value?.offsetHeight ?? 1) * 24 * 60
|
||||
})
|
||||
|
||||
emit('edit-task', draggedTask.value.target)
|
||||
|
||||
draggedTask.value = undefined
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -119,7 +183,7 @@ function dragDrop(_: DragEvent) {
|
||||
</div>
|
||||
|
||||
<div id="col" ref="column" @mousedown="mousedown" @mouseup="mouseup" @mousemove="mouseover" @dragover="dragover"
|
||||
@dragend="dragDrop" class="relative flex flex-col grow items-center select-none">
|
||||
@dragend="dragDrop" @drop="dragDrop" class="relative flex flex-col grow items-center select-none">
|
||||
<CalendarSeperator v-for="sep in seperators" :seperator="sep">
|
||||
<hr class="w-full border-muted">
|
||||
</CalendarSeperator>
|
||||
@@ -134,9 +198,12 @@ function dragDrop(_: DragEvent) {
|
||||
<div v-if="draggedEvent !== undefined && draggedEvent.date.equals(props.day)"
|
||||
class="absolute w-11/12 top-20 bg-black opacity-45 rounded-lg"
|
||||
:style="{ height: `${draggedEvent.height}px`, top: `${draggedEvent.top}px` }"></div>
|
||||
<div v-if="draggedTask !== undefined && draggedTask.dragInfo !== undefined && draggedTask.dragInfo.date.equals(props.day)"
|
||||
class="absolute w-11/12 top-20 bg-black opacity-45 rounded-lg"
|
||||
:style="{ height: `${draggedTask.dragInfo.height}px`, top: `${draggedTask.dragInfo.top}px` }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped></style>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"moment": "^2.30.1",
|
||||
"nuxt": "^3.17.2",
|
||||
"nuxt-app": "file:",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"typescript": "^5.6.3",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.1",
|
||||
|
||||
@@ -5,63 +5,78 @@ import MainContent from '~/components/ui/MainContent.vue';
|
||||
import Sidebar from '~/components/ui/Sidebar.vue';
|
||||
import { Event, type SerializableEvent } from '~/utils/event';
|
||||
|
||||
const todos = ["Staistics", "Computer Graphics", "Webdev"]
|
||||
const {$socket} = useNuxtApp()
|
||||
|
||||
const date = ref<DateTime>(DateTime.now())
|
||||
const events = ref<Event[]>([])
|
||||
const tasks = ref<Task[]>([])
|
||||
const draggedTask = ref<DraggedTask | undefined>(undefined)
|
||||
|
||||
const { data: eventsResponse } = await useAsyncData<SerializableEvent[]>(
|
||||
const { data: eventsResponse, refresh: refreshEvent } = await useAsyncData<SerializableEvent[]>(
|
||||
'events',
|
||||
() => axios.get('/events').then(res => res.data)
|
||||
() => axios.get<SerializableEvent[]>('/events').then(res => res.data)
|
||||
);
|
||||
|
||||
const { data: tasksResponse, refresh: refreshTask } = await useAsyncData<SerializableTask[]>(
|
||||
'tasks',
|
||||
() => axios.get<SerializableTask[]>('/tasks').then(res => res.data)
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
events.value = eventsResponse.value?.map(Event.fromSerializable) ?? []
|
||||
tasks.value = tasksResponse.value?.map(Task.fromSerializable) ?? []
|
||||
$socket.on('change', async () => {
|
||||
console.log("change socket")
|
||||
location.reload()
|
||||
//await refreshEvent()
|
||||
//await refreshTask()
|
||||
})
|
||||
})
|
||||
|
||||
type Task = {
|
||||
id: number
|
||||
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) {
|
||||
console.log('posting Event')
|
||||
await axios.post('/event', event.toSerializable())
|
||||
await axios.post('/events', event.toSerializable())
|
||||
$socket.emit('change')
|
||||
}
|
||||
|
||||
async function postTask(name: string) {
|
||||
async function postTask(task: Task) {
|
||||
console.log('posting Task')
|
||||
await axios.post('/task', {
|
||||
title: name,
|
||||
description: "",
|
||||
done: false,
|
||||
estimated_time: (new Date()).toISOString(), //TODO
|
||||
due_date: (new Date()).toISOString(),
|
||||
})
|
||||
await refresh()
|
||||
const createdTask = await axios.post<SerializableTask>('/tasks', task)
|
||||
console.log(createdTask)
|
||||
task.id = createdTask.data.id
|
||||
$socket.emit('change')
|
||||
}
|
||||
|
||||
async function deleteEvent(id: number) {
|
||||
console.log('deleting Event')
|
||||
await axios.delete(`/events/${id}`)
|
||||
await refreshTask()
|
||||
$socket.emit('change')
|
||||
}
|
||||
|
||||
async function deleteTask(id: number) {
|
||||
console.log('deleting Task')
|
||||
await axios.delete(`/task/${id}`)
|
||||
await refresh()
|
||||
await axios.delete(`/tasks/${id}`)
|
||||
await refreshTask()
|
||||
$socket.emit('change')
|
||||
}
|
||||
|
||||
async function putEvent(event: Event) {
|
||||
console.log('editing event')
|
||||
await axios.put(`/events/${event.id}`, event)
|
||||
await refreshTask()
|
||||
$socket.emit('change')
|
||||
}
|
||||
|
||||
async function putTask(task: Task) {
|
||||
console.log('editing task')
|
||||
await axios.put(`/tasks/${task.id}`, task)
|
||||
await refreshTask()
|
||||
$socket.emit('change')
|
||||
}
|
||||
|
||||
function scheduleTask(task: Task) {
|
||||
draggedTask.value = { target: task, dragInfo: undefined }
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -69,8 +84,9 @@ async function deleteTask(id: number) {
|
||||
<template>
|
||||
<div class="h-screen w-screen p-4 flex flex-row gap-5">
|
||||
<Sidebar v-if="tasks !== null" v-model:tasks="tasks" v-model:date="date" @create-task="postTask"
|
||||
@delete-task="deleteTask" />
|
||||
<MainContent v-if="events !== null" v-model:events="events" v-model:date="date" @create-event="postEvent" />
|
||||
@delete-task="deleteTask" @schedule-task="scheduleTask" @edit-task="putTask"/>
|
||||
<MainContent v-if="events !== null" v-model:events="events" v-model:date="date"
|
||||
v-model:dragged-task="draggedTask" v-model:tasks="tasks" @create-event="postEvent" @edit-task="putTask" @edit-event="putEvent" @delete-event="deleteEvent"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
18
web/plugins/socket.client.ts
Normal file
18
web/plugins/socket.client.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
// Only runs on client-side
|
||||
import { io } from 'socket.io-client';
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
const socket = io('http://localhost:8080'); // Update with your backend URL
|
||||
|
||||
// Optional: handle connect
|
||||
socket.on('connect', () => {
|
||||
console.log('Connected with socket ID:', socket.id);
|
||||
});
|
||||
|
||||
// Inject globally
|
||||
return {
|
||||
provide: {
|
||||
socket,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -8,7 +8,8 @@ export class Event {
|
||||
public title: string,
|
||||
public from: DateTime,
|
||||
public to: DateTime,
|
||||
public description: string
|
||||
public description: string,
|
||||
public task: Task | undefined = undefined
|
||||
) { }
|
||||
|
||||
|
||||
@@ -144,6 +145,10 @@ export class Event {
|
||||
}
|
||||
}
|
||||
|
||||
isTask() {
|
||||
return this.task !== undefined
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type EventDimensions = {
|
||||
|
||||
98
web/utils/task.ts
Normal file
98
web/utils/task.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { DateTime } from "luxon"
|
||||
import { Event } from "./event"
|
||||
|
||||
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,
|
||||
public scheduled_at: DateTime | undefined
|
||||
) { }
|
||||
|
||||
static fromSimpleTask(simpleTask: SimpleTask) {
|
||||
return new Task(
|
||||
simpleTask.id,
|
||||
simpleTask.title,
|
||||
simpleTask.description,
|
||||
simpleTask.done,
|
||||
simpleTask.estimated_time,
|
||||
simpleTask.due_date,
|
||||
simpleTask.scheduled_at
|
||||
)
|
||||
}
|
||||
|
||||
static fromSerializable(serializableTask: SerializableTask) {
|
||||
console.log('dings', serializableTask.due_date)
|
||||
return new Task(
|
||||
serializableTask.id,
|
||||
serializableTask.title,
|
||||
serializableTask.description,
|
||||
serializableTask.done,
|
||||
serializableTask.estimated_time,
|
||||
stringToDate(serializableTask.due_date),
|
||||
stringToDate(serializableTask.scheduled_at),
|
||||
)
|
||||
}
|
||||
|
||||
isPersistent() {
|
||||
return this.id !== undefined
|
||||
}
|
||||
|
||||
isScheduled() {
|
||||
return this.scheduled_at !== undefined
|
||||
}
|
||||
|
||||
toEvent(): Event {
|
||||
const scheduledAt = this.scheduled_at ?? DateTime.now()
|
||||
return new Event(
|
||||
this.id,
|
||||
this.title,
|
||||
scheduledAt,
|
||||
scheduledAt.plus({ minutes: this.estimated_time }),
|
||||
this.description,
|
||||
this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export type SimpleTask = {
|
||||
id: number | undefined
|
||||
title: string
|
||||
description: string
|
||||
done: boolean
|
||||
estimated_time: number
|
||||
scheduled_at: DateTime | undefined
|
||||
due_date: DateTime | undefined
|
||||
}
|
||||
|
||||
export type SerializableTask = {
|
||||
id: number | undefined
|
||||
title: string
|
||||
description: string
|
||||
done: boolean
|
||||
estimated_time: number
|
||||
due_date: string | undefined
|
||||
scheduled_at: string | undefined
|
||||
created_at: string
|
||||
updated_at: string
|
||||
userid: string
|
||||
}
|
||||
|
||||
export type DraggedTask = {
|
||||
target: Task,
|
||||
dragInfo: {
|
||||
top: number,
|
||||
date: DateTime
|
||||
height: number
|
||||
} | undefined
|
||||
}
|
||||
|
||||
function stringToDate(date: string | undefined) {
|
||||
if (date === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return DateTime.fromISO(date)
|
||||
}
|
||||
Reference in New Issue
Block a user