feat: basic ui and workflow
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "tout-doux",
|
"name": "tout-doux",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@lucide/vue": "^1.23.0",
|
||||||
"vue": "^3.5.38",
|
"vue": "^3.5.38",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -141,6 +142,8 @@
|
|||||||
|
|
||||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
|
"@lucide/vue": ["@lucide/vue@1.23.0", "", { "peerDependencies": { "vue": ">=3.0.1" } }, "sha512-9SIYeY5K+R1iv8F8JUKMGSL7Pck/86BJ8djtZz/lnYsoHtKFUj1H2z6+PvKnC/8blZ8tqTDeDXAoTKobuX80hg=="],
|
||||||
|
|
||||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="],
|
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="],
|
||||||
|
|
||||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||||
|
|||||||
+1
-2
@@ -1,8 +1,7 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<!-- TODO: 1. rename app -->
|
<title>Tout Doux</title>
|
||||||
<title>Change this you moron</title>
|
|
||||||
<link rel="stylesheet" href="/resources/css/style.css" />
|
<link rel="stylesheet" href="/resources/css/style.css" />
|
||||||
<!-- <link rel="icon" href="/favicon.ico"> -->
|
<!-- <link rel="icon" href="/favicon.ico"> -->
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"lint-fix:oxlint": "oxlint . --fix"
|
"lint-fix:oxlint": "oxlint . --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@lucide/vue": "^1.23.0",
|
||||||
"vue": "^3.5.38"
|
"vue": "^3.5.38"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
+11
-1
@@ -1,2 +1,12 @@
|
|||||||
|
@import url("https://fonts.googleapis.com/css2?family=Sour+Gummy:ital,wght@0,100..900;1,100..900&display=swap");
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@plugin "daisyui";
|
@plugin "daisyui" {
|
||||||
|
themes: cupcake --default;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Sour Gummy", sans-serif;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
font-style: normal;
|
||||||
|
font-variation-settings: "wdth" 100;
|
||||||
|
}
|
||||||
|
|||||||
+40
-49
@@ -1,36 +1,45 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onBeforeMount } from "vue";
|
import { ref, onMounted, onBeforeMount } from "vue";
|
||||||
import { getComments, postComment } from "@/api/comments";
|
import type { Task } from "@/types";
|
||||||
import type { Comment } from "@/types";
|
import { getTasks, createTask } from "./api/tasks";
|
||||||
|
import { CirclePlus } from "@lucide/vue";
|
||||||
|
import TaskItem from "@/components/TaskItem.vue";
|
||||||
|
|
||||||
const visible = ref<boolean>(false);
|
const visible = ref<boolean>(false);
|
||||||
const comments = ref<Comment[]>([]);
|
const taskInput = ref<string>("");
|
||||||
const commentInput = ref<string>("");
|
const tasks = ref<Task[]>([]);
|
||||||
|
const list = ref<string>("test");
|
||||||
|
|
||||||
function fetchComments() {
|
function fetchTasks() {
|
||||||
getComments()
|
getTasks(list.value)
|
||||||
.then((data: Comment[]) => {
|
.then((data: Task[]) => {
|
||||||
comments.value = data;
|
tasks.value = data;
|
||||||
|
setTimeout(fetchTasks, 10000);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
//ignore
|
// ignore
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendComment() {
|
function newTask() {
|
||||||
if (commentInput.value.trim()) {
|
if (taskInput.value.trim()) {
|
||||||
postComment(commentInput.value.trim())
|
createTask(list.value, {
|
||||||
.finally(() => {
|
name: taskInput.value.trim(),
|
||||||
commentInput.value = "";
|
reset_cron: null,
|
||||||
fetchComments();
|
})
|
||||||
|
.then((data: Task) => {
|
||||||
|
tasks.value.push(data);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
//ignore
|
//ignore
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
taskInput.value = "";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(fetchComments);
|
onBeforeMount(fetchTasks);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -43,52 +52,34 @@ onMounted(() => {
|
|||||||
<main :style="{ display: visible ? 'inherit' : 'none' }">
|
<main :style="{ display: visible ? 'inherit' : 'none' }">
|
||||||
<div class="hero bg-base-200 min-h-screen">
|
<div class="hero bg-base-200 min-h-screen">
|
||||||
<div class="hero-content text-center">
|
<div class="hero-content text-center">
|
||||||
<div class="max-w-md">
|
<div class="max-w-md min-w-96">
|
||||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
<ul class="list bg-base-100 rounded-box shadow-md">
|
||||||
<li class="p-4 pb-2 text-xl opacity-60 tracking-wide">
|
<li
|
||||||
Comments
|
class="p-4 pb-2 text-4xl font-bold opacity-60 tracking-wide select-none"
|
||||||
|
>
|
||||||
|
Tout Doux
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li
|
<li v-for="(task, i) in tasks" :key="task.id">
|
||||||
v-for="comment in comments"
|
<TaskItem v-if="tasks[i]" v-model="tasks[i]" />
|
||||||
:key="comment.id"
|
|
||||||
class="list-row"
|
|
||||||
>
|
|
||||||
<div class="avatar avatar-placeholder">
|
|
||||||
<div
|
|
||||||
class="bg-neutral text-neutral-content size-10 rounded-box"
|
|
||||||
>
|
|
||||||
<span class="text-xs">{{
|
|
||||||
comment.content[0]
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="text-xs opacity-60">
|
|
||||||
{{ comment.created_at }}
|
|
||||||
</div>
|
|
||||||
<div class="font-semibold">
|
|
||||||
{{ comment.content }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
<li class="list-row">
|
<li class="list-row">
|
||||||
<div>
|
<div class="list-col-grow">
|
||||||
<input
|
<input
|
||||||
v-model="commentInput"
|
v-model="taskInput"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Type here"
|
placeholder="New Task"
|
||||||
class="input"
|
class="input"
|
||||||
@keyup.enter="sendComment"
|
@keyup.enter="newTask"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
class="btn"
|
class="btn btn-square"
|
||||||
:disabled="commentInput.trim().length === 0"
|
:disabled="taskInput.trim().length === 0"
|
||||||
@click="sendComment"
|
@click="newTask"
|
||||||
>
|
>
|
||||||
Send
|
<CirclePlus />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,32 +1,44 @@
|
|||||||
import type { RawTask, TaskCreateData, TaskUpdateData } from "@/types";
|
import type { RawTask, Task, TaskCreateData, TaskUpdateData } from "@/types";
|
||||||
|
|
||||||
export async function getTasks(list: string): Promise<RawTask[]> {
|
function parseTask(task: RawTask): Task {
|
||||||
|
return {
|
||||||
|
id: task.id,
|
||||||
|
name: task.name,
|
||||||
|
reset_cron: task.reset_cron,
|
||||||
|
check_date: task.check_date ? new Date(task.check_date) : null,
|
||||||
|
previous_check_date: task.previous_check_date
|
||||||
|
? new Date(task.previous_check_date)
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTasks(list: string): Promise<Task[]> {
|
||||||
const response = await fetch(`/api/lists/${list}/tasks`);
|
const response = await fetch(`/api/lists/${list}/tasks`);
|
||||||
return await response.json();
|
return (await response.json()).map(parseTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createTask(
|
export async function createTask(
|
||||||
list: string,
|
list: string,
|
||||||
data: TaskCreateData,
|
data: TaskCreateData,
|
||||||
): Promise<RawTask> {
|
): Promise<Task> {
|
||||||
const response = await fetch(`/api/lists/${list}/tasks`, {
|
const response = await fetch(`/api/lists/${list}/tasks`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ ...data }),
|
body: JSON.stringify({ ...data }),
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
});
|
});
|
||||||
return await response.json();
|
return parseTask(await response.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateTask(
|
export async function updateTask(
|
||||||
taskId: string,
|
taskId: string,
|
||||||
data: TaskUpdateData,
|
data: TaskUpdateData,
|
||||||
): Promise<RawTask> {
|
): Promise<Task> {
|
||||||
const response = await fetch(`/api/tasks/${taskId}`, {
|
const response = await fetch(`/api/tasks/${taskId}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify({ ...data }),
|
body: JSON.stringify({ ...data }),
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
});
|
});
|
||||||
return await response.json();
|
return parseTask(await response.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteTask(taskId: string): Promise<void> {
|
export async function deleteTask(taskId: string): Promise<void> {
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import type { Task } from "@/types";
|
||||||
|
import { Copy, Trash, Check, X } from "@lucide/vue";
|
||||||
|
|
||||||
|
const editMode = ref<boolean>(false);
|
||||||
|
|
||||||
|
const task = defineModel<Task>({ required: true });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="list-row text-left">
|
||||||
|
<div class="content-center">
|
||||||
|
<input type="checkbox" class="checkbox" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!editMode"
|
||||||
|
class="cursor-pointer text-lg select-none"
|
||||||
|
@click.prevent="editMode = true"
|
||||||
|
>
|
||||||
|
{{ task.name }}
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<div>
|
||||||
|
<input v-model="task.name" type="text" class="input mb-1" />
|
||||||
|
<br />
|
||||||
|
<button class="btn btn-square me-1" @click="editMode = false">
|
||||||
|
<X />
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-square me-1">
|
||||||
|
<Check />
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-square me-1">
|
||||||
|
<Copy />
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-square">
|
||||||
|
<Trash />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -16,6 +16,14 @@ export interface RawTask {
|
|||||||
previous_check_date: string | null;
|
previous_check_date: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Task {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
reset_cron: string | null;
|
||||||
|
check_date: Date | null;
|
||||||
|
previous_check_date: Date | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TaskCreateData {
|
export interface TaskCreateData {
|
||||||
name: string;
|
name: string;
|
||||||
reset_cron: string | null;
|
reset_cron: string | null;
|
||||||
|
|||||||
Reference in New Issue
Block a user