completed user and group delete features

This commit is contained in:
Vitaliy Pavlov 2024-07-27 07:58:08 +07:00
parent 978a08af00
commit e2bb2122dd
21 changed files with 228 additions and 26 deletions

View File

@ -2,10 +2,12 @@
<v-app>
<v-main>
<router-view />
<confirm-modal />
</v-main>
</v-app>
</template>
<script lang="ts" setup>
//
//
</script>

View File

@ -61,4 +61,12 @@ export default {
return [undefined, e?.response?.data?.error ?? e.message];
}
},
delete: async (ID: number): Promise<[AxiosResponse?, string?]> => {
try {
const data = await $axios.delete(`v1/groups/${ID}`);
return [data, undefined];
} catch (e: any) {
return [undefined, e?.response?.data?.error ?? e.message];
}
},
};

View File

@ -63,4 +63,12 @@ export default {
return [undefined, e?.response?.data?.error ?? e.message];
}
},
delete: async (ID: number): Promise<[AxiosResponse?, string?]> => {
try {
const data = await $axios.delete(`v1/users/${ID}`);
return [data, undefined];
} catch (e: any) {
return [undefined, e?.response?.data?.error ?? e.message];
}
},
};

1
src/components.d.ts vendored
View File

@ -8,6 +8,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
AppFooter: typeof import('./components/AppFooter.vue')['default']
ConfirmModal: typeof import('./components/ConfirmModal.vue')['default']
ErrorPlate: typeof import('./components/ErrorPlate.vue')['default']
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
LanguageSwitcher: typeof import('./components/LanguageSwitcher.vue')['default']

View File

@ -0,0 +1,42 @@
<script setup lang="ts">
import { useConfirmationStore } from '@/stores/confirmation';
import { useLocale } from 'vuetify';
const { t } = useLocale();
const $confirmation = useConfirmationStore();
</script>
<template>
<v-dialog persistent :model-value="$confirmation.show" width="450">
<v-card>
<template v-slot:title>
<v-card-title class="p-0 w-full flex items-center">
<span class="mso mr-1 text-2xl">warning</span>
<span>{{ $confirmation.title }}</span>
</v-card-title>
</template>
<v-card-text>{{ $confirmation.message }}</v-card-text>
<!-- Actions -->
<v-card-actions class="flex justify-end p-5 gap-1">
<!-- Cancel -->
<v-btn
variant="text"
class="px-4"
:text="t('$vuetify.actions.cancel')"
@click="$confirmation.doDecline()"
></v-btn>
<!-- Confirm -->
<v-btn
variant="flat"
color="primary"
class="px-4"
:text="t('$vuetify.actions.confirm')"
@click="$confirmation.doConfirm()"
></v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>

View File

@ -7,6 +7,7 @@
"edit": "Edit",
"block": "Block",
"unblock": "Unblock",
"apply": "Apply",
"confirm": "Confirm",
"cancel": "Cancel"
}

View File

@ -21,6 +21,12 @@
"name": "Name of group",
"permissions": "Permissions of users in group"
}
},
"delete": {
"delete-one": {
"title": "Delete group",
"message": "Are you sure you want to delete group with ID {0}? This action cannot be undone"
}
}
}
}

View File

@ -35,6 +35,12 @@
"group": "Group",
"no-group": "Not in group"
}
},
"delete": {
"delete-one": {
"title": "Delete user",
"message": "Are you sure you want to delete user with ID {0}? This action cannot be undone"
}
}
}
}

View File

@ -7,7 +7,8 @@
"edit": "Редактировать",
"block": "Заблокировать",
"unblock": "Разблокировать",
"confirm": "Применить",
"apply": "Применить",
"confirm": "Подтвердить",
"cancel": "Отменить"
}
}

View File

@ -21,6 +21,12 @@
"name": "Название группы",
"permissions": "Права пользователей в группе"
}
},
"delete": {
"delete-one": {
"title": "Удаление группы",
"message": "Вы уверены, что хотите удалить группу с ID {0}? Данное действие нельзя будет отменить"
}
}
}
}

View File

@ -35,6 +35,12 @@
"group": "Группа пользователя",
"no-group": "Без группы"
}
},
"delete": {
"delete-one": {
"title": "Удаление пользователя",
"message": "Вы уверены, что хотите удалить пользователя с ID {0}? Данное действие нельзя будет отменить"
}
}
}
}

View File

@ -120,7 +120,7 @@ const onConfirm = () => {
variant="flat"
color="primary"
class="px-4"
:text="t('$vuetify.actions.confirm')"
:text="t('$vuetify.actions.apply')"
:loading="$groups.loadingCreate"
:disabled="!validForm"
:class="{

View File

@ -125,7 +125,7 @@ const onConfirm = () => {
variant="flat"
color="primary"
class="px-4"
:text="t('$vuetify.actions.confirm')"
:text="t('$vuetify.actions.apply')"
:loading="$groups.loadingCreate"
:disabled="!validForm"
:class="{

View File

@ -6,6 +6,7 @@ import { useGroupsStore } from '@/stores/groups';
import { isDeleted, isModifiedDate } from '@/mixins/item-validation';
import { Group } from '@/types/group';
import Moment from '@/plugins/moment';
import { useConfirmationStore } from '@/stores/confirmation';
defineProps<{
tableHeight: number;
@ -14,6 +15,7 @@ defineProps<{
const { t } = useLocale();
const $groups = useGroupsStore();
const $confirmation = useConfirmationStore();
const headers: ComputedRef<any[]> = computed(() => [
{ title: t('$vuetify.groups.heads.id'), key: 'ID' },
@ -35,6 +37,14 @@ const editGroup = (g: Group) => {
$groups.openModal();
$groups.startEditGroup(g);
};
const deleteGroup = (g: Group) => {
$confirmation.openModal();
$confirmation.setTitle(t('$vuetify.groups.delete.delete-one.title'));
$confirmation.setMessage(
t('$vuetify.groups.delete.delete-one.message', [g.ID]),
);
$confirmation.setOnConfirm(() => $groups.deleteGroup(g.ID));
};
// Status
const calculateStatus = (group: Group) => {
@ -152,6 +162,8 @@ watch(
variant="text"
density="comfortable"
:disabled="isDeleted(item.deletedAt)"
:loading="$groups.loadingDeleteID === item.ID"
@click="deleteGroup(item)"
>
<span class="mso text-xl">close</span>
</v-btn>

View File

@ -1,12 +1,5 @@
<script setup lang="ts">
import {
computed,
ref,
watch,
onBeforeMount,
type Ref,
type ComputedRef,
} from 'vue';
import { computed, watch, onBeforeMount, type ComputedRef } from 'vue';
import { useLocale } from 'vuetify';
import { ITEMS_PER_PAGE } from '@/constants/static';
import { useUsersStore } from '@/stores/users';
@ -14,6 +7,7 @@ import { UserActions } from '@/enums/user-actions.enum';
import { User } from '@/types/user';
import Moment from '@/plugins/moment';
import { isDeleted, isModifiedDate } from '@/mixins/item-validation';
import { useConfirmationStore } from '@/stores/confirmation';
defineProps<{
tableHeight: number;
@ -22,6 +16,7 @@ defineProps<{
const { t } = useLocale();
const $users = useUsersStore();
const $confirmation = useConfirmationStore();
const headers: ComputedRef<any[]> = computed(() => [
{ title: t('$vuetify.users.heads.id'), key: 'ID' },
@ -68,6 +63,14 @@ const editUser = (u: User) => {
$users.openModal();
$users.startEditUser(u);
};
const deleteUser = (u: User) => {
$confirmation.openModal();
$confirmation.setTitle(t('$vuetify.users.delete.delete-one.title'));
$confirmation.setMessage(
t('$vuetify.users.delete.delete-one.message', [u.ID]),
);
$confirmation.setOnConfirm(() => $users.deleteUser(u.ID));
};
// Status
const calculateUserStatus = (user: User) => {
@ -230,6 +233,8 @@ watch(
variant="text"
density="comfortable"
:disabled="isDeleted(item.deletedAt)"
:loading="$users.loadingDeleteID === item.ID"
@click="deleteUser(item)"
>
<span class="mso text-xl">close</span>
</v-btn>

View File

@ -1,8 +0,0 @@
// Utilities
import { defineStore } from 'pinia'
export const useAppStore = defineStore('app', {
state: () => ({
//
}),
})

View File

@ -0,0 +1,48 @@
import { defineStore } from 'pinia';
export const useConfirmationStore = defineStore('confirmation', () => {
const show: Ref<boolean> = ref(false);
const title: Ref<string> = ref('');
const message: Ref<string> = ref('');
let onConfirm: Function = () => {};
let onDecline: Function = () => {};
const setOnConfirm = (f: Function) => (onConfirm = f);
const setOnDecline = (f: Function) => (onDecline = f);
const openModal = () => (show.value = true);
const closeModal = () => (show.value = false);
const setTitle = (t: string) => (title.value = t);
const setMessage = (m: string) => (message.value = m);
const setDefaults = () => {
show.value = false;
setTimeout(() => {
title.value = '';
message.value = '';
onConfirm = () => {};
onDecline = () => {};
}, 250);
};
const doConfirm = () => {
onConfirm();
setDefaults();
};
const doDecline = () => {
onDecline();
setDefaults();
};
return {
show,
title,
message,
setOnConfirm,
setOnDecline,
openModal,
closeModal,
setTitle,
setMessage,
doConfirm,
doDecline,
};
});

View File

@ -0,0 +1,26 @@
import api from '@/api';
import { getAll } from './search';
//
// MARK: Delete group
//
const loadingDeleteID: Ref<number> = ref(0);
const deleteGroup = async (ID: number) => {
if (loadingDeleteID.value || !ID) {
return;
}
setLoadingDelete(ID);
const [_, e] = await api.groups.delete(ID);
if (e && typeof e === 'string') {
// TODO notify error
} else {
getAll();
}
setLoadingDelete(0);
};
const setLoadingDelete = (ID: number) => (loadingDeleteID.value = ID);
export { loadingDeleteID, deleteGroup };

View File

@ -1,13 +1,16 @@
import { defineStore } from 'pinia';
import * as s from './search';
import * as a from './actions';
import * as m from './modal';
import * as s from './search';
export const useGroupsStore = defineStore('groups', () => {
const search = { ...s };
const actions = { ...a };
const modal = { ...m };
const search = { ...s };
return {
...search,
...actions,
...modal,
...search,
};
});

View File

@ -0,0 +1,26 @@
import api from '@/api';
import { getAll } from './search';
//
// MARK: Delete user
//
const loadingDeleteID: Ref<number> = ref(0);
const deleteUser = async (ID: number) => {
if (loadingDeleteID.value || !ID) {
return;
}
setLoadingDelete(ID);
const [_, e] = await api.users.delete(ID);
if (e && typeof e === 'string') {
// TODO notify error
} else {
getAll();
}
setLoadingDelete(0);
};
const setLoadingDelete = (ID: number) => (loadingDeleteID.value = ID);
export { loadingDeleteID, deleteUser };

View File

@ -1,13 +1,16 @@
import { defineStore } from 'pinia';
import * as s from './search';
import * as a from './actions';
import * as m from './modal';
import * as s from './search';
export const useUsersStore = defineStore('users', () => {
const search = { ...s };
const actions = { ...a };
const modal = { ...m };
const search = { ...s };
return {
...search,
...actions,
...modal,
...search,
};
});