Added user management functionality to the configuration page:
- Introduced `Users` tab in `Config.vue` with a dynamic display controlled by user rights. - Added `EditUser.vue` and `Users.vue` components for managing user details and permissions via modal dialogs. - Implemented new `users` store handling user records, pagination, and updates. - Updated `TableView.vue` to support badge rendering and dynamic configurations. - Adjusted routing guards for consistent user data fetching and permissions. - Various UI refinements and component reorganization for better maintainability.
This commit is contained in:
parent
83e007088c
commit
c57c2ff19d
11 changed files with 504 additions and 47 deletions
|
|
@ -1,20 +1,23 @@
|
|||
<template>
|
||||
<div class="app-group-item">
|
||||
<div class="group-item">
|
||||
<tooltip :text="groupObj.group_description">
|
||||
<checkbox :checked="selected" @checkbox-changed="checkboxChanged"></checkbox>
|
||||
<div>
|
||||
<div class="app-group-item-name">{{ groupObj.group_name }}</div>
|
||||
<div class="app-group-item-descr">{{ groupObj.group_description }}</div>
|
||||
<div class="group-item-name">{{ groupObj.group_name }}</div>
|
||||
<!-- <div class="app-group-item-descr">{{ groupObj.group_description }}</div>-->
|
||||
</div>
|
||||
</tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Checkbox from "@/components/UI/Checkbox.vue";
|
||||
import Tooltip from "@/components/UI/Tooltip.vue";
|
||||
|
||||
export default {
|
||||
name: "AppGroupItem",
|
||||
components: {Checkbox},
|
||||
name: "GroupItem",
|
||||
components: {Tooltip, Checkbox},
|
||||
props: {
|
||||
groupObj: {
|
||||
type: Object,
|
||||
|
|
@ -35,17 +38,17 @@ export default {
|
|||
|
||||
<style scoped>
|
||||
|
||||
.app-group-item {
|
||||
.group-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 0.8rem;
|
||||
padding: 1.6rem;
|
||||
|
||||
}
|
||||
|
||||
.app-group-item-name {
|
||||
font-weight: 500;
|
||||
.group-item-name {
|
||||
font-weight: 400;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="table-container">
|
||||
|
||||
<div class="table-search-bar-container">
|
||||
<div v-if="searchbar" class="table-search-bar-container">
|
||||
|
||||
<search-bar class="search-bar" v-model="filter" @input-changed="reload"></search-bar>
|
||||
</div>
|
||||
|
|
@ -37,8 +37,10 @@
|
|||
<td v-for="column in columns" :key="column.key" class="table-cell" :class="getAlignment(column.align)">
|
||||
|
||||
<flag v-if="column.showFlag" :tooltip-text="getCellValue(item, column)" :iso="getCellValue(item, column)"></flag>
|
||||
<div class="badge-container" v-else-if="column.badgeResolver != null && typeof column.badgeResolver === 'function'">
|
||||
<basic-badge v-for="badge in getCellValue(item, column)" :variant="badge.variant">{{ badge.text }}</basic-badge>
|
||||
</div>
|
||||
<span v-else-if="column.iconResolver == null">{{ getCellValue(item, column) }}</span>
|
||||
<span v-else-if="column.badgeResolver">{{ getCellValue(item, column) }}</span>
|
||||
<component v-else
|
||||
:is="getCellValue(item, column)"
|
||||
weight="regular"
|
||||
|
|
@ -69,10 +71,12 @@ import SearchBar from "@/components/UI/SearchBar.vue";
|
|||
import Box from "@/components/UI/Box.vue";
|
||||
import Pagination from "@/components/UI/Pagination.vue";
|
||||
import Flag from "@/components/UI/Flag.vue";
|
||||
import basicBadge from "@/components/UI/BasicBadge.vue";
|
||||
import BasicBadge from "@/components/UI/BasicBadge.vue";
|
||||
|
||||
export default {
|
||||
name: "TableView",
|
||||
components: {Flag, Pagination, Box, SearchBar, BasicButton, Checkbox, Spinner},
|
||||
components: {BasicBadge, Flag, Pagination, Box, SearchBar, BasicButton, Checkbox, Spinner},
|
||||
emits: ['row-click'],
|
||||
props: {
|
||||
dataSource: {
|
||||
|
|
@ -84,6 +88,10 @@ export default {
|
|||
default: false,
|
||||
|
||||
},
|
||||
searchbar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true,
|
||||
|
|
@ -111,7 +119,11 @@ export default {
|
|||
mounted() {
|
||||
this.reload();
|
||||
},
|
||||
computed: {},
|
||||
computed: {
|
||||
basicBadge() {
|
||||
return basicBadge
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async updatePage(page) {
|
||||
this.reload(this.filter, page);
|
||||
|
|
@ -145,6 +157,10 @@ export default {
|
|||
return column.iconResolver(rawValue, item);
|
||||
}
|
||||
|
||||
if (column.badgeResolver && typeof column.badgeResolver === 'function') {
|
||||
return column.badgeResolver(rawValue, item);
|
||||
}
|
||||
|
||||
if (column.iconResolver) {
|
||||
return 'PhImageBroken';
|
||||
}
|
||||
|
|
@ -178,6 +194,11 @@ export default {
|
|||
opacity: 0;
|
||||
}
|
||||
|
||||
.badge-container {
|
||||
display: flex;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.table-icon {
|
||||
transition: all 0.1s ease-in-out;
|
||||
color: #6B869C;
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@
|
|||
<div class="add-app">
|
||||
<div>App name</div>
|
||||
<div>
|
||||
<div class="text-container"><input class="input-field" v-model="appName" @input="checkChange"/></div>
|
||||
<div class="text-container"><input class="input-field" v-model="appName" /></div>
|
||||
</div>
|
||||
<div class="add-app-group-header">App groups</div>
|
||||
<div>
|
||||
<app-group-item v-for="group in groups" :group-obj="group" :key="group.group_name"
|
||||
<div class="groups-container">
|
||||
<group-item v-for="group in groups" :group-obj="group" :key="group.group_name"
|
||||
:selected="isSelected(group.group_name)"
|
||||
@checkbox-changed="updateSelected"></app-group-item>
|
||||
@checkbox-changed="updateSelected"></group-item>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
|
@ -49,14 +49,14 @@ import {mapStores} from "pinia";
|
|||
import {useGroupStore} from "@/store/group.js";
|
||||
import Checkbox from "@/components/UI/Checkbox.vue";
|
||||
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||
import AppGroupItem from "@/components/UI/AppGroupItem.vue";
|
||||
import GroupItem from "@/components/UI/GroupItem.vue";
|
||||
import {useAppsStore} from "@/store/apps.js";
|
||||
import checkbox from "@/components/UI/Checkbox.vue";
|
||||
import Box from "@/components/UI/Box.vue";
|
||||
|
||||
export default {
|
||||
name: "AddApp",
|
||||
components: {Box, AppGroupItem, BasicButton, Checkbox, InputField},
|
||||
components: {Box, GroupItem, BasicButton, Checkbox, InputField},
|
||||
computed: {
|
||||
checkbox() {
|
||||
return checkbox
|
||||
|
|
@ -117,10 +117,19 @@ export default {
|
|||
|
||||
<style scoped>
|
||||
|
||||
|
||||
|
||||
.groups-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
|
||||
gap: 1.8rem;
|
||||
}
|
||||
|
||||
.stage-container {
|
||||
position: relative;
|
||||
min-height: 20rem;
|
||||
margin: 1.6rem;
|
||||
min-width: 70rem;
|
||||
}
|
||||
|
||||
/* Transition animations */
|
||||
|
|
@ -144,8 +153,8 @@ export default {
|
|||
|
||||
.add-app {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-gap: 10px;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-gap: 1.6rem;
|
||||
font-weight: 500;
|
||||
font-size: 1.4rem;
|
||||
align-items: center;
|
||||
|
|
@ -153,7 +162,7 @@ export default {
|
|||
|
||||
.add-app-group-header {
|
||||
align-self: start;
|
||||
padding-top: 2.4rem;
|
||||
|
||||
}
|
||||
|
||||
.text-container {
|
||||
|
|
@ -24,7 +24,7 @@ import AppListItem from "@/components/UI/AppListItem.vue";
|
|||
import {mapStores} from "pinia";
|
||||
import {useAppsStore} from "@/store/apps.js";
|
||||
import Modal from "@/components/UI/Modal.vue";
|
||||
import AddApp from "@/components/UI/AddApp.vue";
|
||||
import AddApp from "@/components/layout/config/AddApp.vue";
|
||||
|
||||
export default {
|
||||
name: "Apps",
|
||||
|
|
|
|||
193
src/frontend/src/components/layout/config/EditUser.vue
Normal file
193
src/frontend/src/components/layout/config/EditUser.vue
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
<template>
|
||||
<div class="edit-user-container">
|
||||
<div class="edit-user">
|
||||
<div>Workday ID</div>
|
||||
<div>
|
||||
<div class="text-container" :class="{disabled: !isNewUser}"><input :disabled="!isNewUser" class="input-field"
|
||||
v-model="user.workday_id"/></div>
|
||||
</div>
|
||||
<div>Firstname</div>
|
||||
<div>
|
||||
<div class="text-container"><input class="input-field" v-model="user.firstname"/></div>
|
||||
</div>
|
||||
<div>Lastname</div>
|
||||
<div>
|
||||
<div class="text-container"><input class="input-field" v-model="user.lastname"/></div>
|
||||
</div>
|
||||
<div>E-Mail</div>
|
||||
<div>
|
||||
<div class="text-container"><input class="input-field" v-model="user.mail"/></div>
|
||||
</div>
|
||||
<div class="group-header">Groups</div>
|
||||
<div class="groups-container">
|
||||
<group-item v-for="group in groups" :group-obj="group" :key="group.group_name"
|
||||
:selected="isSelected(group.group_name)"
|
||||
@checkbox-changed="updateSelected"></group-item>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div class="add-app-footer">
|
||||
<basic-button :show-icon="false" @click="closeModal(true)" :disabled="disableButton"> {{
|
||||
applyText
|
||||
}}
|
||||
</basic-button>
|
||||
<basic-button :show-icon="false" @click="closeModal(false)" variant="secondary">Cancel</basic-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||
import GroupItem from "@/components/UI/GroupItem.vue";
|
||||
import {mapStores} from "pinia";
|
||||
import {useGroupStore} from "@/store/group.js";
|
||||
|
||||
export default {
|
||||
name: "EditUser",
|
||||
components: {GroupItem, BasicButton},
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isNewUser: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
groups: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeModal(save) {
|
||||
this.$emit("close", save);
|
||||
},
|
||||
isSelected(groupName) {
|
||||
if (groupName === null || this.user?.groups == null || Object.keys(this.groups).length === 0)
|
||||
return false;
|
||||
|
||||
return this.user.groups.includes(groupName);
|
||||
},
|
||||
updateSelected(checked, groupName) {
|
||||
|
||||
|
||||
const idx = this.user.groups?.indexOf(groupName);
|
||||
console.log("update selected", idx, checked, groupName, this.user.groups);
|
||||
|
||||
if (checked) {
|
||||
if ((idx ?? null) !== null && idx === -1)
|
||||
this.user.groups.push(groupName);
|
||||
|
||||
} else {
|
||||
if ((idx ?? null) !== null && idx !== -1)
|
||||
this.user.groups.splice(idx);
|
||||
}
|
||||
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useGroupStore),
|
||||
applyText() {
|
||||
return this.isNewUser ? "Create user" : "Update user";
|
||||
},
|
||||
mailValid() {
|
||||
return String(this.user.mail)
|
||||
.toLowerCase()
|
||||
.match(
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
);
|
||||
},
|
||||
disableButton() {
|
||||
return !this.mailValid ||
|
||||
!(this.user.groups.length !== 0
|
||||
&& this.user.firstname?.length !== 0
|
||||
&& this.user.lastname?.length !== 0
|
||||
&& this.user.mail?.length !== 0
|
||||
&& this.user.workday_id?.length !== 0);
|
||||
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.groupStore.loadGroups()
|
||||
this.groups = this.groupStore.groups
|
||||
this.groups?.forEach(group => {
|
||||
group.selected = false;
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
.text-container.disabled {
|
||||
background-color: #f3f4f6;
|
||||
cursor: not-allowed;
|
||||
border-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.text-container.disabled input {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
|
||||
.groups-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
|
||||
gap: 1.8rem;
|
||||
}
|
||||
|
||||
.edit-user-container {
|
||||
min-width: 100rem;
|
||||
}
|
||||
|
||||
.edit-user {
|
||||
padding: 1.6rem;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-gap: 1.6rem;
|
||||
font-weight: 500;
|
||||
font-size: 1.4rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.group-header {
|
||||
align-self: start;
|
||||
|
||||
}
|
||||
|
||||
.text-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
border-radius: 0.4rem;
|
||||
padding: 0.6rem 1.2rem;
|
||||
/* box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);*/
|
||||
border: 0.2rem solid #E3EDFF;
|
||||
transition: all 0.1s ease;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
|
||||
.input-field {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
resize: none;
|
||||
font-family: inherit;
|
||||
font-size: 1.4rem;
|
||||
color: #002F54;
|
||||
width: 100%;
|
||||
min-width: 5rem;
|
||||
}
|
||||
|
||||
.add-app-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1.6rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
148
src/frontend/src/components/layout/config/Users.vue
Normal file
148
src/frontend/src/components/layout/config/Users.vue
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="user-list">
|
||||
<table-view ref="tableViewRef" :searchbar="false" :columns="columns" :data-source="fetch" @row-click="selectUser"
|
||||
:mouse-over="true"></table-view>
|
||||
</div>
|
||||
<modal :state="showModal">
|
||||
<edit-user @close="closeModal" v-model:user="selectedUser" :is-new-user="isNewUser"></edit-user>
|
||||
</modal>
|
||||
<basic-button icon="Plus" @click="createUser">New User</basic-button>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {mapStores} from "pinia";
|
||||
import {useUsersStore} from "@/store/users.js";
|
||||
import TableView from "@/components/UI/TableView.vue";
|
||||
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||
import Modal from "@/components/UI/Modal.vue";
|
||||
import AddApp from "@/components/layout/config/AddApp.vue";
|
||||
import EditUser from "@/components/layout/config/EditUser.vue";
|
||||
import logger from "@/logger.js";
|
||||
|
||||
export default {
|
||||
name: "Users",
|
||||
props: {
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
components: {EditUser, AddApp, Modal, BasicButton, TableView},
|
||||
computed: {
|
||||
...mapStores(useUsersStore)
|
||||
},
|
||||
watch: {
|
||||
async isSelected(newVal) {
|
||||
if (newVal === true) {
|
||||
await this.usersStore.load();
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.usersStore.load();
|
||||
},
|
||||
methods: {
|
||||
async fetch(query) {
|
||||
await this.usersStore.setQuery(query);
|
||||
this.pagination = this.usersStore.pagination;
|
||||
return this.usersStore.users;
|
||||
},
|
||||
selectUser(user) {
|
||||
this.isNewUser = false;
|
||||
|
||||
const groups = [...user.groups];
|
||||
|
||||
this.selectedUser = {
|
||||
firstname: user.firstname,
|
||||
lastname: user.lastname,
|
||||
mail: user.mail,
|
||||
workday_id: user.workday_id,
|
||||
groups: groups
|
||||
}
|
||||
this.showModal = true;
|
||||
},
|
||||
createUser() {
|
||||
|
||||
this.isNewUser = true;
|
||||
|
||||
this.selectedUser = {
|
||||
firstname: null,
|
||||
lastname: null,
|
||||
mail: null,
|
||||
workday_id: null,
|
||||
groups: []
|
||||
}
|
||||
|
||||
this.showModal = true;
|
||||
},
|
||||
async closeModal(save) {
|
||||
this.showModal = false;
|
||||
if (save) {
|
||||
await this.usersStore.updateUser(this.selectedUser);
|
||||
await this.usersStore.load();
|
||||
this.selectedUser = null;
|
||||
this.$refs.tableViewRef.reload('', 1);
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isNewUser: false,
|
||||
showModal: false,
|
||||
selectedUser: null,
|
||||
users: null,
|
||||
columns: [
|
||||
{
|
||||
key: 'workday_id',
|
||||
label: 'Workday ID',
|
||||
},
|
||||
{
|
||||
key: 'firstname',
|
||||
label: 'First name',
|
||||
},
|
||||
{
|
||||
key: 'lastname',
|
||||
label: 'Last name',
|
||||
},
|
||||
{
|
||||
key: 'mail',
|
||||
label: 'E-Mail',
|
||||
},
|
||||
|
||||
{
|
||||
key: 'groups',
|
||||
label: 'User groups',
|
||||
badgeResolver: (value) => {
|
||||
|
||||
const formattedValues = []
|
||||
value.slice(0,5).forEach(v => formattedValues.push({text: v, variant: "secondary"}));
|
||||
|
||||
if(value.length > 5)
|
||||
formattedValues.push({text: "...", variant: "secondary"});
|
||||
|
||||
return formattedValues;
|
||||
}
|
||||
},
|
||||
],
|
||||
pagination: {
|
||||
page: 1,
|
||||
pageCount: 1,
|
||||
totalCount: 0
|
||||
},
|
||||
pageSize: 20
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.user-list {
|
||||
margin-bottom: 2.4rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -28,6 +28,7 @@ import ErrorLog from "@/pages/ErrorLog.vue";
|
|||
import {mapStores} from "pinia";
|
||||
import {useActiveUserStore} from "@/store/activeuser.js";
|
||||
import Apps from "@/components/layout/config/Apps.vue";
|
||||
import Users from "@/components/layout/config/Users.vue";
|
||||
|
||||
export default {
|
||||
name: "Config",
|
||||
|
|
@ -45,6 +46,11 @@ export default {
|
|||
component: markRaw(Apps),
|
||||
props: {isSelected: false},
|
||||
},
|
||||
usersTab: {
|
||||
title: 'Users',
|
||||
component: markRaw(Users),
|
||||
props: {isSelected: false},
|
||||
},
|
||||
systemLogTab: {
|
||||
title: 'System log',
|
||||
component: markRaw(ErrorLog),
|
||||
|
|
@ -86,6 +92,10 @@ export default {
|
|||
tabs.push(this.appsTab);
|
||||
}
|
||||
|
||||
if (this.activeUserStore.isRightManagement) {
|
||||
tabs.push(this.usersTab);
|
||||
}
|
||||
|
||||
if (this.activeUserStore.isSuper || this.activeUserStore.isMaterial) {
|
||||
tabs.push(this.materialsTab);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ const router = createRouter({
|
|||
name: 'edit',
|
||||
beforeEnter: async (to, from) => {
|
||||
const userStore = useActiveUserStore();
|
||||
await userStore.loadIfRequired();
|
||||
await userStore.load();
|
||||
|
||||
if (userStore.allowCalculation) {
|
||||
return true;
|
||||
|
|
@ -72,7 +72,7 @@ const router = createRouter({
|
|||
name: 'bulk',
|
||||
beforeEnter: async (to, from) => {
|
||||
const userStore = useActiveUserStore();
|
||||
await userStore.loadIfRequired();
|
||||
await userStore.load();
|
||||
|
||||
if (userStore.allowCalculation) {
|
||||
return true;
|
||||
|
|
@ -86,7 +86,7 @@ const router = createRouter({
|
|||
name: 'bulk-single-edit',
|
||||
beforeEnter: async (to, from) => {
|
||||
const userStore = useActiveUserStore();
|
||||
await userStore.loadIfRequired();
|
||||
await userStore.load();
|
||||
|
||||
if (userStore.allowCalculation) {
|
||||
return true;
|
||||
|
|
@ -99,7 +99,7 @@ const router = createRouter({
|
|||
component: Reporting,
|
||||
beforeEnter: async (to, from) => {
|
||||
const userStore = useActiveUserStore();
|
||||
await userStore.loadIfRequired();
|
||||
await userStore.load();
|
||||
|
||||
if (userStore.allowReporting) {
|
||||
return true;
|
||||
|
|
@ -112,7 +112,7 @@ const router = createRouter({
|
|||
component: Config,
|
||||
beforeEnter: async (to, from) => {
|
||||
const userStore = useActiveUserStore();
|
||||
await userStore.loadIfRequired();
|
||||
await userStore.load();
|
||||
|
||||
if (userStore.allowConfiguration) {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,11 @@ export const useActiveUserStore = defineStore('activeUser', {
|
|||
return false;
|
||||
return state.user.groups?.includes("service");
|
||||
},
|
||||
isRightManagement(state) {
|
||||
if (state.user === null)
|
||||
return false;
|
||||
return state.user.groups?.includes("right-management");
|
||||
},
|
||||
isPackaging(state) {
|
||||
if (state.user === null)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -41,16 +41,18 @@ export const useNotificationStore = defineStore('notification', {
|
|||
duration: notification.duration ?? 8000
|
||||
})
|
||||
},
|
||||
async addError(errorDto, options = {}) {
|
||||
async addError(errorDto, options = {}, silent = false) {
|
||||
const {request = null, store = null, global = false} = options;
|
||||
const state = this.captureStoreState(store, global);
|
||||
|
||||
this.addNotification({
|
||||
icon: 'bug',
|
||||
message: errorDto.message ?? 'Unknown error code',
|
||||
title: errorDto.title ?? 'Unknown error',
|
||||
variant: 'exception',
|
||||
});
|
||||
if (!silent) {
|
||||
this.addNotification({
|
||||
icon: 'bug',
|
||||
message: errorDto.message ?? 'Unknown error code',
|
||||
title: errorDto.title ?? 'Unknown error',
|
||||
variant: 'exception',
|
||||
});
|
||||
}
|
||||
|
||||
const error = {
|
||||
error: {
|
||||
|
|
@ -158,17 +160,18 @@ export function setupErrorBuffer() {
|
|||
const errorStore = useNotificationStore()
|
||||
|
||||
//Unhandled Promise Rejections
|
||||
// window.addEventListener('unhandledrejection', (event) => {
|
||||
//
|
||||
// const error = {
|
||||
// code: "Unhandled rejection",
|
||||
// title: "Frontend error",
|
||||
// message: event.reason?.message || 'Unhandled Promise Rejection',
|
||||
// traceCombined: event.reason?.stack,
|
||||
// };
|
||||
//
|
||||
// errorStore.addError(error, {global: true}).then(r => {} );
|
||||
// })
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
|
||||
const error = {
|
||||
code: "Unhandled rejection",
|
||||
title: "Frontend error",
|
||||
message: event.reason?.message || 'Unhandled Promise Rejection',
|
||||
traceCombined: event.reason?.stack,
|
||||
};
|
||||
|
||||
errorStore.addError(error, {global: true}, true).then(r => {
|
||||
});
|
||||
})
|
||||
|
||||
// // JavaScript Errors
|
||||
window.addEventListener('error', (event) => {
|
||||
|
|
@ -178,7 +181,8 @@ export function setupErrorBuffer() {
|
|||
message: event.reason?.message || 'Unhandled Promise Rejection',
|
||||
traceCombined: event.reason?.stack,
|
||||
};
|
||||
errorStore.addError(error, {global: true}).then(r => {} );
|
||||
errorStore.addError(error, {global: true}).then(r => {
|
||||
});
|
||||
})
|
||||
|
||||
window.addEventListener('beforeunload', async () => {
|
||||
|
|
|
|||
64
src/frontend/src/store/users.js
Normal file
64
src/frontend/src/store/users.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import {defineStore} from 'pinia'
|
||||
import {config} from '@/config'
|
||||
import {useNotificationStore} from "@/store/notification.js";
|
||||
import performRequest from "@/backend.js";
|
||||
import logger from "@/logger.js";
|
||||
|
||||
|
||||
export const useUsersStore = defineStore('users', {
|
||||
state: () => ({
|
||||
users: [],
|
||||
loading: false,
|
||||
pagination: {},
|
||||
query: {},
|
||||
}),
|
||||
getters: {
|
||||
getById: (state) => {
|
||||
return (id) => state.users.find(p => p.id === id)
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async setQuery(query) {
|
||||
this.query = query;
|
||||
await this.load();
|
||||
},
|
||||
async updateUser(user) {
|
||||
const body = {
|
||||
firstname: user.firstname,
|
||||
lastname: user.lastname,
|
||||
mail: user.mail,
|
||||
workday_id: user.workday_id,
|
||||
groups: user.groups
|
||||
}
|
||||
|
||||
await performRequest(this, "PUT", `${config.backendUrl}/users/`, body, false);
|
||||
},
|
||||
async load() {
|
||||
|
||||
this.loading = true;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (this.query?.page)
|
||||
params.append('page', this.query.page);
|
||||
|
||||
if (this.query?.pageSize)
|
||||
params.append('limit', this.query.pageSize);
|
||||
|
||||
const endpoint = '/users';
|
||||
const url = `${config.backendUrl}${endpoint}/${params.size === 0 ? '' : '?'}${params.toString()}`;
|
||||
const {data: data, headers: headers} = await performRequest(this, "GET", url, null, true);
|
||||
|
||||
this.pagination = {
|
||||
page: parseInt(headers.get('X-Current-Page')),
|
||||
pageCount: parseInt(headers.get('X-Page-Count')),
|
||||
totalCount: parseInt(headers.get('X-Total-Count'))
|
||||
};
|
||||
|
||||
this.loading = false;
|
||||
this.empty = data.length === 0;
|
||||
this.users = data;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue