Initial commit
This commit is contained in:
commit
1e63e008af
48 changed files with 12715 additions and 0 deletions
74
pages/client/[id].vue
Normal file
74
pages/client/[id].vue
Normal file
|
@ -0,0 +1,74 @@
|
|||
<script setup lang="ts">
|
||||
import { useRoute, useFetch, createError } from "nuxt/app";
|
||||
import { Ref } from "vue";
|
||||
|
||||
import PagedList from "~/components/pagedList.vue";
|
||||
import { client as clientType } from "~/utils/types/database";
|
||||
|
||||
const route = useRoute();
|
||||
const id = route.params.id;
|
||||
|
||||
const clientRequest = await useFetch(`/api/clients/${id}`);
|
||||
if (clientRequest.error.value) throw createError(clientRequest.error.value?.data ?? "");
|
||||
const client = clientRequest.data as Ref<clientType>;
|
||||
console.log(client);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<div
|
||||
class="text-h4"
|
||||
:class="client.name === null ? ['font-italic'] : []"
|
||||
>
|
||||
{{ client.name ?? "[none]" }}
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol md="4" cols="12">
|
||||
<VCard>
|
||||
<VList>
|
||||
<VListItem
|
||||
v-if="client.address"
|
||||
prepend-icon="mdi-map-marker"
|
||||
>
|
||||
<VListItemTitle class="text-wrap">
|
||||
{{ client.address }}
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem
|
||||
v-if="client.email"
|
||||
prepend-icon="mdi-email"
|
||||
>
|
||||
<VListItemTitle class="text-wrap">
|
||||
{{ client.email }}
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem
|
||||
v-if="client.phone"
|
||||
prepend-icon="mdi-phone"
|
||||
>
|
||||
<VListItemTitle class="text-wrap">
|
||||
{{ client.phone }}
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VCard>
|
||||
</VCol>
|
||||
<VCol cols="12" md="8">
|
||||
<PagedList
|
||||
:records="[{a: 'owo'}, {a: 'uwu'}, {a: 'qwq'}]"
|
||||
record-key="a"
|
||||
>
|
||||
<template #text="i">
|
||||
{{ i }}
|
||||
</template>
|
||||
<template #title="i">
|
||||
{{ i }}
|
||||
</template>
|
||||
</PagedList>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
124
pages/clients.backup.vue
Normal file
124
pages/clients.backup.vue
Normal file
|
@ -0,0 +1,124 @@
|
|||
<script setup lang="ts">
|
||||
/* global $fetch */
|
||||
import { useFetch, createError } from "nuxt/app";
|
||||
import { ref, Ref } from "vue";
|
||||
import { definePageMeta } from "~/.nuxt/imports";
|
||||
import { client as clientType } from "~/utils/types/database";
|
||||
|
||||
definePageMeta({ middleware: ["auth"] });
|
||||
|
||||
const clientsRequest = await useFetch("/api/clients");
|
||||
if (clientsRequest.error.value) throw createError(clientsRequest.error.value?.data ?? "");
|
||||
const clients = clientsRequest.data as Ref<NonNullable<typeof clientsRequest.data.value>>;
|
||||
|
||||
const countRequest = await useFetch("/api/clients/count");
|
||||
if (countRequest.error.value) throw createError(countRequest.error.value?.data ?? "");
|
||||
const count = countRequest.data as Ref<NonNullable<typeof countRequest.data.value>>;
|
||||
|
||||
function rowClicked(client: string, edit = false) {
|
||||
console.log(client);
|
||||
}
|
||||
|
||||
async function rowDelete(client: string) {
|
||||
try {
|
||||
await $fetch<clientType>(`/api/clients/${client}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
clients.value = clients.value.filter(e => e.id !== client);
|
||||
count.value.count--;
|
||||
} catch (e) {
|
||||
// FIXME: show the error
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
const loadingMore = ref<boolean>(false);
|
||||
async function loadBefore() {
|
||||
loadingMore.value = true;
|
||||
|
||||
clients.value.push(...await $fetch("/api/clients", {
|
||||
query: {
|
||||
before: clients.value[clients.value.length - 1].id,
|
||||
},
|
||||
}));
|
||||
loadingMore.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VOverlay
|
||||
model-value
|
||||
origin="top center"
|
||||
:scrim="false"
|
||||
height="fit-content"
|
||||
persistent
|
||||
no-click-animation
|
||||
>
|
||||
<VAlert class="alert">
|
||||
owowowowowowowowo
|
||||
</VAlert>
|
||||
</VOverlay>
|
||||
<VRow>
|
||||
<VCol>
|
||||
<VBreadcrumbs :items="['Clients']" />
|
||||
<VSpacer />
|
||||
<div class="text-h4">
|
||||
There are {{ count?.count }} clients in the database.
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Address</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="client in clients" :key="client.id">
|
||||
<td @click="() => rowClicked(client.id)">
|
||||
{{ client.name }}
|
||||
</td>
|
||||
<td @click="() => rowClicked(client.id)">
|
||||
{{ client.address }}
|
||||
</td>
|
||||
<td class="buttons">
|
||||
<div>
|
||||
<VBtn icon="mdi-pencil" variant="text" @click="() => rowClicked(client.id, true)" />
|
||||
<VBtn icon="mdi-delete" color="red" variant="text" @click="() => rowDelete(client.id)" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</VCard>
|
||||
<VCol>
|
||||
<VBtn
|
||||
v-if="clients.length < count.count"
|
||||
color="primary"
|
||||
:loading="loadingMore"
|
||||
@click="loadBefore"
|
||||
>
|
||||
Load more
|
||||
</VBtn>
|
||||
</VCol>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.buttons {
|
||||
width: 0;
|
||||
padding-right: 4px !important;
|
||||
}
|
||||
.buttons > div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
</style>
|
105
pages/clients.vue
Normal file
105
pages/clients.vue
Normal file
|
@ -0,0 +1,105 @@
|
|||
<script setup lang="ts">
|
||||
/* global $fetch */
|
||||
import { useFetch, createError } from "nuxt/app";
|
||||
import { ref, Ref } from "vue";
|
||||
import { definePageMeta } from "~/.nuxt/imports";
|
||||
import { client as clientType } from "~/utils/types/database";
|
||||
import pagedTable from "~/components/pagedTable.vue";
|
||||
|
||||
definePageMeta({ middleware: ["auth"] });
|
||||
|
||||
const clientsRequest = await useFetch("/api/clients");
|
||||
if (clientsRequest.error.value) throw createError(clientsRequest.error.value?.data ?? "");
|
||||
const clients = clientsRequest.data as Ref<NonNullable<typeof clientsRequest.data.value>>;
|
||||
|
||||
const countRequest = await useFetch("/api/clients/count");
|
||||
if (countRequest.error.value) throw createError(countRequest.error.value?.data ?? "");
|
||||
const count = countRequest.data as Ref<NonNullable<typeof countRequest.data.value>>;
|
||||
|
||||
function rowClicked(client: string, edit = false) {
|
||||
console.log(client);
|
||||
}
|
||||
|
||||
async function rowDelete(client: string) {
|
||||
try {
|
||||
await $fetch<clientType>(`/api/clients/${client}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
clients.value = clients.value.filter(e => e.id !== client);
|
||||
count.value.count--;
|
||||
} catch (e) {
|
||||
// FIXME: show the error
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
const loadingMore = ref<boolean>(false);
|
||||
async function loadBefore() {
|
||||
loadingMore.value = true;
|
||||
|
||||
clients.value.push(...await $fetch("/api/clients", {
|
||||
query: {
|
||||
before: clients.value[clients.value.length - 1].id,
|
||||
},
|
||||
}));
|
||||
loadingMore.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VOverlay
|
||||
model-value
|
||||
origin="top center"
|
||||
:scrim="false"
|
||||
height="fit-content"
|
||||
persistent
|
||||
no-click-animation
|
||||
>
|
||||
<VAlert class="alert">
|
||||
owowowowowowowowo
|
||||
</VAlert>
|
||||
</VOverlay>
|
||||
<VRow>
|
||||
<VCol>
|
||||
<VBreadcrumbs :items="['Clients']" />
|
||||
<VSpacer />
|
||||
<div class="text-h4">
|
||||
There are {{ count?.count }} clients in the database.
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Address</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<pagedTable
|
||||
buttons
|
||||
:records="clients"
|
||||
:fields="['name', 'address']"
|
||||
record-key="id"
|
||||
@click="(id) => rowClicked(id, false)"
|
||||
@click:edit="(id) => rowClicked(id, true)"
|
||||
@click:delete="(id) => rowDelete(id)"
|
||||
/>
|
||||
</VTable>
|
||||
</VCard>
|
||||
<VCol>
|
||||
<VBtn
|
||||
v-if="clients.length < count.count"
|
||||
color="primary"
|
||||
:loading="loadingMore"
|
||||
@click="loadBefore"
|
||||
>
|
||||
Load more
|
||||
</VBtn>
|
||||
</VCol>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
28
pages/forms.vue
Normal file
28
pages/forms.vue
Normal file
|
@ -0,0 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
/* global $fetch */
|
||||
import { ref } from 'vue';
|
||||
|
||||
const question = ref();
|
||||
const answer = ref();
|
||||
|
||||
async function getAnswer() {
|
||||
const message = await $fetch("/api/echo", {
|
||||
body: question.value,
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
answer.value = message;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard width="400px">
|
||||
<template #text>
|
||||
<VTextarea v-model="question" /><br>
|
||||
<p>{{ answer }}</p>
|
||||
<VBtn @click="getAnswer">
|
||||
Send
|
||||
</VBtn>
|
||||
</template>
|
||||
</VCard>
|
||||
</template>
|
22
pages/index.vue
Normal file
22
pages/index.vue
Normal file
|
@ -0,0 +1,22 @@
|
|||
<script setup>
|
||||
import Test from '~/components/test.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>Hi mom!</h1><br>
|
||||
<Test />
|
||||
<Test />
|
||||
<Test />
|
||||
<Test />
|
||||
<Test /><br>
|
||||
<VCard
|
||||
text="..."
|
||||
variant="outlined"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
color: blue;
|
||||
}
|
||||
</style>
|
129
pages/login.vue
Normal file
129
pages/login.vue
Normal file
|
@ -0,0 +1,129 @@
|
|||
<script setup lang="ts">
|
||||
/* global $fetch */
|
||||
import { ref, watch } from "vue";
|
||||
import { VForm } from "vuetify/components";
|
||||
import { navigateTo, useCookie, useFetch, useRoute } from "nuxt/app";
|
||||
import { cookieSettings } from "~/utils/cookieSettings";
|
||||
import { definePageMeta } from "~/.nuxt/imports";
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const login = ref("");
|
||||
const password = ref("");
|
||||
const loading = ref(false);
|
||||
const error = ref<true | string>(true);
|
||||
const form = ref<VForm | null>(null);
|
||||
const loggedIn = ref<boolean>(useCookie("token", cookieSettings).value != null);
|
||||
|
||||
const redirectTo = ref(route.redirectedFrom);
|
||||
|
||||
definePageMeta({
|
||||
layout: false,
|
||||
});
|
||||
|
||||
async function submit() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await $fetch("/api/login", {
|
||||
body: { login: login.value, password: password.value },
|
||||
method: "POST",
|
||||
});
|
||||
console.log(result);
|
||||
loggedIn.value = true;
|
||||
password.value = "";
|
||||
} catch (e) {
|
||||
console.log(typeof e);
|
||||
console.error(e);
|
||||
console.log(e);
|
||||
error.value = (e as any).data.message;
|
||||
}
|
||||
loading.value = false;
|
||||
if (redirectTo.value) navigateTo(redirectTo.value.fullPath);
|
||||
if (form.value) form.value.validate();
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
await $fetch("/api/logout");
|
||||
loggedIn.value = false;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
const userInfo = ref(JSON.stringify(useFetch("/api/users/me").data));
|
||||
watch(loggedIn, updateUserInfo);
|
||||
async function updateUserInfo() {
|
||||
if (loggedIn.value) {
|
||||
try {
|
||||
userInfo.value = JSON.stringify(await $fetch("/api/users/me"));
|
||||
} catch (e) {
|
||||
// expected if the user is not logged in
|
||||
userInfo.value = "";
|
||||
}
|
||||
} else {
|
||||
userInfo.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
updateUserInfo();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard max-width="450px" class="mx-auto mt-16" variant="outlined">
|
||||
<VProgressLinear
|
||||
absolute
|
||||
:active="loading"
|
||||
:indeterminate="loading"
|
||||
color="primary"
|
||||
/>
|
||||
<template #title>
|
||||
Log in
|
||||
</template>
|
||||
<template #text>
|
||||
<VForm v-if="!loggedIn" ref="form" class="form" :disabled="loading">
|
||||
<VTextField
|
||||
v-model="login"
|
||||
label="Login"
|
||||
:rules="[() => error]"
|
||||
:disabled="loggedIn"
|
||||
autocomplete="username"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
/>
|
||||
<VTextField
|
||||
v-model="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
:rules="[() => error]"
|
||||
:disabled="loggedIn"
|
||||
autocomplete="current-password"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
class="pt-4"
|
||||
/>
|
||||
</VForm>
|
||||
<p v-if="loggedIn">
|
||||
Logged in<br>{{ userInfo }}
|
||||
</p>
|
||||
</template>
|
||||
<template #actions>
|
||||
<VBtn
|
||||
v-if="!loggedIn"
|
||||
color="primary"
|
||||
:disabled="loggedIn || loading"
|
||||
@click="submit"
|
||||
>
|
||||
Login
|
||||
</VBtn>
|
||||
<VBtn
|
||||
v-if="loggedIn"
|
||||
color="primary"
|
||||
:disabled="!loggedIn"
|
||||
@click="logout"
|
||||
>
|
||||
Logout
|
||||
</VBtn>
|
||||
</template>
|
||||
</VCard>
|
||||
</template>
|
50
pages/tableExample.vue
Normal file
50
pages/tableExample.vue
Normal file
|
@ -0,0 +1,50 @@
|
|||
<script setup lang="ts">
|
||||
import { useFetch } from '#app';
|
||||
import { ref } from 'vue';
|
||||
const tableContent = ref(await useFetch("/api/dbtest").data);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card width="min-content">
|
||||
<v-table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>id</th>
|
||||
<th>nazwa</th>
|
||||
<th>adres</th>
|
||||
<th>kontakt</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in tableContent" :key="row.id">
|
||||
<td>{{ row.id }}</td>
|
||||
<td>{{ row.nazwa }}</td>
|
||||
<td>{{ row.adres }}</td>
|
||||
<td>{{ row.kontakt }}</td>
|
||||
<td>
|
||||
<v-btn-group variant="plain">
|
||||
<v-btn icon="mdi-pencil" color="main" />
|
||||
<v-btn icon="mdi-delete" color="red" />
|
||||
</v-btn-group>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="last">
|
||||
<td>*</td>
|
||||
<td><v-text-field label="nazwa" density="compact" /></td>
|
||||
<td><v-text-field label="adres" density="compact" /></td>
|
||||
<td><v-text-field label="kontakt" density="compact" /></td>
|
||||
<td><v-btn icon="mdi-plus" variant="plain" class="rounded" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.last td:not(:last-child):not(:first-child) {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
</style>
|
Loading…
Add table
Add a link
Reference in a new issue