Compare commits
No commits in common. "ed739e27fd02d60a4525c450b3572a38eb41e9eb" and "10ff342991b44f96ac9e7a9dede9608e410c87fc" have entirely different histories.
ed739e27fd
...
10ff342991
10 changed files with 4207 additions and 2885 deletions
|
@ -1,25 +1,15 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
import FormClient from "~/components/formClient.vue";
|
type optionalMap<Optional> = Optional extends true ? undefined : string | number;
|
||||||
|
// type typeMap<Type extends string = {};
|
||||||
|
|
||||||
type optionalMap<Optional, type> = Optional extends true ? undefined | type : type;
|
export type fieldDefinition<Optional extends boolean = boolean> = {
|
||||||
|
|
||||||
type typeMap = {
|
|
||||||
"text": string,
|
|
||||||
"password": string,
|
|
||||||
"number": number,
|
|
||||||
"boolean": boolean,
|
|
||||||
"none": undefined,
|
|
||||||
"client": `${bigint}`
|
|
||||||
};
|
|
||||||
|
|
||||||
export type fieldDefinition<Optional extends boolean = boolean, type extends keyof typeMap = keyof typeMap> = {
|
|
||||||
key: string,
|
key: string,
|
||||||
label?: string,
|
label?: string,
|
||||||
type: type,
|
type: "text" | "password" | "number",
|
||||||
optional?: Optional,
|
optional?: Optional,
|
||||||
value?: optionalMap<Optional, typeMap[type]>,
|
value?: optionalMap<Optional>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -32,7 +22,7 @@ const emit = defineEmits<{
|
||||||
(e: "updateSubModelValue", key: fieldDefinition["key"], value: fieldDefinition["value"]): void,
|
(e: "updateSubModelValue", key: fieldDefinition["key"], value: fieldDefinition["value"]): void,
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const modelValue = ref<{[key: string]: string | number | boolean | undefined}>({});
|
const modelValue = ref<{[key: string]: string | number | undefined}>({});
|
||||||
|
|
||||||
for (const i of props.fields) {
|
for (const i of props.fields) {
|
||||||
modelValue.value[i.key] = i.value;
|
modelValue.value[i.key] = i.value;
|
||||||
|
@ -49,43 +39,12 @@ emit("update:modelValue", modelValue.value);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-for="i of fields" :key="i.key">
|
<VTextField
|
||||||
<VTextField
|
v-for="i of fields"
|
||||||
v-if="i.type == 'text'"
|
:key="i.key"
|
||||||
:model-value="modelValue[i.key]"
|
:model-value="modelValue[i.key]"
|
||||||
:label="i.label"
|
:label="i.label"
|
||||||
type="text"
|
:type="i.type"
|
||||||
@update:model-value="v => updateModel(i.key, v)"
|
@update:model-value="v => updateModel(i.key, v)"
|
||||||
/>
|
/>
|
||||||
<VTextField
|
|
||||||
v-if="i.type == 'password'"
|
|
||||||
:model-value="modelValue[i.key]"
|
|
||||||
:label="i.label"
|
|
||||||
type="password"
|
|
||||||
@update:model-value="v => updateModel(i.key, v)"
|
|
||||||
/>
|
|
||||||
<VTextField
|
|
||||||
v-if="i.type == 'number'"
|
|
||||||
:model-value="modelValue[i.key]"
|
|
||||||
:label="i.label"
|
|
||||||
type="number"
|
|
||||||
@update:model-value="v => updateModel(i.key, Number(v))"
|
|
||||||
/>
|
|
||||||
<v-checkbox
|
|
||||||
v-if="i.type == 'boolean'"
|
|
||||||
:label="i.label"
|
|
||||||
:model-value="modelValue[i.key]"
|
|
||||||
@update:model-value="v => updateModel(i.key, Boolean(v))"
|
|
||||||
/>
|
|
||||||
<p v-if="i.type == 'none'">
|
|
||||||
{{ i.label }}
|
|
||||||
</p>
|
|
||||||
<FormClient
|
|
||||||
v-if="i.type == 'client'"
|
|
||||||
:model-value="modelValue[i.key] as `${bigint}`"
|
|
||||||
:label="i.label"
|
|
||||||
:optional="i.optional"
|
|
||||||
@update:model-value="v => updateModel(i.key, v)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useFetch, createError } from '#app';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
label?: string,
|
|
||||||
optional?: boolean,
|
|
||||||
modelValue?: `${bigint}`,
|
|
||||||
}>();
|
|
||||||
|
|
||||||
// eslint-disable-next-line func-call-spacing
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: "update:modelValue", value: `${bigint}`): void,
|
|
||||||
}>();
|
|
||||||
|
|
||||||
// FIXME: allow to search all clients instead of newest 50 (needs api call)
|
|
||||||
const clientsRequest = await useFetch("/api/clients");
|
|
||||||
if (clientsRequest.error.value) throw createError(clientsRequest.error.value?.data ?? "");
|
|
||||||
const clients = clientsRequest.data.value?.map((e) => {
|
|
||||||
return {
|
|
||||||
value: e.id,
|
|
||||||
title: e.name ?? `[null] (${e.id})`,
|
|
||||||
props: {
|
|
||||||
subtitle: e.address,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}) ?? [];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<v-autocomplete
|
|
||||||
:label="label ?? 'Client'"
|
|
||||||
:model-value="modelValue"
|
|
||||||
:items="clients"
|
|
||||||
:clearable="optional"
|
|
||||||
@update:model-value="v => emit('update:modelValue', v)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
|
@ -1,13 +1,10 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useDisplay } from "vuetify/lib/framework.mjs";
|
import { useDisplay } from "vuetify/lib/framework.mjs";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { navigateTo, useRoute } from "#app";
|
import { navigateTo } from "#app";
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const { mobile } = useDisplay();
|
const { mobile } = useDisplay();
|
||||||
const navOpen = ref(!mobile.value);
|
const navOpen = ref(!mobile.value);
|
||||||
const navSelected = ref([route.path]);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -16,10 +13,9 @@ const navSelected = ref([route.path]);
|
||||||
<VToolbarTitle>Database Project</VToolbarTitle>
|
<VToolbarTitle>Database Project</VToolbarTitle>
|
||||||
</VAppBar>
|
</VAppBar>
|
||||||
<VNavigationDrawer v-model="navOpen" :temporary="mobile">
|
<VNavigationDrawer v-model="navOpen" :temporary="mobile">
|
||||||
<VList v-model:selected="navSelected" density="compact" nav>
|
<VList density="compact" nav>
|
||||||
<VListItem prepend-icon="mdi-login" title="Login" value="/login" @click="navigateTo('/login')" />
|
<VListItem prepend-icon="mdi-login" title="Login" value="/login" @click="navigateTo('/login')" />
|
||||||
<VListItem prepend-icon="mdi-account" title="Clients" value="/clients" @click="navigateTo('/clients')" />
|
<VListItem prepend-icon="mdi-account" title="Clients" value="/clients" @click="navigateTo('/clients')" />
|
||||||
<VListItem prepend-icon="mdi-receipt-text" title="Orders" value="/orders" @click="navigateTo('/orders')" />
|
|
||||||
<VDivider />
|
<VDivider />
|
||||||
</VList>
|
</VList>
|
||||||
</VNavigationDrawer>
|
</VNavigationDrawer>
|
||||||
|
|
6758
package-lock.json
generated
6758
package-lock.json
generated
File diff suppressed because it is too large
Load diff
11
package.json
11
package.json
|
@ -16,14 +16,15 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
||||||
"@typescript-eslint/parser": "^6.9.1",
|
"@typescript-eslint/parser": "^6.9.1",
|
||||||
"eslint": "^8.39.0",
|
"eslint": "^8.39.0",
|
||||||
"nuxt": "3.8.2",
|
"nuxt": "3.8.1",
|
||||||
"prisma": "5.7.0",
|
"prisma": "5.5.2",
|
||||||
"sass": "^1.62.0",
|
"sass": "^1.62.0",
|
||||||
"vite-plugin-vuetify": "^2.0.1",
|
"vite-plugin-vuetify": "^1.0.2",
|
||||||
"vuetify": "^3.1.15"
|
"vuetify": "^3.1.15"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "5.7.0",
|
"@prisma/client": "5.5.2",
|
||||||
"@prisma/migrate": "5.7.0"
|
"@prisma/migrate": "^5.5.2",
|
||||||
|
"mysql2": "^3.2.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ const formData = ref<any>({
|
||||||
|
|
||||||
function normalizeForm() {
|
function normalizeForm() {
|
||||||
for (const i in formData.value)
|
for (const i in formData.value)
|
||||||
formData.value[i] = formData.value[i] === "" || formData.value[i] === undefined ? null : formData.value[i];
|
formData.value[i] = formData.value[i] === "" ? null : formData.value[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
|
@ -97,7 +97,6 @@ async function handleSubmit() {
|
||||||
body: formData.value,
|
body: formData.value,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
clients.value.unshift(result);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
submitting.value = false;
|
submitting.value = false;
|
||||||
|
|
186
pages/orders.vue
186
pages/orders.vue
|
@ -1,186 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
/* global $fetch */
|
|
||||||
import { ref, type Ref } from "vue";
|
|
||||||
import { VBtn } from "vuetify/components";
|
|
||||||
import type { NuxtError } from "#app";
|
|
||||||
|
|
||||||
import Alerts, { type AlertData } from "~/components/alerts.vue";
|
|
||||||
import { type fieldDefinition } from "~/components/entryEditor.vue";
|
|
||||||
|
|
||||||
import { definePageMeta, useFetch, createError, useRoute, navigateTo } from "#imports";
|
|
||||||
|
|
||||||
definePageMeta({ middleware: ["auth"] });
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const alerts = ref<Array<AlertData>>([]);
|
|
||||||
|
|
||||||
const ordersRequest = await useFetch("/api/orders");
|
|
||||||
if (ordersRequest.error.value) throw createError(ordersRequest.error.value?.data ?? "");
|
|
||||||
const orders = ordersRequest.data as Ref<NonNullable<typeof ordersRequest.data.value>>;
|
|
||||||
|
|
||||||
const countRequest = await useFetch("/api/orders/count");
|
|
||||||
if (countRequest.error.value) throw createError(countRequest.error.value?.data ?? "");
|
|
||||||
const count = countRequest.data as Ref<NonNullable<typeof countRequest.data.value>>;
|
|
||||||
|
|
||||||
const createMode = ref<boolean>(route.query?.create === "1");
|
|
||||||
|
|
||||||
async function rowClicked(client: string, edit = false) {
|
|
||||||
await navigateTo({
|
|
||||||
path: `/order/${client}`,
|
|
||||||
query: { edit: edit ? 1 : undefined },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function rowDelete(client: string) {
|
|
||||||
try {
|
|
||||||
await $fetch(`/api/orders/${client}` as "api/orders/:id", {
|
|
||||||
method: "DELETE",
|
|
||||||
});
|
|
||||||
orders.value = orders.value.filter(e => e.id !== client);
|
|
||||||
count.value.count--;
|
|
||||||
} catch (e) {
|
|
||||||
alerts.value.push({ text: (e as NuxtError).message, type: "error" });
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadingMore = ref<boolean>(false);
|
|
||||||
async function loadBefore() {
|
|
||||||
loadingMore.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
orders.value.push(...await $fetch("/api/orders", {
|
|
||||||
query: {
|
|
||||||
before: orders.value[orders.value.length - 1].id,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
} catch (e) {
|
|
||||||
alerts.value.push({ text: (e as NuxtError).message });
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
loadingMore.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function editorFields(): fieldDefinition[] {
|
|
||||||
return [
|
|
||||||
{ key: "clientId", type: "client", label: "Client" },
|
|
||||||
{ key: "draft", type: "boolean", label: "Draft", value: true },
|
|
||||||
{ key: "imported_products", type: "none", label: "imported_produts - TBA" },
|
|
||||||
{ key: "work", type: "none", label: "work - TBA" },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitting = ref<boolean>(false);
|
|
||||||
|
|
||||||
// const updateForm = ref<VForm | null>(null);
|
|
||||||
const formButton = ref<VBtn | null>(null);
|
|
||||||
const formData = ref<any>({});
|
|
||||||
|
|
||||||
function normalizeForm() {
|
|
||||||
for (const i in formData.value)
|
|
||||||
formData.value[i] = formData.value[i] === "" ? null : formData.value[i];
|
|
||||||
formData.value.imported_products = [];
|
|
||||||
formData.value.work = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSubmit() {
|
|
||||||
submitting.value = true;
|
|
||||||
normalizeForm();
|
|
||||||
try {
|
|
||||||
const result = await $fetch(
|
|
||||||
"/api/orders", {
|
|
||||||
method: "POST",
|
|
||||||
body: formData.value,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
submitting.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
submitting.value = false;
|
|
||||||
createMode.value = false;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Alerts :alerts="alerts" />
|
|
||||||
<VDialog
|
|
||||||
v-model="createMode"
|
|
||||||
:persistent="submitting"
|
|
||||||
:activator="formButton as unknown as (Element | null) ?? undefined"
|
|
||||||
width="auto"
|
|
||||||
>
|
|
||||||
<VCard width="400px" :loading="submitting">
|
|
||||||
<VCardTitle>
|
|
||||||
Create client
|
|
||||||
</VCardTitle>
|
|
||||||
<VForm
|
|
||||||
ref="updateForm"
|
|
||||||
:disabled="submitting"
|
|
||||||
class="px-4"
|
|
||||||
>
|
|
||||||
<EntryEditor
|
|
||||||
:fields="editorFields()"
|
|
||||||
@update-sub-model-value="(k, v) => { formData[k] = v; }"
|
|
||||||
/>
|
|
||||||
</VForm>
|
|
||||||
<VCardActions>
|
|
||||||
<VBtn
|
|
||||||
color="primary"
|
|
||||||
@click="handleSubmit"
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</VBtn>
|
|
||||||
</VCardActions>
|
|
||||||
</VCard>
|
|
||||||
</VDialog>
|
|
||||||
<VRow>
|
|
||||||
<VCol>
|
|
||||||
<VBreadcrumbs :items="['Orders']" />
|
|
||||||
<VSpacer />
|
|
||||||
<div class="text-h4">
|
|
||||||
There are {{ count?.count }} orders in the database.
|
|
||||||
</div>
|
|
||||||
<VBtn
|
|
||||||
ref="formButton"
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</VBtn>
|
|
||||||
</VCol>
|
|
||||||
</VRow>
|
|
||||||
<VRow>
|
|
||||||
<VCol cols="12">
|
|
||||||
<VCard>
|
|
||||||
<VTable>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Client</th>
|
|
||||||
<th>Draft</th>
|
|
||||||
<th />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<pagedTable
|
|
||||||
buttons
|
|
||||||
:records="orders"
|
|
||||||
:fields="['client', 'Draft']"
|
|
||||||
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="orders.length < count.count"
|
|
||||||
color="primary"
|
|
||||||
:loading="loadingMore"
|
|
||||||
@click="loadBefore"
|
|
||||||
>
|
|
||||||
Load more
|
|
||||||
</VBtn>
|
|
||||||
</VCol>
|
|
||||||
</VCol>
|
|
||||||
</VRow>
|
|
||||||
</template>
|
|
|
@ -1,8 +1,8 @@
|
||||||
import url from "node:url";
|
import url from "node:url";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { defineEventHandler, setResponseStatus, readBody } from "h3";
|
import { defineEventHandler, setResponseStatus, readBody } from "h3";
|
||||||
// eslint-disable-next-line import/default
|
// @ts-expect-error
|
||||||
import PrismaMigrate from "@prisma/migrate";
|
import { DbPush } from "@prisma/migrate";
|
||||||
|
|
||||||
import { database } from "../utils/database";
|
import { database } from "../utils/database";
|
||||||
import { isFirstRun } from "./firstRun.get";
|
import { isFirstRun } from "./firstRun.get";
|
||||||
|
@ -39,7 +39,7 @@ export default defineEventHandler(async (e) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await (new PrismaMigrate.DbPush()).parse(dbPushParam);
|
await DbPush.new().parse(dbPushParam);
|
||||||
await database.user.create({
|
await database.user.create({
|
||||||
data: {
|
data: {
|
||||||
id: new Snowflake().state,
|
id: new Snowflake().state,
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { defineEventHandler } from "h3";
|
|
||||||
|
|
||||||
import { database } from "~/server/utils/database";
|
|
||||||
|
|
||||||
export default defineEventHandler(async (e) => {
|
|
||||||
return {
|
|
||||||
count: await database.order.count({}),
|
|
||||||
};
|
|
||||||
});
|
|
|
@ -4,12 +4,12 @@ let
|
||||||
# Updating this package will force an update for nodePackages.prisma. The
|
# Updating this package will force an update for nodePackages.prisma. The
|
||||||
# version of prisma-engines and nodePackages.prisma must be the same for them to
|
# version of prisma-engines and nodePackages.prisma must be the same for them to
|
||||||
# function correctly.
|
# function correctly.
|
||||||
prisma-version = "5.7.0";
|
prisma-version = "5.5.2";
|
||||||
prisma-src = pkgs.fetchFromGitHub {
|
prisma-src = pkgs.fetchFromGitHub {
|
||||||
owner = "prisma";
|
owner = "prisma";
|
||||||
repo = "prisma-engines";
|
repo = "prisma-engines";
|
||||||
rev = prisma-version;
|
rev = prisma-version;
|
||||||
hash = "sha256-gZEz0UtgNwumsZbweAyx3TOVHJshpBigc9pzWN7Gb/A=";
|
hash = "sha256-d24b+Jobt5+vH7SGYOnDIR9DOtM0Y2XSfHZGkr7EidA=";
|
||||||
};
|
};
|
||||||
new-prisma-engines = pkgs.rustPlatform.buildRustPackage {
|
new-prisma-engines = pkgs.rustPlatform.buildRustPackage {
|
||||||
pname = "prisma-engines";
|
pname = "prisma-engines";
|
||||||
|
@ -30,7 +30,6 @@ let
|
||||||
cargoLock = {
|
cargoLock = {
|
||||||
lockFile = "${prisma-src}/Cargo.lock";
|
lockFile = "${prisma-src}/Cargo.lock";
|
||||||
outputHashes = {
|
outputHashes = {
|
||||||
"cuid-1.3.2" = "sha256-ZihFrLerEIOdbJggaBbByRbC1sZRvF4M0LN2albB7vA=";
|
|
||||||
"barrel-0.6.6-alpha.0" = "sha256-USh0lQ1z+3Spgc69bRFySUzhuY79qprLlEExTmYWFN8=";
|
"barrel-0.6.6-alpha.0" = "sha256-USh0lQ1z+3Spgc69bRFySUzhuY79qprLlEExTmYWFN8=";
|
||||||
"graphql-parser-0.3.0" = "sha256-0ZAsj2mW6fCLhwTETucjbu4rPNzfbNiHu2wVTBlTNe4=";
|
"graphql-parser-0.3.0" = "sha256-0ZAsj2mW6fCLhwTETucjbu4rPNzfbNiHu2wVTBlTNe4=";
|
||||||
"mysql_async-0.31.3" = "sha256-QIO9s0Upc0/1W7ux1RNJNGKqzO4gB4gMV3NoakAbxkQ=";
|
"mysql_async-0.31.3" = "sha256-QIO9s0Upc0/1W7ux1RNJNGKqzO4gB4gMV3NoakAbxkQ=";
|
||||||
|
|
Loading…
Reference in a new issue