updated the frontend and added mockdata

This commit is contained in:
Mikail Killi 2024-10-08 13:20:42 +02:00
parent 2e32e24f4a
commit 6e4df6ba07
6 changed files with 107 additions and 542 deletions

View file

@ -1,12 +1,12 @@
<template>
<v-app>
<v-main>
<div v-if="store.token === null">
<!-- <div v-if="store.token === null">
<loginPage />
</div>
<div v-else>
<div v-else> -->
<MainPage />
</div>
<!--</div>-->
</v-main>
</v-app>
</template>

View file

@ -1,97 +1,18 @@
<template>
<div class="mt-3">
<v-row no-gutters>
<v-col cols="11">
<h2>{{ licenseGroup.name }}:</h2>
</v-col>
<v-col>
<v-btn
block
class="ml-7"
flat
size="small"
color="isHovering ? 'red' : undefined"
variant="text"
v-model="deleteDialog"
@click="deleteDialog = true"
>
<Trash />
</v-btn>
<v-dialog v-model="deleteDialog" width="600" persistent>
<v-card max-width="600">
<v-card-title>
<span class="headline">Delete group</span>
</v-card-title>
<v-card-subtitle>
<span>Delete a group inside the database</span>
</v-card-subtitle>
<v-card-text>
<h4>This action is irreversible!</h4>
</v-card-text>
<v-card-actions>
<v-row>
<v-col cols="8" align="right" no-gutters>
<v-btn
class="ms-auto"
text="Cancel"
color="blue darken-1"
@click="deleteDialog = false"
></v-btn>
</v-col>
<v-col>
<v-btn
class="ms-auto"
text="Confirm Delete"
type="submit"
color="red darken-1"
@click="deleteMutate"
></v-btn>
</v-col>
</v-row>
</v-card-actions>
</v-card>
</v-dialog>
</v-col>
</v-row>
<li v-for="license in licenseGroup.licenses" :key="license.id">
<ListViewElement :license="license" />
</li>
<div>
<h3>{{ props.nodeGroup.name }}</h3>
<ul>
<li v-for="node in props.nodeGroup.nodes" :key="node.uuid">
<ListViewElement :node="node" />
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { NodeGroup } from "@/types";
import ListViewElement from "./ListViewElement.vue";
import { LicenseGroup } from "@/types";
import { Trash } from "lucide-vue-next";
import { ref } from "vue";
import { useMutation, useQueryClient } from "@tanstack/vue-query";
import { axiosInstance } from "@/client";
const { licenseGroup } = defineProps<{ licenseGroup: LicenseGroup }>();
const props = defineProps<{ nodeGroup: NodeGroup }>();
const deleteDialog = ref(false);
const queryClient = useQueryClient();
const id = licenseGroup.id;
const { mutate: deleteMutate } = useMutation({
mutationFn: async () => {
await axiosInstance.delete(`/groups/${id}`);
},
onError: (error) => {
console.log(error);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["licenses"] });
deleteDialog.value = false;
},
});
</script>
<style scoped>
li {
list-style-type: none;
}
</style>
</script>

View file

@ -9,175 +9,6 @@
<template v-if="!user" />
<UsersDialog v-else-if="user.admin" />
<UsersEditActions v-else :admin-menu="false" :user="user!" />
<!-- -->
<!-- GROUP SECTION -->
<!-- -->
<v-btn icon class="mr-3" @click="group = true">
<FolderPlus />
</v-btn>
<v-dialog v-model="group" width="600" persistent>
<v-card max-width="600">
<v-form @submit.prevent="submitGroup">
<v-card-title>
<span class="headline">Add Group</span>
</v-card-title>
<v-card-subtitle>
<span> Add a Group to the Database</span>
</v-card-subtitle>
<v-card-text>
<div>
<v-text-field
label="Group Name *"
v-model="groupGroupName"
variant="outlined"
required
clearable
:rules="licenseGroupRules"
class="mb-3"
></v-text-field>
</div>
</v-card-text>
<v-card-actions>
<v-row>
<v-col cols="10" align="right" no-gutters>
<v-btn
class="ms-auto"
text="Cancel"
@click="group = false"
color="blue darken-1"
></v-btn>
</v-col>
<v-col>
<v-btn
class="ms-auto"
text="Add"
type="submit"
color="blue darken-1"
></v-btn>
</v-col>
</v-row>
</v-card-actions>
</v-form>
</v-card>
</v-dialog>
<!-- -->
<!-- ADD SECTION -->
<!-- -->
<v-btn icon class="mr-3" @click="add = true">
<Plus />
</v-btn>
<v-dialog v-model="add" width="600" persistent>
<v-card max-width="600">
<v-form @submit.prevent="submit">
<v-card-title>
<span class="headline">Add License</span>
</v-card-title>
<v-card-subtitle>
<span> Add an extra License to the database</span>
</v-card-subtitle>
<v-card-text>
<div>
<v-text-field
label="License Name *"
v-model="addLicenseName"
variant="outlined"
required
clearable
:rules="licenseNameRules"
class="mb-3"
></v-text-field>
<v-text-field
label="License Key *"
v-model="addLicenseKey"
variant="outlined"
required
clearable
:rules="licenseKeyRules"
class="mb-3"
></v-text-field>
<v-autocomplete
clearable
label="Select Group *"
:items="data"
variant="outlined"
:rules="selectGroupRules"
class="mb-1"
v-model="addLicenseGroup"
></v-autocomplete>
<!-- Divider maybe remove -->
<v-divider
class="border-opacity-50"
:thickness="2"
></v-divider>
<div>
<div class="mb-3 mt-3">
<v-date-input
label="Start Date (optional)"
variant="outlined"
prepend-icon=""
prepend-inner-icon="mdi-calendar-check"
clearable
v-model="addStartDate"
hide-details
@click:clear="addStartDate = undefined"
></v-date-input>
</div>
<div class="mb-6">
<v-date-input
label="Stop Date (optional)"
variant="outlined"
prepend-icon=""
prepend-inner-icon="mdi-calendar-remove"
clearable
v-model="addStopDate"
hide-details
@click:clear="addStopDate = undefined"
></v-date-input>
</div>
</div>
<v-number-input
label="Amount (optional)"
users
variant="outlined"
clearable
:min="0"
v-model="addAmount"
>
</v-number-input>
<v-text-field
label="Notes (optional)"
v-model="addNotes"
variant="outlined"
clearable
></v-text-field>
<span class="dialogNote">
all fields marked with * are required
</span>
</div>
</v-card-text>
<v-card-actions>
<v-row>
<v-col cols="10" align="right" no-gutters>
<v-btn
class="ms-auto"
text="Cancel"
color="blue darken-1"
@click="add = false"
></v-btn>
</v-col>
<v-col>
<v-btn
class="ms-auto"
text="Add"
type="submit"
color="blue darken-1"
></v-btn>
</v-col>
</v-row>
</v-card-actions>
</v-form>
</v-card>
</v-dialog>
</div>
<!-- Search -->
<v-text-field
@ -206,34 +37,15 @@
<script setup lang="ts">
import { store } from "@/store";
import { SubmitEventPromise } from "vuetify";
import { ref } from "vue";
import { Plus, LogOut, FolderPlus } from "lucide-vue-next";
import { useQuery, useMutation, useQueryClient } from "@tanstack/vue-query";
import { LogOut } from "lucide-vue-next";
import { useQuery } from "@tanstack/vue-query";
import UsersDialog from "./NavBarIcons/UsersDialog.vue";
import UsersEditActions from "./NavBarIcons/UsersEditActions.vue";
import { axiosInstance } from "@/client";
import { LicenseGroup, CreateLicenseDto, CreateGroupDto, User } from "@/types";
import { User } from "@/types";
import { search } from "@/store";
const queryClient = useQueryClient();
const { data } = useQuery({
queryKey: ["licenses"],
queryFn: async () => {
const res = await axiosInstance.get<LicenseGroup[]>("/licenses");
return res.data;
},
select: (data) => {
return data.map((group) => {
return {
title: group.name,
value: group.id,
};
});
},
});
const { data: user } = useQuery({
queryKey: ["user"],
queryFn: async () => {
@ -242,124 +54,10 @@ const { data: user } = useQuery({
},
});
const { mutate: licenseMutate } = useMutation({
mutationFn: async (newLicense: CreateLicenseDto) => {
await axiosInstance.post("/licenses", newLicense);
},
onError: (error) => {
snackbar.value = true;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["licenses"] });
add.value = false;
},
});
const { mutate: groupsMutate } = useMutation({
mutationFn: async (newGroup: CreateGroupDto) => {
await axiosInstance.post("/groups", newGroup);
},
onError: (error) => {
snackbar.value = true;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["licenses"] });
group.value = false;
},
});
function handlelogout() {
store.setToken(null);
}
async function submit(event: SubmitEventPromise) {
const result = await event;
if (result.valid && addLicenseGroup.value) {
const data = {
name: addLicenseName.value,
group_id: addLicenseGroup.value,
amount: addAmount.value,
key: addLicenseKey.value,
start: addStartDate.value ?? null,
end: addStopDate.value ?? null,
note: addNotes.value,
};
console.log(data);
licenseMutate(data);
} else {
console.log("Invalid");
}
}
async function submitGroup(event: SubmitEventPromise) {
const result = await event;
if (result.valid) {
const groupsData = {
name: groupGroupName.value,
};
console.log(groupsData);
groupsMutate(groupsData);
} else {
console.log("Invalid");
}
}
const snackbar = ref(false);
//
// GROUP SECTION
//
const group = ref(false); // Dialog for Group
const groupGroupName = ref<string>("");
// Rules for Group Dialog
const licenseGroupRules = [
(value: string) => {
if (value) return true;
return "YOU MUST ENTER (A GROUP NAME)";
},
];
//
// ADD SECTION
//
const add = ref(false);
// References for Add License Dialog
const addLicenseName = ref<string>("");
const addLicenseKey = ref<string>("");
const addLicenseGroup = ref<string>();
// Rules for Add License Dialog
const licenseNameRules = [
(value: string) => {
if (value) return true;
return "YOU MUST ENTER (A LICENSE NAME)";
},
];
const selectGroupRules = [
(value: string) => {
if (value) return true;
return "YOU MUST SELECT (A LICENSE GROUP)";
},
];
const licenseKeyRules = [
(value: string) => {
if (value) return true;
return "YOU MUST ENTER (A LICENSE KEY)";
},
];
// Optional Fields
const addAmount = ref<number | undefined>();
const addNotes = ref<string | undefined>("");
// Date Picker
const addStartDate = ref<Date | undefined>();
const addStopDate = ref<Date | undefined>();
</script>
<style scoped>

View file

@ -4,87 +4,35 @@
<v-expansion-panels>
<v-expansion-panel>
<v-expansion-panel-title>
<div class="mr-3">
{{ license.name }}
</div>
<div
class="mr-3 d-flex align-self-center"
style="align-items: center"
>
<KeyRound class="mr-1" />
{{ license.key }}
</div>
<div
class="mr-3 d-flex align-self-center"
style="align-items: center"
>
<CalendarCheck />
<span>
{{
license.start
? new Date(license.start).toLocaleDateString()
: "Indefinitely"
}}
</span>
</div>
<div
class="mr-3 d-flex align-self-center"
style="align-items: center"
>
<CalendarX />
<span>
{{
license.end
? new Date(license.end).toLocaleDateString()
: "Indefinitely"
}}
</span>
</div>
<div
class="mr-3 d-flex align-self-center"
style="align-items: center"
>
<img
src="../assets/doublekey.svg"
alt="logo"
class="logo mr-1"
width="24"
/>
<span
v-if="license.amount == null || license.amount == undefined"
>
<Infinity />
</span>
<span v-else>
{{ license.amount }}
</span>
<div class="d-flex justify-content-between align-items-center">
<div class="mr-3" v-if="node.status === 200">
Name: {{ node.name }} (Status: online)
</div>
<div class="mr-3" v-else>
Name: {{ node.name }} (Status: offline)
</div>
</div>
</v-expansion-panel-title>
<v-expansion-panel-text>
Notes:
<div class="flex-nowrap d-flex" no-gutters>
<div
class="flex-grow-1 overflow-x-auto border-e-md align-self-end"
cols="10"
>
{{ license.note }}
</div>
<div align="end">
<!-- -->
<!-- EDIT SECTION -->
<!-- -->
<UpdateDialog :license="license" />
<!-- -->
<!-- DELETE BTN -->
<!-- -->
<DeleteDialog :license="license" />
<v-expansion-panel-text>
<div class="d-flex flex-column">
<div class="d-flex align-items-center mb-2">
<span class="mr-2">Coordinates:</span>
<span>La: {{ node.coordla }}, Long: {{ node.coordlong }}</span>
</div>
</div>
<div class="d-flex align-items-center mb-2">
<span class="mr-2">Temperature:</span>
<span>{{ node.temperature }}°C</span>
</div>
<div class="d-flex align-items-center mb-2">
<span class="mr-2">Battery:</span>
<span>{{ node.battery }}%</span>
</div>
<div class="d-flex align-items-center mb-2">
<span class="mr-2">Runtime:</span>
<span>{{ node.runtime }} hours</span>
</div>
</div>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
@ -93,38 +41,21 @@
</template>
<script setup lang="ts">
import { License } from "@/types";
import { Infinity, KeyRound, CalendarCheck, CalendarX } from "lucide-vue-next";
import UpdateDialog from "./UpdateDialog.vue";
import DeleteDialog from "./DeleteDialog.vue";
import { Node } from "@/types";
import { ComputedRef, inject, ref, watch } from "vue";
import { key } from "@/store";
const { license } = defineProps<{
license: License;
/*id: string;
licenseName: string;
licenseKey: string;
startDate: Date | null;
endDate: Date | null;
amount?: number;
notes?: string;*/
}>();
const props = defineProps<{ node: Node }>();
const visible = ref(true);
const { searching, visibleIds } = inject(key) as {
searching: ComputedRef<boolean>;
visibleIds: ComputedRef<string[]>;
};
watch([searching, visibleIds], ([searching, visibleIds]) => {
if (!searching) {
visible.value = true;
} else {
visible.value = visibleIds.includes(license.id);
}
visible.value = searching ? visibleIds.includes(props.node.uuid) : true;
});
</script>
<style scoped>
@ -132,4 +63,9 @@ watch([searching, visibleIds], ([searching, visibleIds]) => {
display: flex;
align-items: center;
}
.v-text-field {
max-width: 200px;
width: 100%;
}
</style>

View file

@ -3,10 +3,9 @@
<HeaderBar />
<div class="ma-8">
<div v-if="isPending">Loading...</div>
<div v-else-if="isError">Error: {{ error?.message }}</div>
<ul v-else-if="data">
<li v-for="group in data" :key="group.id">
<CategoryContainer :licenseGroup="group" />
<li v-for="nodeGroup in data" :key="nodeGroup.groupId">
<CategoryContainer :nodeGroup="nodeGroup" />
</li>
</ul>
</div>
@ -18,44 +17,68 @@ import HeaderBar from "./HeaderBar.vue";
import CategoryContainer from "./CategoryContainer.vue";
import { useQuery } from "@tanstack/vue-query";
import { axiosInstance } from "@/client";
import { LicenseGroup, License } from "@/types";
import { NodeGroup } from "@/types";
import { search, key } from "@/store";
import MiniSearch from "minisearch";
import { computed, provide } from "vue";
const { isPending, isError, data, error } = useQuery({
queryKey: ["licenses"],
queryFn: async () => {
const res = await axiosInstance.get<LicenseGroup[]>("/licenses");
console.log(res.data);
return res.data;
const mockNodeGroups: NodeGroup[] = [
{
groupId: "group-1",
name: "Test Node Group 1",
nodes: [
{
uuid: "123e4567-e89b-12d3-a456-426614174000",
name: "Sensor Node A1",
status: 502,
coordla: 52.5200,
coordlong: 13.4050,
temperature: 22.5,
battery: 85,
runtime: 120,
},
{
uuid: "223e4567-e89b-12d3-a456-426614174001",
name: "Sensor Node A2",
status: 200,
coordla: 48.8566,
coordlong: 2.3522,
temperature: 20.0,
battery: 90,
runtime: 100,
},
]
},
refetchInterval: 60 * 1000,
];
const { isPending, isError, data, error } = useQuery({
queryKey: ["nodeGroups"],
queryFn: async () => {
//hier der API-Call für die Daten
return mockNodeGroups;
},
refetchInterval: 1 * 1,
});
const searchEngine = computed(() => {
let minisearch = new MiniSearch({
fields: ["name", "description", "id"],
const minisearch = new MiniSearch({
fields: ["name", "groupId"],
searchOptions: {
boost: { name: 2 },
prefix: true,
},
});
let licenses: License[] = [];
data.value?.forEach((group) => {
group.licenses.forEach((license) => licenses.push(license));
});
console.log(licenses);
minisearch.addAll(licenses);
if (data.value) {
minisearch.addAll(data.value);
}
return minisearch;
});
const searching = computed(() => search.value !== "");
const visibleIds = computed(() => {
return searchEngine.value.search(search.value).map((searchResult) => {
return searchResult.id;
});
return searching.value ? searchEngine.value.search(search.value).map(result => result.groupId) : [];
});
provide(key, {

View file

@ -1,32 +1,22 @@
export interface LicenseGroup {
id: string;
export interface Node {
uuid: string;
name: string;
licenses: License[];
status: number;
coordla: number;
coordlong: number;
temperature: number;
battery: number;
runtime: number;
}
export interface License {
export interface NodeGroup {
groupId: string;
name: string;
id: string;
start?: string;
end?: string;
key: string;
amount?: number;
note?: string;
group_id: string;
}
export interface CreateLicenseDto {
name: string;
start: Date | null;
end: Date | null;
key: string;
amount?: number;
group_id: string;
note?: string;
nodes: Node[];
}
export interface User {
id: string;
uuid: string;
name: string;
email: string;
admin: boolean;
@ -46,6 +36,3 @@ export interface UpdateUserDto {
admin?: boolean;
}
export type CreateGroupDto = Omit<LicenseGroup, "id" | "licenses">;
export type UpdateLicenseDto = Omit<License, "id">;