Skip to content

Commit

Permalink
invitation table view and api integration
Browse files Browse the repository at this point in the history
  • Loading branch information
ipula committed Aug 20, 2024
1 parent 83820ab commit 23647b7
Show file tree
Hide file tree
Showing 26 changed files with 914 additions and 178 deletions.
10 changes: 8 additions & 2 deletions public/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,8 @@ window.pkp = {
'submissions.declined': 'Declined',
'submissions.incomplete': 'Incomplete',
todo: '##todo##',
'user.email': 'Email',
'user.emailAddress': 'Email Address',
'about.contact.email': 'Email',
'user.email': 'Email Address',
'user.orcid': 'ORCID iD',
'user.username': 'Username',
'user.password': 'Password',
Expand Down Expand Up @@ -560,6 +560,12 @@ window.pkp = {
'acceptInvitation.modal.button':'View All Submissions',
'acceptInvitation.privacyStatement.btn':'Privacy Statement',
'acceptInvitation.privacyStatement.label':'Yes, I agree to have my data collected and stored according to the',
'invitation.cancel': 'Cancel Invite',
'invitation.inviteToRole.btn': 'Invite to a role',
'invitation.header': 'Invitation',
'invitation.tableHeader.name': 'Name',
'invitation.searchForm.emptyError': 'At least provide one search criteria.',

},

tinyMCE: {
Expand Down
9 changes: 9 additions & 0 deletions src/managers/InvitationManager/InvitationManager.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {Primary, Controls, Stories, Meta, ArgTypes} from '@storybook/blocks';

import * as InvitationManager from './InvitationManager.stories.js';

<Meta of={InvitationManager} />

# Invitation page

<ArgTypes />
42 changes: 42 additions & 0 deletions src/managers/InvitationManager/InvitationManager.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import InvitationManager from './InvitationManager.vue';
import {http, HttpResponse} from 'msw';
import invitationMock from './mocks/invitationMock.js';

export default {
title: 'Managers/InvitationManager',
component: InvitationManager,
};

export const Init = {
render: (args) => ({
components: {InvitationManager},
setup() {
return {args};
},
template: '<InvitationManager v-bind="args"/>',
}),
parameters: {
msw: {
handlers: [
http.get(
'https://mock/index.php/publicknowledge/api/v1/invitations',
async ({request}) => {
const url = new URL(request.url);
const offset = parseInt(url.searchParams.get('offset') || 0);
const count = parseInt(url.searchParams.get('count'));
const invitations = invitationMock.items.slice(
offset,
offset + count,
);

return HttpResponse.json({
itemsMax: invitationMock.itemsMax,
items: invitations,
});
},
),
],
},
},
args: [],
};
108 changes: 108 additions & 0 deletions src/managers/InvitationManager/InvitationManager.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<template>
<div
v-if="!isInvitationLoading"
class="flex items-center pb-2 pt-2 text-3xl-normal"
>
<h4>
{{ t('invitation.header') }}({{ store.invitationsPagination.itemCount }})
</h4>
<div class="ml-auto">
<PkpButton @click="store.sendInvitation">
{{ t('invitation.inviteToRole.btn') }}
</PkpButton>
</div>
</div>
<PkpTable aria-label="Example for basic table">
<TableHeader>
<TableColumn>{{ t('invitation.tableHeader.name') }}</TableColumn>
<TableColumn>{{ t('about.contact.email') }}</TableColumn>
<TableColumn>{{ t('invitation.header') }}</TableColumn>
<TableColumn>{{ t('common.status') }}</TableColumn>
<TableColumn>{{ t('user.affiliation') }}</TableColumn>
<TableColumn></TableColumn>
</TableHeader>
<TableBody>
<TableRow v-if="store.invitations.length === 0">
<TableCell>No invitation have been sent out yet.</TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
<TableRow v-for="(row, index) in store.invitations" :key="index">
<TableCell>
{{ store.getFullName(row.givenName, row.familyName) }}
<Icon icon="orcid" :inline="true" />
</TableCell>
<TableCell>
{{ row.invitationModel.email }}
</TableCell>
<TableCell>
<template v-for="(userGroups, i) in row.userGroupsToAdd" :key="i">
<span>
{{ localize(userGroups.userGroupPayload.userGroup._data.name) }}
</span>
<br />
</template>
</TableCell>
<TableCell>
Invited
{{
new Date(row.invitationModel.createdAt).toLocaleDateString('en-US')
}}
</TableCell>
<TableCell>
{{ localize(row.affiliation) }}
</TableCell>
<TableCell>
<DropdownActions
:actions="[
{
label: 'View',
url: store.pageUrl + '/' + row.invitationModel.id,
icon: 'View',
},
{
label: 'Cancel Invite',
url: store.pageUrl + '/' + row.invitationModel.id,
icon: 'Cancel',
isWarnable: true,
},
]"
label="Invitation management options"
:display-as-ellipsis="true"
direction="left"
/>
</TableCell>
</TableRow>
</TableBody>
</PkpTable>
<div class="flex justify-end">
<Pagination
:current-page="store.invitationsPagination.currentPage"
:last-page="store.invitationsPagination.pageCount"
:is-loading="store.isInvitationLoading"
:show-adjacent-pages="3"
@set-page="store.setCurrentPage"
/>
</div>
</template>

<script setup>
import PkpTable from '@/components/TableNext/Table.vue';
import TableCell from '@/components/TableNext/TableCell.vue';
import TableHeader from '@/components/TableNext/TableHeader.vue';
import TableColumn from '@/components/TableNext/TableColumn.vue';
import TableBody from '@/components/TableNext/TableBody.vue';
import TableRow from '@/components/TableNext/TableRow.vue';
import Icon from '@/components/Icon/Icon.vue';
import PkpButton from '@/components/Button/Button.vue';
import {useInvitationManagerStore} from './InvitationManagerStore.js';
import Pagination from '@/components/Pagination/Pagination.vue';
import {useTranslation} from '@/composables/useTranslation';
import DropdownActions from '@/components/DropdownActions/DropdownActions.vue';
const store = useInvitationManagerStore();
const {t} = useTranslation();
</script>
85 changes: 85 additions & 0 deletions src/managers/InvitationManager/InvitationManagerStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {defineComponentStore} from '@/utils/defineComponentStore';
import {useApiUrl} from '@/composables/useApiUrl';
import {useAnnouncer} from '@/composables/useAnnouncer';
import {useUrl} from '@/composables/useUrl';
import {useLocalize} from '@/composables/useLocalize';
import {useFetchPaginated} from '@/composables/useFetchPaginated';
import {computed, ref, watch} from 'vue';

export const useInvitationManagerStore = defineComponentStore(
'invitationsPage',
() => {
const {t} = useLocalize();
/** Announcer */

const {announce} = useAnnouncer();
/**
* redirect to send invitation page
*/
const {pageUrl} = useUrl('invitation/invite');
const sendInvitationPageUrl = computed(() => {
return pageUrl.value;
});
function sendInvitation() {
window.location = sendInvitationPageUrl.value;
}

/**
* get invitations twith paginations
*/
const invitationCount = ref(0);

const countPerPage = ref(2);
const currentPage = ref(1);
function setCurrentPage(_currentPage) {
currentPage.value = _currentPage;
}

const {apiUrl} = useApiUrl('invitations');
const getInvitationApiUrl = computed(() => {
return apiUrl.value;
});
const {
items: invitations,
pagination: invitationsPagination,
isLoading: isInvitationLoading,
fetch: fetchInvitation,
} = useFetchPaginated(getInvitationApiUrl, {
currentPage,
pageSize: countPerPage,
});
watch(
[currentPage],
async () => {
announce(t('common.loading'));

await fetchInvitation();
announce(t('common.loaded'));
},
{immediate: true},
);

/**
* create user full name
* @param String givenName
* @param String familyName
* @returns String
*/
function getFullName(givenName, familyName) {
return givenName + ' ' + familyName;
}

return {
invitationCount,
setCurrentPage,
sendInvitation,
currentPage,
invitationsPagination,
pageUrl,
// invitation table data
invitations,
isInvitationLoading,
getFullName,
};
},
);
Loading

0 comments on commit 23647b7

Please sign in to comment.