This commit is contained in:
parent
4cb8cad25b
commit
4830cebc70
12 changed files with 413 additions and 880 deletions
|
@ -13,4 +13,7 @@ export default defineConfig({
|
||||||
domains: ["directus.swablab.de", "files.mastodon.social"],
|
domains: ["directus.swablab.de", "files.mastodon.social"],
|
||||||
service: passthroughImageService(),
|
service: passthroughImageService(),
|
||||||
},
|
},
|
||||||
|
redirects: {
|
||||||
|
"/todo": "https://directus.swablab.de/admin/content/tasks_general",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,12 +16,9 @@
|
||||||
"@iconify-json/ph": "^1.2.1",
|
"@iconify-json/ph": "^1.2.1",
|
||||||
"@iconify/tailwind": "^1.1.3",
|
"@iconify/tailwind": "^1.1.3",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@types/alpinejs": "^3.13.11",
|
|
||||||
"alpinejs": "^3.14.6",
|
|
||||||
"astro": "^5.0.3",
|
"astro": "^5.0.3",
|
||||||
"daisyui": "^4.12.14",
|
"daisyui": "^4.12.14",
|
||||||
"tailwindcss": "^3.4.16",
|
"tailwindcss": "^3.4.16"
|
||||||
"typescript": "^5.7.2"
|
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"id": "user1"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"id": 1,
|
|
||||||
"last": "2024-05-08T18:00:00",
|
|
||||||
"next": "2024-05-08T18:00:00"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"id": "task",
|
|
||||||
"status": "in_progress",
|
|
||||||
"title": "title 1",
|
|
||||||
"date_due": "2024-01-01T12:00:00",
|
|
||||||
"labels": [
|
|
||||||
"label1"
|
|
||||||
],
|
|
||||||
"description": "description 1",
|
|
||||||
"priority": "high",
|
|
||||||
"responsibles": [
|
|
||||||
{
|
|
||||||
"directus_users_id": {
|
|
||||||
"id": "user1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directus_users_id": {
|
|
||||||
"id": "user2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
{
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "task",
|
|
||||||
"status": "in_progress",
|
|
||||||
"title": "title1",
|
|
||||||
"date_due": "2024-01-01T12:00:00",
|
|
||||||
"labels": [
|
|
||||||
"label1"
|
|
||||||
],
|
|
||||||
"description": "description1",
|
|
||||||
"priority": "high",
|
|
||||||
"responsibles": [
|
|
||||||
{
|
|
||||||
"directus_users_id": {
|
|
||||||
"id": "user1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directus_users_id": {
|
|
||||||
"id": "user2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "task",
|
|
||||||
"status": "in_progress",
|
|
||||||
"title": "title2",
|
|
||||||
"date_due": null,
|
|
||||||
"labels": [],
|
|
||||||
"description": "description2",
|
|
||||||
"priority": "medium",
|
|
||||||
"responsibles": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "user1",
|
|
||||||
"first_name": "first_name1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "user2",
|
|
||||||
"first_name": "first_name2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,33 +1,7 @@
|
||||||
const env = (import.meta as unknown as {
|
export async function directus<T>(path: string): Promise<T> {
|
||||||
env: {
|
return await fetch(`https://directus.swablab.de/${path}`)
|
||||||
DEV: boolean
|
|
||||||
SSR: boolean
|
|
||||||
}
|
|
||||||
}).env
|
|
||||||
|
|
||||||
export async function directus<T>(
|
|
||||||
path: string,
|
|
||||||
method: string = "GET",
|
|
||||||
body: string | null = null,
|
|
||||||
): Promise<T> {
|
|
||||||
return await fetch(
|
|
||||||
env.DEV && !env.SSR
|
|
||||||
? `/test/${path.split("/").at(-1)}`
|
|
||||||
: `https://directus.swablab.de/${path}`,
|
|
||||||
{
|
|
||||||
method,
|
|
||||||
credentials: "include",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((res) => {
|
.then((res) => res.data)
|
||||||
if (res.errors) throw res.errors[0].message
|
|
||||||
return res.data
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatDate(
|
export function formatDate(
|
||||||
|
@ -58,18 +32,3 @@ export const documents = {
|
||||||
"Werkstatt-AGB": "/docs/werkstatt-agb.pdf",
|
"Werkstatt-AGB": "/docs/werkstatt-agb.pdf",
|
||||||
"Werkstatt-Regeln": "/docs/werkstatt-regeln.pdf",
|
"Werkstatt-Regeln": "/docs/werkstatt-regeln.pdf",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Task = {
|
|
||||||
id: string | undefined
|
|
||||||
title: string
|
|
||||||
status: string
|
|
||||||
date_due?: string
|
|
||||||
priority: string
|
|
||||||
description: string
|
|
||||||
responsibles: {
|
|
||||||
directus_users_id: {
|
|
||||||
id: string
|
|
||||||
first_name: string
|
|
||||||
}
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,207 +0,0 @@
|
||||||
---
|
|
||||||
import Base from "../layouts/Base.astro"
|
|
||||||
|
|
||||||
const statuses = [
|
|
||||||
{ id: "not_started", title: "Nicht gestartet" },
|
|
||||||
{ id: "in_progress", title: "In Arbeit" },
|
|
||||||
{ id: "hold", title: "Wartend" },
|
|
||||||
{ id: "done", title: "Abgeschlossen" },
|
|
||||||
]
|
|
||||||
const priorities = [
|
|
||||||
{ id: "low", title: "Niedrig" },
|
|
||||||
{ id: "normal", title: "Normal" },
|
|
||||||
{ id: "high", title: "Hoch" },
|
|
||||||
]
|
|
||||||
---
|
|
||||||
|
|
||||||
<Base title="Todo">
|
|
||||||
<div x-data="task" x-cloak>
|
|
||||||
<div class="navbar glass">
|
|
||||||
<div class="navbar-start">
|
|
||||||
<a class="btn btn-ghost text-xl">todo</a>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-end space-x-2">
|
|
||||||
<a class="btn btn-success btn-square" x-on:click="submit()">
|
|
||||||
<span class="icon-[ph--floppy-disk] w-6 h-6"></span>
|
|
||||||
</a>
|
|
||||||
<a class="btn btn-error btn-square" href="/todo">
|
|
||||||
<span class="icon-[ph--x] w-6 h-6"></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div x-show="error" class="p-4 flex flex-col space-y-4">
|
|
||||||
<div
|
|
||||||
x-show="!error.includes('permission to access collection')"
|
|
||||||
class="alert alert-error"
|
|
||||||
>
|
|
||||||
<span x-text="error"></span>
|
|
||||||
</div>
|
|
||||||
<a
|
|
||||||
x-show="error.includes('permission to access collection')"
|
|
||||||
class="btn btn-primary"
|
|
||||||
href="https://directus.swablab.de/auth/login/zitadel?redirect=https://swablab.de/todo"
|
|
||||||
>
|
|
||||||
Klicke hier, um dich anzumelden
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="drawer">
|
|
||||||
<input id="drawer" type="checkbox" class="drawer-toggle" />
|
|
||||||
|
|
||||||
<div class="drawer-content">
|
|
||||||
<div class="p-4 grid md:grid-cols-3 gap-4">
|
|
||||||
<input x-model="title" class="input input-bordered md:col-span-3" />
|
|
||||||
|
|
||||||
<label class="form-control">
|
|
||||||
<div class="label">Status</div>
|
|
||||||
<select x-model="status" class="select select-bordered">
|
|
||||||
{
|
|
||||||
statuses.map((status) => (
|
|
||||||
<option value={status.id}>{status.title}</option>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="form-control">
|
|
||||||
<div class="label">Priorität</div>
|
|
||||||
<select x-model="priority" class="select select-bordered">
|
|
||||||
{
|
|
||||||
priorities.map((prio) => (
|
|
||||||
<option value={prio.id}>{prio.title}</option>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="form-control">
|
|
||||||
<div class="label">Fälligkeitdatum</div>
|
|
||||||
<input
|
|
||||||
x-model="date_due"
|
|
||||||
class="input input-bordered"
|
|
||||||
type="date"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div class="form-control md:col-span-3">
|
|
||||||
<div class="label space-x-2 justify-start">
|
|
||||||
<span>Verantwortliche</span>
|
|
||||||
<label for="drawer" class="btn btn-sm btn-primary btn-square">
|
|
||||||
+
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="flex space-x-2">
|
|
||||||
<template x-for="res in responsibles">
|
|
||||||
<button
|
|
||||||
class="btn"
|
|
||||||
x-on:click="responsibles = responsibles.filter(x => x != res)"
|
|
||||||
x-text="users.find(u => u.id == res)?.first_name ?? res"
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label class="form-control md:col-span-3 h-64">
|
|
||||||
<div class="label">Beschreibung</div>
|
|
||||||
<textarea
|
|
||||||
x-model="description"
|
|
||||||
class="textarea textarea-bordered md:col-span-3 h-64"
|
|
||||||
>
|
|
||||||
</textarea>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="drawer-side">
|
|
||||||
<label for="drawer" class="drawer-overlay"></label>
|
|
||||||
<ul class="menu p-4 w-80 min-h-full bg-base-200 text-base-content">
|
|
||||||
<template x-for="user in users">
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
x-on:click="responsibles.indexOf(user.id) == -1 && responsibles.push(user.id)"
|
|
||||||
x-text="user.first_name"
|
|
||||||
>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Base>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Alpine from "alpinejs"
|
|
||||||
import { directus, Task } from "../helper"
|
|
||||||
|
|
||||||
Alpine.data("task", () => ({
|
|
||||||
id: globalThis.location.search.replace("?", ""),
|
|
||||||
title: "",
|
|
||||||
status: "not_started",
|
|
||||||
date_due: "",
|
|
||||||
priority: "normal",
|
|
||||||
description: "",
|
|
||||||
responsibles: [],
|
|
||||||
|
|
||||||
users: [],
|
|
||||||
error: "",
|
|
||||||
|
|
||||||
init() {
|
|
||||||
directus<{ id: string; first_name: string }[]>(
|
|
||||||
`users?fields=id,first_name&filter[provider][_neq]=default`,
|
|
||||||
).then(
|
|
||||||
(res) =>
|
|
||||||
(this.users = res.sort((a, b) =>
|
|
||||||
a.first_name.localeCompare(b.first_name),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (this.id != "") {
|
|
||||||
directus<Task>(
|
|
||||||
`items/tasks_general/${this.id}?fields=*,responsibles.directus_users_id.id`,
|
|
||||||
)
|
|
||||||
.then((res) => {
|
|
||||||
this.title = res.title
|
|
||||||
this.status = res.status
|
|
||||||
this.date_due = res.date_due?.substring(0, 10) ?? ""
|
|
||||||
this.priority = res.priority
|
|
||||||
this.description = res.description
|
|
||||||
this.responsibles = res.responsibles.map(
|
|
||||||
(x) => x.directus_users_id.id,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.catch((err) => (this.error = err))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
submit() {
|
|
||||||
const task = {
|
|
||||||
id: this.id,
|
|
||||||
title: this.title,
|
|
||||||
status: this.status,
|
|
||||||
date_due: this.date_due != "" ? this.date_due : null,
|
|
||||||
priority: this.priority,
|
|
||||||
description: this.description,
|
|
||||||
responsibles: this.responsibles.map(
|
|
||||||
(r: { directus_users_id: string }) => ({
|
|
||||||
directus_users_id: r,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
if (this.id != "") {
|
|
||||||
directus(
|
|
||||||
`items/tasks_general/${this.id}`,
|
|
||||||
"PATCH",
|
|
||||||
JSON.stringify(task),
|
|
||||||
).then((_) => (document.location.href = "/todo"))
|
|
||||||
} else {
|
|
||||||
directus(`items/tasks_general`, "POST", JSON.stringify(task)).then(
|
|
||||||
(_) => (document.location.href = "/todo"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
Alpine.start()
|
|
||||||
</script>
|
|
|
@ -1,133 +0,0 @@
|
||||||
---
|
|
||||||
import Base from "../layouts/Base.astro"
|
|
||||||
|
|
||||||
const statuses = [
|
|
||||||
{ id: "not_started", title: "Nicht gestartet" },
|
|
||||||
{ id: "in_progress", title: "In Arbeit" },
|
|
||||||
{ id: "hold", title: "Wartend" },
|
|
||||||
]
|
|
||||||
---
|
|
||||||
|
|
||||||
<Base title="Todo">
|
|
||||||
<div x-data="kanban" x-cloak>
|
|
||||||
<div class="navbar glass">
|
|
||||||
<div class="navbar-start">
|
|
||||||
<a class="btn btn-ghost text-xl">todo</a>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-end">
|
|
||||||
<a class="btn btn-primary btn-square" href="/todo-edit">
|
|
||||||
<span class="icon-[ph--plus] w-6 h-6"></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div x-show="error" class="p-4 flex flex-col space-y-4">
|
|
||||||
<div
|
|
||||||
x-show="!error.includes('permission to access collection')"
|
|
||||||
class="alert alert-error"
|
|
||||||
>
|
|
||||||
<span x-text="error"></span>
|
|
||||||
</div>
|
|
||||||
<a
|
|
||||||
x-show="error.includes('permission to access collection')"
|
|
||||||
class="btn btn-primary"
|
|
||||||
href="https://directus.swablab.de/auth/login/zitadel?redirect=https://swablab.de/todo"
|
|
||||||
>
|
|
||||||
Klicke hier, um dich anzumelden
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="p-4 flex flex-col space-y-2" x-show="!error">
|
|
||||||
<div class="join">
|
|
||||||
<input
|
|
||||||
class="input input-primary w-full join-item"
|
|
||||||
placeholder="Suchen nach Titel oder Mitglied..."
|
|
||||||
x-model="search"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
class="btn btn-primary join-item"
|
|
||||||
x-on:click="search = `id:${me}`"
|
|
||||||
>
|
|
||||||
Nur meine Aufgaben
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<ul class="menu grid lg:grid-cols-3 w-full bg-base-200 rounded-box">
|
|
||||||
{
|
|
||||||
statuses.map((status) => (
|
|
||||||
<li x-data={"tasklist('" + status.id + "')"} class="flex-auto">
|
|
||||||
<a class="pointer-events-none">{status.title}</a>
|
|
||||||
<ul>
|
|
||||||
<template x-for="task in sortedTasks()">
|
|
||||||
<li>
|
|
||||||
<a x-bind:href="`/todo-edit?${task.id}`">
|
|
||||||
<span
|
|
||||||
x-show="task.responsibles.find(x => x.directus_users_id.id == me)"
|
|
||||||
class="badge badge-xs badge-info"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
x-bind:class="{'text-secondary': task.priority == 'high','text-neutral-500': task.priority == 'low'}"
|
|
||||||
x-text="task.title"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="badge badge-xs"
|
|
||||||
x-bind:class="new Date(task.date_due ?? 0) < new Date() ? `badge-error`: `badge-ghost`"
|
|
||||||
x-show="task.date_due != null"
|
|
||||||
x-text="formatDate(task.date_due, `short`)"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Base>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Alpine from "alpinejs"
|
|
||||||
import { directus, formatDate, Task } from "../helper"
|
|
||||||
|
|
||||||
Alpine.data("kanban", () => ({
|
|
||||||
me: "",
|
|
||||||
search: "",
|
|
||||||
error: "",
|
|
||||||
init() {
|
|
||||||
directus<{ id: string }>("users/me?fields=id")
|
|
||||||
.then((res) => (this.me = res.id))
|
|
||||||
.catch((err) => (this.error = err))
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
Alpine.data("tasklist", (status: string) => ({
|
|
||||||
tasks: [],
|
|
||||||
formatDate: formatDate,
|
|
||||||
init() {
|
|
||||||
directus<Task[]>(
|
|
||||||
`items/tasks_general?filter[status][_eq]=${status}&fields=*,responsibles.directus_users_id.id,responsibles.directus_users_id.first_name`,
|
|
||||||
)
|
|
||||||
.then((res) => (this.tasks = res))
|
|
||||||
.catch((err) => (this.error = err))
|
|
||||||
},
|
|
||||||
sortedTasks() {
|
|
||||||
return (this.tasks as Task[])
|
|
||||||
.filter(
|
|
||||||
(a) =>
|
|
||||||
a.title.toLowerCase().includes(this.search.toLowerCase()) ||
|
|
||||||
a.responsibles
|
|
||||||
.map(
|
|
||||||
(x) =>
|
|
||||||
`${x.directus_users_id.first_name}|id:${x.directus_users_id.id}`,
|
|
||||||
)
|
|
||||||
.join("|")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(this.search.toLowerCase()),
|
|
||||||
)
|
|
||||||
.sort((a, b) => a.title.localeCompare(b.title))
|
|
||||||
.sort((a, b) => (a.date_due ?? "z").localeCompare(b.date_due ?? "z"))
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
Alpine.start()
|
|
||||||
</script>
|
|
|
@ -30,7 +30,3 @@ input {
|
||||||
U+FEFF,
|
U+FEFF,
|
||||||
U+FFFD;
|
U+FFFD;
|
||||||
}
|
}
|
||||||
|
|
||||||
[x-cloak] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue