web: add delete group button
Co-authored-by: Sphereso <Sphereso@users.noreply.github.com>
This commit is contained in:
parent
56ead89983
commit
0ce8e9765b
7 changed files with 244 additions and 99 deletions
|
@ -13,6 +13,7 @@
|
||||||
"@vueuse/core": "^10.11.0",
|
"@vueuse/core": "^10.11.0",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"lucide-vue-next": "^0.402.0",
|
"lucide-vue-next": "^0.402.0",
|
||||||
|
"minisearch": "^7.0.0",
|
||||||
"roboto-fontface": "*",
|
"roboto-fontface": "*",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vuetify": "^3.5.8"
|
"vuetify": "^3.5.8"
|
||||||
|
|
|
@ -23,6 +23,9 @@ importers:
|
||||||
lucide-vue-next:
|
lucide-vue-next:
|
||||||
specifier: ^0.402.0
|
specifier: ^0.402.0
|
||||||
version: 0.402.0(vue@3.4.31(typescript@5.5.3))
|
version: 0.402.0(vue@3.4.31(typescript@5.5.3))
|
||||||
|
minisearch:
|
||||||
|
specifier: ^7.0.0
|
||||||
|
version: 7.0.0
|
||||||
roboto-fontface:
|
roboto-fontface:
|
||||||
specifier: '*'
|
specifier: '*'
|
||||||
version: 0.10.0
|
version: 0.10.0
|
||||||
|
@ -594,6 +597,9 @@ packages:
|
||||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
||||||
|
minisearch@7.0.0:
|
||||||
|
resolution: {integrity: sha512-0OIJ3hUE+YBJNruDCqbTMFmk/IoB1CpZzuGfl11khFIel66ew9UoLF/+gfq3bdyrneqr3P7BTjFZApUbmk+9Dg==}
|
||||||
|
|
||||||
ms@2.1.2:
|
ms@2.1.2:
|
||||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||||
|
|
||||||
|
@ -1279,6 +1285,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 2.0.1
|
brace-expansion: 2.0.1
|
||||||
|
|
||||||
|
minisearch@7.0.0: {}
|
||||||
|
|
||||||
ms@2.1.2: {}
|
ms@2.1.2: {}
|
||||||
|
|
||||||
muggle-string@0.4.1: {}
|
muggle-string@0.4.1: {}
|
||||||
|
|
|
@ -1,7 +1,60 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<h2>{{ props.licenseGroupName }}:</h2>
|
<v-row no-gutters>
|
||||||
<li v-for="license in licenses" :key="license.id">
|
<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" />
|
<ListViewElement :license="license" />
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,9 +62,32 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ListViewElement from "./ListViewElement.vue";
|
import ListViewElement from "./ListViewElement.vue";
|
||||||
import { License } from "@/types";
|
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 props = defineProps<{ licenseGroupName: string; licenses: License[] }>();
|
const { licenseGroup } = defineProps<{ licenseGroup: LicenseGroup }>();
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -179,7 +179,6 @@
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Search -->
|
<!-- Search -->
|
||||||
<v-text-field
|
<v-text-field
|
||||||
class="compact-form mr-2"
|
class="compact-form mr-2"
|
||||||
|
@ -190,6 +189,7 @@
|
||||||
hide-details
|
hide-details
|
||||||
single-line
|
single-line
|
||||||
clearable
|
clearable
|
||||||
|
v-model="search"
|
||||||
rounded="pill"
|
rounded="pill"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
|
|
||||||
|
@ -214,6 +214,7 @@ import UsersDialog from "./NavBarIcons/UsersDialog.vue";
|
||||||
import UsersEditActions from "./NavBarIcons/UsersEditActions.vue";
|
import UsersEditActions from "./NavBarIcons/UsersEditActions.vue";
|
||||||
import { axiosInstance } from "@/client";
|
import { axiosInstance } from "@/client";
|
||||||
import { LicenseGroup, CreateLicenseDto, CreateGroupDto, User } from "@/types";
|
import { LicenseGroup, CreateLicenseDto, CreateGroupDto, User } from "@/types";
|
||||||
|
import { search } from "@/store";
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
|
|
@ -1,91 +1,95 @@
|
||||||
<template>
|
<template>
|
||||||
<v-sheet elevation="4" width="96vw" rounded="lg" class="mt-3">
|
<template v-if="visible">
|
||||||
<v-expansion-panels>
|
<v-sheet elevation="4" width="96vw" rounded="lg" class="mt-3">
|
||||||
<v-expansion-panel>
|
<v-expansion-panels>
|
||||||
<v-expansion-panel-title>
|
<v-expansion-panel>
|
||||||
<div class="mr-3">
|
<v-expansion-panel-title>
|
||||||
{{ license.name }}
|
<div class="mr-3">
|
||||||
</div>
|
{{ 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>
|
|
||||||
</v-expansion-panel-title>
|
|
||||||
<v-expansion-panel-text>
|
|
||||||
Notes:
|
|
||||||
<div class="flex-nowrap d-flex" no-gutters>
|
|
||||||
<div
|
<div
|
||||||
class="flex-grow-1 overflow-x-auto border-e-md align-self-end"
|
class="mr-3 d-flex align-self-center"
|
||||||
cols="10"
|
style="align-items: center"
|
||||||
>
|
>
|
||||||
{{ license.note }}
|
<KeyRound class="mr-1" />
|
||||||
|
{{ license.key }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align="end">
|
<div
|
||||||
<!-- -->
|
class="mr-3 d-flex align-self-center"
|
||||||
<!-- EDIT SECTION -->
|
style="align-items: center"
|
||||||
<!-- -->
|
>
|
||||||
<UpdateDialog :license="license" />
|
<CalendarCheck />
|
||||||
<!-- -->
|
<span>
|
||||||
<!-- DELETE BTN -->
|
{{
|
||||||
<!-- -->
|
license.start
|
||||||
<DeleteDialog :license="license" />
|
? new Date(license.start).toLocaleDateString()
|
||||||
|
: "Indefinitely"
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</v-expansion-panel-text>
|
<div
|
||||||
</v-expansion-panel>
|
class="mr-3 d-flex align-self-center"
|
||||||
</v-expansion-panels>
|
style="align-items: center"
|
||||||
</v-sheet>
|
>
|
||||||
|
<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>
|
||||||
|
</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" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-expansion-panel-text>
|
||||||
|
</v-expansion-panel>
|
||||||
|
</v-expansion-panels>
|
||||||
|
</v-sheet>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -93,6 +97,8 @@ import { License } from "@/types";
|
||||||
import { Infinity, KeyRound, CalendarCheck, CalendarX } from "lucide-vue-next";
|
import { Infinity, KeyRound, CalendarCheck, CalendarX } from "lucide-vue-next";
|
||||||
import UpdateDialog from "./UpdateDialog.vue";
|
import UpdateDialog from "./UpdateDialog.vue";
|
||||||
import DeleteDialog from "./DeleteDialog.vue";
|
import DeleteDialog from "./DeleteDialog.vue";
|
||||||
|
import { ComputedRef, inject, ref, watch } from "vue";
|
||||||
|
import { key } from "@/store";
|
||||||
|
|
||||||
const { license } = defineProps<{
|
const { license } = defineProps<{
|
||||||
license: License;
|
license: License;
|
||||||
|
@ -104,6 +110,21 @@ const { license } = defineProps<{
|
||||||
amount?: number;
|
amount?: number;
|
||||||
notes?: string;*/
|
notes?: string;*/
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -6,10 +6,7 @@
|
||||||
<div v-else-if="isError">Error: {{ error?.message }}</div>
|
<div v-else-if="isError">Error: {{ error?.message }}</div>
|
||||||
<ul v-else-if="data">
|
<ul v-else-if="data">
|
||||||
<li v-for="group in data" :key="group.id">
|
<li v-for="group in data" :key="group.id">
|
||||||
<CategoryContainer
|
<CategoryContainer :licenseGroup="group" />
|
||||||
:licenseGroupName="group.name"
|
|
||||||
:licenses="group.licenses"
|
|
||||||
/>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,7 +18,10 @@ import HeaderBar from "./HeaderBar.vue";
|
||||||
import CategoryContainer from "./CategoryContainer.vue";
|
import CategoryContainer from "./CategoryContainer.vue";
|
||||||
import { useQuery } from "@tanstack/vue-query";
|
import { useQuery } from "@tanstack/vue-query";
|
||||||
import { axiosInstance } from "@/client";
|
import { axiosInstance } from "@/client";
|
||||||
import { LicenseGroup } from "@/types";
|
import { LicenseGroup, License } from "@/types";
|
||||||
|
import { search, key } from "@/store";
|
||||||
|
import MiniSearch from "minisearch";
|
||||||
|
import { computed, provide } from "vue";
|
||||||
|
|
||||||
const { isPending, isError, data, error } = useQuery({
|
const { isPending, isError, data, error } = useQuery({
|
||||||
queryKey: ["licenses"],
|
queryKey: ["licenses"],
|
||||||
|
@ -31,6 +31,36 @@ const { isPending, isError, data, error } = useQuery({
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const searchEngine = computed(() => {
|
||||||
|
let minisearch = new MiniSearch({
|
||||||
|
fields: ["name", "description", "id"],
|
||||||
|
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);
|
||||||
|
return minisearch;
|
||||||
|
});
|
||||||
|
|
||||||
|
const searching = computed(() => search.value !== "");
|
||||||
|
|
||||||
|
const visibleIds = computed(() => {
|
||||||
|
return searchEngine.value.search(search.value).map((searchResult) => {
|
||||||
|
return searchResult.id;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
provide(key, {
|
||||||
|
visibleIds,
|
||||||
|
searching,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
import { reactive } from 'vue';
|
import { ComputedRef, InjectionKey, reactive, ref } from "vue";
|
||||||
|
|
||||||
export const store = reactive<{token: string | null, setToken: (token: string | null) => void}>({
|
export const store = reactive<{
|
||||||
token: localStorage.getItem('token') || null,
|
token: string | null;
|
||||||
|
setToken: (token: string | null) => void;
|
||||||
|
}>({
|
||||||
|
token: localStorage.getItem("token") || null,
|
||||||
setToken: (token: string | null) => {
|
setToken: (token: string | null) => {
|
||||||
store.token = token
|
store.token = token;
|
||||||
if (token) {
|
if (token) {
|
||||||
localStorage.setItem('token', token)
|
localStorage.setItem("token", token);
|
||||||
} else{
|
} else {
|
||||||
localStorage.removeItem('token')
|
localStorage.removeItem("token");
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const search = ref("");
|
||||||
|
|
||||||
|
export const key = Symbol() as InjectionKey<{
|
||||||
|
searching: ComputedRef<boolean>;
|
||||||
|
visibleIds: ComputedRef<string[]>;
|
||||||
|
}>;
|
||||||
|
|
Loading…
Reference in a new issue