Skip to content

Commit e65555c

Browse files
ipulawithanage
andauthored
pkp/pkp-lib#9658 user access table and table actions (#437)
* user access manager * Support user json object * Support user json object * Add Actions * add user search function * Rename variable * add user table actions * update after data changed * fix disabled user enable * hide actions that not allowed to do current user in the user list * add phone and biography * add user loginAs and mergeUser permission check * remove localized user group name * update user access mock and set margin between tables * improve flexibility on extensibility * remove phone and biography meta data * add separate columns components * change user action functions variable * add user get user permission for merge, loginAs user actions --------- Co-authored-by: withanage <[email protected]>
1 parent 96aae59 commit e65555c

13 files changed

+727
-0
lines changed

public/globals.js

+7
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ window.pkp = {
406406
'email.cc': 'CC',
407407
'email.confirmSwitchLocale':
408408
'Are you sure you want to change to {$localeName} to compose this email? Any changes you have made to the subject and body of the email will be lost.',
409+
'email.email': 'Email',
409410
'email.subject': 'Subject',
410411
'email.to': 'To',
411412
'fileManager.copyeditedFiles': 'Copyedited Files',
@@ -439,18 +440,22 @@ window.pkp = {
439440
'grid.action.deleteContributor': 'Delete Contributor',
440441
'grid.action.deleteContributor.confirmationMessage':
441442
'Are you sure you want to remove {$name} as a contributor? This action can not be undone.',
443+
'grid.action.disable': 'Disable User',
442444
'grid.action.edit': 'Edit',
443445
'grid.action.editFile': 'Edit a file',
444446
'grid.action.logInAs': 'Login As',
445447
'grid.action.moreInformation': 'More Information',
446448
'grid.action.order': 'Order',
449+
'grid.action.remove': 'Remove',
447450
'grid.action.saveOrdering': 'Save Order',
448451
'grid.action.sort': 'Sort',
449452
'grid.columns.actions': 'Actions',
450453
'grid.libraryFiles.submission.title': 'Submission Library',
451454
'grid.noItems': 'No Items',
452455
'grid.user.confirmLogInAs':
453456
'Log in as this user? All actions you perform will be attributed to this user.',
457+
'grid.user.currentUsers':'Current Users',
458+
'grid.action.mergeUser':'Merge User',
454459
'help.help': 'Help',
455460
'informationCenter.informationCenter': 'Information Center',
456461
'invitation.cancelInvite.actionName': 'Cancel Invite',
@@ -770,6 +775,8 @@ window.pkp = {
770775
'Are you sure want remove this role permanently?',
771776
'user.role.reviewer': 'Reviewer',
772777
'user.role.reviewers': 'Reviewers',
778+
'user.roles': 'Roles',
779+
'user.startDate': 'Start Date',
773780
'user.username': 'Username',
774781
'userInvitation.cancel.goBack': 'Go Back',
775782
'userInvitation.cancel.message':
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {Primary, Controls, Stories, Meta, ArgTypes} from '@storybook/blocks';
2+
3+
import * as UserAccessManager from './UserAccessManager.stories.js';
4+
5+
<Meta of={UserAccessManager} />
6+
7+
# User Access Manager
8+
9+
This table displays the current list of users ns and allows the user access manager to search users.
10+
11+
<ArgTypes />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import UserAccessManager from './UserAccessManager.vue';
2+
import {http, HttpResponse} from 'msw';
3+
import userAccessMock from './mocks/UserAccessMock.js';
4+
5+
export default {
6+
title: 'Managers/UserAccessManager',
7+
component: UserAccessManager,
8+
};
9+
10+
export const Init = {
11+
render: (args) => ({
12+
components: {UserAccessManager},
13+
setup() {
14+
return {args};
15+
},
16+
template: '<UserAccessManager v-bind="args"/>',
17+
}),
18+
parameters: {
19+
msw: {
20+
handlers: [
21+
http.get(
22+
'https://mock/index.php/publicknowledge/api/v1/users',
23+
async ({request}) => {
24+
const url = new URL(request.url);
25+
const offset = parseInt(url.searchParams.get('offset') || 0);
26+
const count = parseInt(url.searchParams.get('count'));
27+
const users = userAccessMock.items.slice(offset, offset + count);
28+
29+
return HttpResponse.json({
30+
itemsMax: userAccessMock.itemsMax,
31+
items: users,
32+
});
33+
},
34+
),
35+
],
36+
},
37+
},
38+
args: [],
39+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<template>
2+
<PkpTable class="mt-2">
3+
<template #label>
4+
<h3 class="text-3xl-bold">
5+
{{ t('grid.user.currentUsers') }}({{
6+
store.userAccessPagination.itemCount
7+
}})
8+
</h3>
9+
</template>
10+
<template #top-controls>
11+
<Search
12+
:search-phrase="searchPhrase"
13+
:search-label="t('userAccess.search')"
14+
@search-phrase-changed="store.setSearchPhrase"
15+
/>
16+
</template>
17+
<TableHeader>
18+
<TableColumn v-for="(column, i) in store.getColumns()" :key="i">
19+
<span :class="column.headerSrOnly ? 'sr-only' : ''">
20+
{{ column.header }}
21+
</span>
22+
</TableColumn>
23+
</TableHeader>
24+
<TableBody>
25+
<TableRow v-for="user in store.userList" :key="user.id">
26+
<component
27+
:is="Components[column.component] || column.component"
28+
v-for="(column, i) in store.getColumns()"
29+
:key="i"
30+
:user="user"
31+
></component>
32+
</TableRow>
33+
</TableBody>
34+
</PkpTable>
35+
<TablePagination
36+
:pagination="store.userAccessPagination"
37+
@set-page="store.setCurrentPage"
38+
/>
39+
</template>
40+
41+
<script setup>
42+
import PkpTable from '@/components/Table/Table.vue';
43+
import TableHeader from '@/components/Table/TableHeader.vue';
44+
import TableColumn from '@/components/Table/TableColumn.vue';
45+
import TableBody from '@/components/Table/TableBody.vue';
46+
import TableRow from '@/components/Table/TableRow.vue';
47+
import {useUserAccessManagerStore} from './UserAccessManagerStore.js';
48+
import TablePagination from '@/components/Table/TablePagination.vue';
49+
import {useTranslation} from '@/composables/useTranslation';
50+
import Search from '@/components/Search/Search.vue';
51+
import {ref} from 'vue';
52+
import UserAccessManagerCellStartDate from './UserAccessManagerCellStartDate.vue';
53+
import UserAccessManagerCellUserGroups from './UserAccessManagerCellUserGroups.vue';
54+
import UserAccessManagerCellActions from './UserAccessManagerCellActions.vue';
55+
import UserAccessManagerCellName from './UserAccessManagerCellName.vue';
56+
import UserAccessManagerCellEmail from './UserAccessManagerCellEmail.vue';
57+
import UserAccessManagerCellAffiliation from './UserAccessManagerCellAffiliation.vue';
58+
59+
const Components = {
60+
UserAccessManagerCellStartDate,
61+
UserAccessManagerCellUserGroups,
62+
UserAccessManagerCellActions,
63+
UserAccessManagerCellName,
64+
UserAccessManagerCellEmail,
65+
UserAccessManagerCellAffiliation,
66+
};
67+
68+
const store = useUserAccessManagerStore();
69+
const {t} = useTranslation();
70+
const searchPhrase = ref('');
71+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<template>
2+
<TableCell>
3+
<DropdownActions
4+
:actions="store.getItemActions({user})"
5+
:label="t('userAccess.management.options')"
6+
button-variant="ellipsis"
7+
direction="left"
8+
@action="(actionName) => store[actionName]({user})"
9+
/>
10+
</TableCell>
11+
</template>
12+
13+
<script setup>
14+
import TableCell from '@/components/Table/TableCell.vue';
15+
import DropdownActions from '@/components/DropdownActions/DropdownActions.vue';
16+
import {useUserAccessManagerStore} from './UserAccessManagerStore.js';
17+
import {useTranslation} from '@/composables/useTranslation';
18+
19+
defineProps({
20+
user: {type: Object, required: true},
21+
});
22+
23+
const store = useUserAccessManagerStore();
24+
const {t} = useTranslation();
25+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<template>
2+
<TableCell>
3+
<span class="text-base-normal">
4+
{{ localize(user.affiliation) }}
5+
</span>
6+
</TableCell>
7+
</template>
8+
9+
<script setup>
10+
import TableCell from '@/components/Table/TableCell.vue';
11+
12+
defineProps({
13+
user: {type: Object, required: true},
14+
});
15+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<template>
2+
<TableCell>
3+
<span class="text-base-normal">
4+
{{ user.email }}
5+
</span>
6+
</TableCell>
7+
</template>
8+
9+
<script setup>
10+
import TableCell from '@/components/Table/TableCell.vue';
11+
12+
defineProps({
13+
user: {type: Object, required: true},
14+
});
15+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<template>
2+
<TableCell>
3+
<span class="text-base-normal">
4+
{{ user.fullName }}
5+
</span>
6+
7+
<Icon v-if="user.orcid" icon="Orcid" class="h-4 w-4" :inline="true" />
8+
</TableCell>
9+
</template>
10+
11+
<script setup>
12+
import TableCell from '@/components/Table/TableCell.vue';
13+
import Icon from '@/components/Icon/Icon.vue';
14+
15+
defineProps({
16+
user: {type: Object, required: true},
17+
});
18+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<template>
2+
<TableCell>
3+
<template v-for="(userGroups, i) in user.groups" :key="i">
4+
<div class="flex flex-col">
5+
{{ formatShortDate(userGroups?.startDate) }}
6+
</div>
7+
</template>
8+
</TableCell>
9+
</template>
10+
11+
<script setup>
12+
import TableCell from '@/components/Table/TableCell.vue';
13+
import {useDate} from '@/composables/useDate';
14+
15+
defineProps({
16+
user: {type: Object, required: true},
17+
});
18+
const {formatShortDate} = useDate();
19+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<template>
2+
<TableCell>
3+
<template v-for="(userGroups, i) in user.groups" :key="i">
4+
<div class="flex flex-col">
5+
{{ userGroups.name }}
6+
</div>
7+
</template>
8+
</TableCell>
9+
</template>
10+
11+
<script setup>
12+
import TableCell from '@/components/Table/TableCell.vue';
13+
14+
defineProps({
15+
user: {type: Object, required: true},
16+
});
17+
</script>

0 commit comments

Comments
 (0)