Delete todo app, add blog website
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 v-if="$slots.namedslot">
|
||||
<strong v-if="$slots.namedslot">
|
||||
<slot name="namedslot" mdc-unwrap="p" />
|
||||
</h2>
|
||||
</strong>
|
||||
<h2 v-else>Broken Slot</h2>
|
||||
|
||||
<slot mdc-unwrap="p" />
|
||||
<p>
|
||||
<slot mdc-unwrap="p" />
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
namedslot: String,
|
||||
});
|
||||
defineProps({
|
||||
namedslot: String,
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,2 +1,11 @@
|
||||
<template>
|
||||
<footer class="row mb-2">
|
||||
<div class="col-12">
|
||||
© {{ year }} Rick van Vonderen
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const year = new Date().getFullYear();
|
||||
</script>
|
||||
|
||||
@@ -1,61 +1,30 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<Menubar :model="items">
|
||||
<template #item="{ item, props, hasSubmenu }">
|
||||
<router-link v-if="item.route" v-slot="{ href, navigate }" :to="item.route" custom>
|
||||
<a v-ripple :href="href" v-bind="props.action" @click="navigate">
|
||||
<span :class="item.icon" />
|
||||
<span>{{ item.label }}</span>
|
||||
</a>
|
||||
</router-link>
|
||||
<a v-else v-ripple :href="item.url" :target="item.target" v-bind="props.action">
|
||||
<span :class="item.icon" />
|
||||
<span>{{ item.label }}</span>
|
||||
<span v-if="hasSubmenu" class="pi pi-fw pi-angle-down" />
|
||||
</a>
|
||||
</template>
|
||||
</Menubar>
|
||||
</div>
|
||||
<BNavbar v-b-color-mode="'dark'" toggleable="lg" variant="dark" fixed="top">
|
||||
<BNavbarBrand to="/">
|
||||
<IFaHome /> Home
|
||||
</BNavbarBrand>
|
||||
<BNavbarToggle target="nav-collapse" />
|
||||
<BCollapse id="nav-collapse" is-nav>
|
||||
<!-- Left aligned nav items -->
|
||||
<BNavbarNav>
|
||||
<BNavItem to="/articles">Articles</BNavItem>
|
||||
<BNavItem to="/todos">Todos</BNavItem>
|
||||
</BNavbarNav>
|
||||
<!-- Right aligned nav items -->
|
||||
<BNavbarNav class="ms-auto mb-2 mb-lg-0">
|
||||
<BNavItem href="https://git.riyyi.com/riyyi" target="_blank">
|
||||
<IFaCoffee /> Gitea
|
||||
</BNavItem>
|
||||
<BNavItem href="https://github.com/riyyi" target="_blank">
|
||||
<IFaGithub /> Github
|
||||
</BNavItem>
|
||||
<BNavItem href="https://gitlab.com/riyyi" target="_blank">
|
||||
<IFaGitlab /> Gitlab
|
||||
</BNavItem>
|
||||
<BNavItem href="https://linkedin.com/in/rickvanvonderen" target="_blank">Linked
|
||||
<IFaLinkedinSquare />
|
||||
</BNavItem>
|
||||
</BNavbarNav>
|
||||
</BCollapse>
|
||||
</BNavbar>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const currentDate = new Date().toLocaleString("nl-NL");
|
||||
|
||||
const items = ref([
|
||||
{
|
||||
label: "Home",
|
||||
icon: "pi pi-home",
|
||||
route: "/"
|
||||
},
|
||||
{
|
||||
label: "Todos",
|
||||
icon: "pi pi-check",
|
||||
command: () => {
|
||||
router.push("/todos");
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Articles",
|
||||
icon: "pi pi-link",
|
||||
route: "/articles"
|
||||
},
|
||||
{
|
||||
label: "Service",
|
||||
icon: "pi pi-link",
|
||||
route: "#"
|
||||
},
|
||||
{
|
||||
label: "Contact",
|
||||
icon: "pi pi-link",
|
||||
route: "#"
|
||||
},
|
||||
{
|
||||
label: "Date: " + currentDate,
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<template>
|
||||
adasda
|
||||
<Column field="title">
|
||||
<template #header>
|
||||
<span @click="toggleSorting('title')" class="p-datatable-column-title">Title
|
||||
<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>
|
||||
@@ -1,59 +0,0 @@
|
||||
<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>
|
||||
@@ -1,94 +0,0 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<ConfirmDialog></ConfirmDialog>
|
||||
<DataTable :value="computedTodos" :lazy="true" tableStyle="min-width: 50rem" removableSort
|
||||
:sortMode="'multiple'" v-model:multiSortMeta="multiSortMeta">
|
||||
<Column field="number" header="#" />
|
||||
<Column field="id" header="ID" sortable />
|
||||
<Column field="title" header="Title" sortable />
|
||||
<Column header="Modifier">
|
||||
<template #body="slotProps">
|
||||
<a @click="removeTodo(slotProps.data.id)" v-tooltip.top="'Delete'" class="pointer">
|
||||
<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 { DataTableSortMeta } from "primevue/datatable";
|
||||
import type { Todo } from "@/schemas/todo";
|
||||
|
||||
const confirm = useConfirm();
|
||||
const store = useTodoStore();
|
||||
const toast = useToast();
|
||||
|
||||
// Reactive bindings for sorting
|
||||
const multiSortMeta = ref<DataTableSortMeta[]>([]);
|
||||
|
||||
function sortTodos(): Todo[] {
|
||||
let todos: Todo[] = JSON.parse(JSON.stringify(store.todos)); // deep copy
|
||||
|
||||
// Loop in reverse order to apply the sorting correctly
|
||||
for (let i = multiSortMeta.value.length - 1; i >= 0; i--) {
|
||||
|
||||
const sort: DataTableSortMeta = multiSortMeta.value[i];
|
||||
const value = (item: Todo): string => {
|
||||
if (typeof sort.field === "string") {
|
||||
return item[sort.field as keyof Todo] || "";
|
||||
}
|
||||
else if (typeof sort.field === "function") {
|
||||
return sort.field(item) || "";
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
todos.sort((a, b) => sort.order! * value(a).localeCompare(value(b)));
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user