Browse Source

Everywhere: More work on the todo page

master
Riyyi 4 weeks ago
parent
commit
18936ec703
  1. 8
      README.org
  2. 24
      nuxt.config.ts
  3. 5
      package.json
  4. 1
      src/app.vue
  5. 7
      src/assets/css/style.css
  6. 0
      src/components/Shared/Footer.vue
  7. 0
      src/components/Shared/Header.vue
  8. 0
      src/components/Shared/NavMenu.vue
  9. 18
      src/components/Table/SortingColumn.vue
  10. 59
      src/components/Todo/Add.vue
  11. 87
      src/components/Todo/Browse.vue
  12. 26
      src/pages/todos.vue
  13. 8
      src/schemas/todo.ts
  14. 15
      src/stores/todoStore.ts

8
README.org

@ -65,12 +65,16 @@ $ bun install -g @vue/language-server
$ bun install -g @vue/typescript-plugin
# Pinia
$ bun install pinia
$ bun install @pinia/nuxt # also add it to nuxt.config modules!
$ bun install pinia-plugin-persistedstate # same as above
# PrimeVue
$ bun install primevue primeicons @primevue/themes
$ bun install primevue primeicons @primevue/themes @primevue/forms
$ bun install --dev @primevue/nuxt-module
# Zod
$ bun install zod
# UUID
$ bun install uuid
#+END_SRC

24
nuxt.config.ts

@ -4,13 +4,30 @@ import Aura from "@primevue/themes/aura";
export default defineNuxtConfig({
compatibilityDate: "2024-11-01",
css: [
"primeicons/primeicons.css"
"primeicons/primeicons.css",
"~/assets/css/style.css"
],
devtools: { enabled: true },
dir: {
public: "../public"
},
modules: [
"@nuxt/eslint",
"@primevue/nuxt-module"
"@pinia/nuxt",
"@primevue/nuxt-module",
"pinia-plugin-persistedstate/nuxt",
],
pinia: {
storesDirs: ["src/stores/**"] // also auto-import nested directories
},
piniaPluginPersistedstate: {
debug: process.env.NODE_ENV === "development", // log error to console
storage: "cookies",
cookieOptions: {
sameSite: "lax", // prevent CSRF
secure: process.env.NODE_ENV !== "development" // only send over HTTPS
}
},
primevue: {
options: {
theme: {
@ -18,9 +35,6 @@ export default defineNuxtConfig({
}
}
},
dir: {
public: "../public"
},
srcDir: "src/",
ssr: false
})

5
package.json

@ -10,11 +10,14 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
"@pinia/nuxt": "^0.7.0",
"@primevue/forms": "^4.2.3",
"@primevue/themes": "^4.2.3",
"nuxt": "^3.14.1592",
"pinia": "^2.2.6",
"pinia-plugin-persistedstate": "^4.1.3",
"primeicons": "^7.0.0",
"primevue": "^4.2.3",
"uuid": "^11.0.3",
"vue": "latest",
"vue-router": "latest",
"zod": "^3.23.8"

1
src/app.vue

@ -2,4 +2,5 @@
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
<Toast />
</template>

7
src/assets/css/style.css

@ -0,0 +1,7 @@
/*----------------------------------------*/
/* General */
body {
font-family: "Segoe UI", "DejaVu Sans", sans-serif;
scroll-behavior: smooth;
}

0
src/components/shared/Footer.vue → src/components/Shared/Footer.vue

0
src/components/shared/Header.vue → src/components/Shared/Header.vue

0
src/components/shared/NavMenu.vue → src/components/Shared/NavMenu.vue

18
src/components/Table/SortingColumn.vue

@ -0,0 +1,18 @@
<template>
adasda
<Column field="title">
<template #header>
<span @click="toggleSorting('title')" class="p-datatable-column-title">Title&nbsp;
<span :class="['pi', sorting.title === 'asc' ? 'pi-arrow-circle-down' : 'pi-arrow-circle-up']"></span>
</span>
</template>
</Column>
</template>
<script setup lang="ts">
const sorting = defineModel<Record<string, string>>({ required: true });
function toggleSorting(key: string) {
sorting.value[key] = sorting.value[key] === 'asc' ? 'desc' : 'asc';
};
</script>

59
src/components/Todo/Add.vue

@ -0,0 +1,59 @@
<template>
<Form v-slot="$form" ref="formRef" :initialValues :resolver @submit="onFormSubmit"
class="flex flex-col gap-4 w-full sm:w-56">
<!-- {{ $form }} -->
<div class="flex flex-col gap-1">
<InputText @input="initial = false;" v-model="formData.title" name="title" type="text" placeholder="Title"
autocomplete="off" fluid />
<Message v-if="$form.title?.invalid" severity="error" size="small" variant="simple">
{{ $form.title.error?.message }}
</Message>
</div>
<Button :disabled="initial || !$form.valid" class="fr" type="submit" severity="secondary" label="Submit" />
</Form>
</template>
<script setup lang="ts">
import { useTodoStore } from "@/stores/todoStore";
import { todoSchema } from "@/schemas/todo";
import { zodResolver } from '@primevue/forms/resolvers/zod';
import { v4 as uuidv4 } from "uuid";
import { type FormSubmitEvent } from "@primevue/forms/form";
const store = useTodoStore();
const toast = useToast();
const resolver = zodResolver(todoSchema);
const initial = ref(true); // makes submit button disabled
const initialValues = ref({
title: ""
});
const formRef = ref<HTMLElement | null>(null);
const formData = ref({ ...initialValues.value }); // copy data from initialValues
async function onFormSubmit(e: FormSubmitEvent) {
if (e.valid) {
toast.add({ severity: "success", summary: "Todo added", life: 3000 });
store.todos.push({
id: uuidv4(),
title: e.values?.title || e.states?.title.value
});
// Reset the form
initial.value = true;
formData.value = { ...initialValues.value }; // copy data from initialValues
}
else {
toast.add({ severity: "error", summary: "Invalid request", life: 3000 });
}
};
</script>
<style scoped>
.fr {
float: right;
}
</style>

87
src/components/Todo/Browse.vue

@ -0,0 +1,87 @@
<template>
<div class="card">
<ConfirmDialog></ConfirmDialog>
<DataTable :value="computedTodos" tableStyle="min-width: 50rem" @sort="onSort" sortMode="multiple" removableSort>
<Column field="number" header="#"></Column>
<Column field="id" header="ID" sortable filterField="id" showFilterMenu filter="true"></Column>
<Column field="title" header="Title" sortable filterField="title" showFilterMenu filter="true"></Column>
<Column header="Modifier">
<template #body="slotProps">
<a @click="removeTodo(slotProps.data.id)">
<span class="pi pi-trash" style="color: var(--p-red-600);"></span>
</a>
</template>
</Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { useConfirm } from "primevue/useconfirm";
import { useToast } from "primevue/usetoast";
import { useTodoStore } from "@/stores/todoStore";
import type { Todo } from "@/schemas/todo";
const confirm = useConfirm();
const store = useTodoStore();
const toast = useToast();
const onSort = (event: any) => {
console.log(event);
};
const sorting = ref<Record<string, string>>({
id: "asc",
title: "asc",
});
function toggleSorting(key: string) {
sorting.value[key] = sorting.value[key] === 'asc' ? 'desc' : 'asc';
};
function sortTodos(): Todo[] {
let todos: Todo[] = JSON.parse(JSON.stringify(store.todos)); // deep copy
// Number
// ID
// Title
todos = (sorting.value.title === "desc")
? todos.sort((a, b) => b.title.localeCompare(a.title))
: todos.sort((a, b) => a.title.localeCompare(b.title));
return todos;
}
const computedTodos = computed(() => {
return sortTodos().map((todo, index) => ({
number: index + 1,
...todo
}));
});
const removeTodo = (id: String) => {
confirm.require({
message: "Do you want to delete this todo?",
header: "Confirmation",
icon: "pi pi-info-circle",
rejectLabel: "Cancel",
rejectProps: {
label: "Cancel",
severity: "secondary",
outlined: true
},
acceptProps: {
label: "Delete",
severity: "danger"
},
accept: () => {
toast.add({ severity: "info", summary: "Confirmed", detail: "Todo deleted", life: 3000 });
store.todos = store.todos.filter(todo => todo.id !== id);
},
reject: () => {}
});
};
</script>

26
src/pages/todos.vue

@ -1,24 +1,18 @@
<template>
<div class="card">
<DataTable :value="todos" tableStyle="min-width: 50rem">
<Column field="number" header="#"></Column>
<Column field="id" header="ID"></Column>
<Column field="title" header="Title"></Column>
<Column field="modifier" header="Modifier"></Column>
</DataTable>
</div>
<TodoBrowse />
<br>
<TodoAdd />
<br>
<Button @click="showToast()" type="button">Toast</Button>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
// import { ProductService } from '@/service/ProductService';
import { useToast } from "primevue/usetoast";
onMounted(() => {
todos.value = [
{ number: "1", id: "toby", title: "Do stuff..", modifier: "hehe"}
]
});
const toast = useToast();
const todos = ref();
async function showToast() {
toast.add({ severity: "success", summary: "This is a toast", detail: "Wow!", life: 3000 });
}
</script>

8
src/schemas/todo.ts

@ -0,0 +1,8 @@
import { z } from "zod";
export const todoSchema = z.object({
id: z.optional(z.string().uuid()),
title: z.string().min(1, "Title can't be empty"),
});
export type Todo = z.infer<typeof todoSchema>;

15
src/stores/todoStore.ts

@ -0,0 +1,15 @@
import { defineStore } from "pinia"
import type { Todo } from "@/schemas/todo"
export const useTodoStore = defineStore("todo", () => {
const todos = ref<Todo[]>([
{ id: "d8681644-74d0-4a30-90db-06baa277d0a0", title: "laundry" },
{ id: "03c5bf55-f528-43a2-89a1-1a1afb0fa4f6", title: "feed pet" },
{ id: "356cd252-bef8-4a1c-ba81-5a68d89df56e", title: "run" }
]);
return { todos }
}, {
persist: process.env.NODE_ENV === 'development' ? true : false,
})
Loading…
Cancel
Save