web/satellite: project members frontend selection caching added (#3238)

This commit is contained in:
Nikolay Yurchenko 2019-10-15 16:22:41 +03:00 committed by GitHub
parent f1867a954b
commit 875c7dfe72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 92 additions and 31 deletions

View File

@ -35,9 +35,10 @@
is-white="true"
:on-press="onClearSelection"
/>
<span class="header-selected-members__info-text"><b>{{selectedProjectMembersCount}}</b> users selected</span>
</div>
<div class="header-after-delete-click" v-if="areSelectedProjectMembersBeingDeleted">
<span class="header-after-delete-click__delete-confirmation">Are you sure you want to delete {{selectedProjectMembersCount}} {{userCountTitle}}?</span>
<span class="header-after-delete-click__delete-confirmation">Are you sure you want to delete <b>{{selectedProjectMembersCount}}</b> {{userCountTitle}}?</span>
<div class="header-after-delete-click__button-area">
<VButton
class="button deletion"
@ -71,7 +72,7 @@ import VButton from '@/components/common/VButton.vue';
import VHeader from '@/components/common/VHeader.vue';
import AddUserPopup from '@/components/team/AddUserPopup.vue';
import { ProjectMember, ProjectMemberHeaderState } from '@/types/projectMembers';
import { ProjectMemberHeaderState } from '@/types/projectMembers';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS, PM_ACTIONS } from '@/utils/constants/actionNames';
declare interface ClearSearch {
@ -119,22 +120,20 @@ export default class HeaderArea extends Vue {
this.$store.dispatch(PM_ACTIONS.CLEAR_SELECTION);
this.isDeleteClicked = false;
this.$emit('onSuccessAction');
this.$refs.headerComponent.clearSearch();
}
public async onDelete(): Promise<void> {
const projectMemberEmails: string[] = this.$store.getters.selectedProjectMembers.map((member: ProjectMember) => {
return member.user.email;
});
try {
await this.$store.dispatch(PM_ACTIONS.DELETE, projectMemberEmails);
await this.$store.dispatch(PM_ACTIONS.DELETE);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Error while deleting users from projectMembers. ${error.message}`);
return;
}
this.$emit('onSuccessAction');
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Members was successfully removed from project');
this.isDeleteClicked = false;
@ -212,6 +211,11 @@ export default class HeaderArea extends Vue {
align-items: flex-end;
height: 85px;
justify-content: center;
&__info-text {
margin-left: 25px;
line-height: 48px;
}
}
.button {

View File

@ -6,7 +6,8 @@
<div class="team-area__header">
<HeaderArea
:header-state="headerState"
:selected-project-members-count="selectedProjectMembers.length"
:selected-project-members-count="selectedProjectMembersLength"
@onSuccessAction="resetPaginator"
/>
</div>
<div class="team-area__container" id="team-container" v-if="isTeamAreaShown">
@ -99,7 +100,7 @@ export default class ProjectMembersArea extends Vue {
}
public onMemberClick(member: ProjectMember): void {
this.$store.dispatch(PM_ACTIONS.TOGGLE_SELECTION, member.user.id);
this.$store.dispatch(PM_ACTIONS.TOGGLE_SELECTION, member);
}
public get projectMembers(): ProjectMember[] {
@ -122,12 +123,12 @@ export default class ProjectMembersArea extends Vue {
return this.$store.state.projectMembersModule.page.pageCount;
}
public get selectedProjectMembers(): ProjectMember[] {
return this.$store.getters.selectedProjectMembers;
public get selectedProjectMembersLength(): number {
return this.$store.state.projectMembersModule.selectedProjectMembersEmails.length;
}
public get headerState(): number {
if (this.selectedProjectMembers.length > 0) {
if (this.selectedProjectMembersLength > 0) {
return ProjectMemberHeaderState.ON_SELECT;
}
@ -159,6 +160,10 @@ export default class ProjectMembersArea extends Vue {
await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project members. ${error.message}`);
}
this.resetPaginator();
}
public resetPaginator(): void {
if (this.totalPageCount > 1) {
this.$refs.pagination.resetPageIndex();
}

View File

@ -36,6 +36,7 @@ const {
class ProjectMembersState {
public cursor: ProjectMemberCursor = new ProjectMemberCursor();
public page: ProjectMembersPage = new ProjectMembersPage();
public selectedProjectMembersEmails: string[] = [];
}
export function makeProjectMembersModule(api: ProjectMembersApi): StoreModule<ProjectMembersState> {
@ -44,6 +45,13 @@ export function makeProjectMembersModule(api: ProjectMembersApi): StoreModule<Pr
mutations: {
[FETCH](state: ProjectMembersState, page: ProjectMembersPage) {
state.page = page;
state.page.projectMembers = state.page.projectMembers.map(member => {
if (state.selectedProjectMembersEmails.includes(member.user.email)) {
member.isSelected = true;
}
return member;
});
},
[SET_PAGE](state: ProjectMembersState, page: number) {
state.cursor.page = page;
@ -60,17 +68,23 @@ export function makeProjectMembersModule(api: ProjectMembersApi): StoreModule<Pr
[CLEAR](state: ProjectMembersState) {
state.cursor = new ProjectMemberCursor();
state.page = new ProjectMembersPage();
state.selectedProjectMembersEmails = [];
},
[TOGGLE_SELECTION](state: ProjectMembersState, projectMemberId: string) {
state.page.projectMembers = state.page.projectMembers.map((projectMember: ProjectMember) => {
if (projectMember.user.id === projectMemberId) {
projectMember.isSelected = !projectMember.isSelected;
[TOGGLE_SELECTION](state: ProjectMembersState, projectMember: ProjectMember) {
if (!state.selectedProjectMembersEmails.includes(projectMember.user.email)) {
projectMember.isSelected = true;
state.selectedProjectMembersEmails.push(projectMember.user.email);
return;
}
return projectMember;
projectMember.isSelected = false;
state.selectedProjectMembersEmails = state.selectedProjectMembersEmails.filter(projectMemberEmail => {
return projectMemberEmail !== projectMember.user.email;
});
},
[CLEAR_SELECTION](state: ProjectMembersState) {
state.selectedProjectMembersEmails = [];
state.page.projectMembers = state.page.projectMembers.map((projectMember: ProjectMember) => {
projectMember.isSelected = false;
@ -84,10 +98,12 @@ export function makeProjectMembersModule(api: ProjectMembersApi): StoreModule<Pr
await api.add(projectId, emails);
},
deleteProjectMembers: async function ({rootGetters}: any, projectMemberEmails: string[]): Promise<void> {
deleteProjectMembers: async function ({rootGetters, state, commit}: any): Promise<void> {
const projectId = rootGetters.selectedProject.id;
await api.delete(projectId, projectMemberEmails);
await api.delete(projectId, state.selectedProjectMembersEmails);
commit(CLEAR_SELECTION);
},
fetchProjectMembers: async function ({commit, rootGetters, state}: any, page: number): Promise<ProjectMembersPage> {
const projectID = rootGetters.selectedProject.id;
@ -112,15 +128,17 @@ export function makeProjectMembersModule(api: ProjectMembersApi): StoreModule<Pr
clearProjectMembers: function ({commit}) {
commit(CLEAR);
},
toggleProjectMemberSelection: function ({commit}: any, projectMemberId: string) {
commit(TOGGLE_SELECTION, projectMemberId);
toggleProjectMemberSelection: function ({commit}: any, projectMember: ProjectMember) {
commit(TOGGLE_SELECTION, projectMember);
},
clearProjectMemberSelection: function ({commit}: any) {
commit(CLEAR_SELECTION);
},
},
getters: {
selectedProjectMembers: (state: any) => state.page.projectMembers.filter((member: ProjectMember) => member.isSelected),
selectedProjectMembers: (state: ProjectMembersState) =>
state.page.projectMembers.filter((member: ProjectMember) =>
state.selectedProjectMembersEmails.includes(member.user.email)),
},
};
}

View File

@ -93,8 +93,8 @@ export class ProjectMember {
public joinedAt: string;
public isSelected: boolean;
public constructor(fullName: string, shortName: string, email: string, joinedAt: string, id?: string) {
this.user = new User(id || '', fullName, shortName, email);
public constructor(fullName: string, shortName: string, email: string, joinedAt: string, id: string = '') {
this.user = new User(id, fullName, shortName, email);
this.joinedAt = joinedAt;
this.isSelected = false;
}

View File

@ -25,7 +25,7 @@ exports[`Team HeaderArea renders correctly with 1 selected user and delete click
<vheader-stub placeholder="Team Members" search="function () { [native code] }">
<!---->
<!---->
<div class="header-after-delete-click"><span class="header-after-delete-click__delete-confirmation">Are you sure you want to delete 1 user?</span>
<div class="header-after-delete-click"><span class="header-after-delete-click__delete-confirmation">Are you sure you want to delete <b>1</b> user?</span>
<div class="header-after-delete-click__button-area">
<vbutton-stub label="Delete" width="122px" height="48px" onpress="function () { [native code] }" class="button deletion"></vbutton-stub>
<vbutton-stub label="Cancel" width="122px" height="48px" iswhite="true" onpress="function () { [native code] }" class="button"></vbutton-stub>
@ -46,7 +46,7 @@ exports[`Team HeaderArea renders correctly with 2 selected users and delete clic
<vheader-stub placeholder="Team Members" search="function () { [native code] }">
<!---->
<!---->
<div class="header-after-delete-click"><span class="header-after-delete-click__delete-confirmation">Are you sure you want to delete 2 users?</span>
<div class="header-after-delete-click"><span class="header-after-delete-click__delete-confirmation">Are you sure you want to delete <b>2</b> users?</span>
<div class="header-after-delete-click__button-area">
<vbutton-stub label="Delete" width="122px" height="48px" onpress="function () { [native code] }" class="button deletion"></vbutton-stub>
<vbutton-stub label="Cancel" width="122px" height="48px" iswhite="true" onpress="function () { [native code] }" class="button"></vbutton-stub>
@ -86,7 +86,7 @@ exports[`Team HeaderArea renders correctly with selected users 1`] = `
<!---->
<div class="header-selected-members">
<vbutton-stub label="Delete" width="122px" height="48px" onpress="function () { [native code] }" class="button deletion"></vbutton-stub>
<vbutton-stub label="Cancel" width="122px" height="48px" iswhite="true" onpress="function () { [native code] }" class="button"></vbutton-stub>
<vbutton-stub label="Cancel" width="122px" height="48px" iswhite="true" onpress="function () { [native code] }" class="button"></vbutton-stub> <span class="header-selected-members__info-text"><b>2</b> users selected</span>
</div>
<!---->
</vheader-stub>

View File

@ -79,9 +79,24 @@ describe('mutations', () => {
});
it('toggle selection', function () {
store.commit(PROJECT_MEMBER_MUTATIONS.TOGGLE_SELECTION, projectMember1.user.id);
const testProjectMembersPage = new ProjectMembersPage();
testProjectMembersPage.projectMembers = [projectMember1];
testProjectMembersPage.totalCount = 1;
testProjectMembersPage.pageCount = 1;
store.commit(PROJECT_MEMBER_MUTATIONS.TOGGLE_SELECTION, projectMember1);
expect(state.page.projectMembers[0].isSelected).toBe(true);
expect(state.selectedProjectMembersEmails.length).toBe(1);
store.commit(PROJECT_MEMBER_MUTATIONS.FETCH, testProjectMembersPage);
expect(state.selectedProjectMembersEmails.length).toBe(1);
store.commit(PROJECT_MEMBER_MUTATIONS.TOGGLE_SELECTION, projectMember1);
expect(state.page.projectMembers[0].isSelected).toBe(false);
expect(state.selectedProjectMembersEmails.length).toBe(0);
});
it('clear selection', function () {
@ -90,6 +105,8 @@ describe('mutations', () => {
state.page.projectMembers.forEach((pm: ProjectMember) => {
expect(pm.isSelected).toBe(false);
});
expect(state.selectedProjectMembersEmails.length).toBe(0);
});
it('clear store', function () {
@ -100,6 +117,7 @@ describe('mutations', () => {
expect(state.cursor.order).toBe(ProjectMemberOrderBy.NAME);
expect(state.cursor.orderDirection).toBe(SortDirection.ASCENDING);
expect(state.page.projectMembers.length).toBe(0);
expect(state.selectedProjectMembersEmails.length).toBe(0);
});
});
@ -243,9 +261,25 @@ describe('actions', async () => {
);
await store.dispatch(PM_ACTIONS.FETCH, FIRST_PAGE);
store.dispatch(PM_ACTIONS.TOGGLE_SELECTION, projectMember1.user.id);
store.dispatch(PM_ACTIONS.TOGGLE_SELECTION, projectMember1);
expect(state.page.projectMembers[0].isSelected).toBe(true);
expect(state.selectedProjectMembersEmails.length).toBe(1);
store.dispatch(PM_ACTIONS.TOGGLE_SELECTION, projectMember2);
expect(state.page.projectMembers[1].isSelected).toBe(true);
expect(state.selectedProjectMembersEmails.length).toBe(2);
await store.dispatch(PM_ACTIONS.FETCH, FIRST_PAGE);
expect(state.page.projectMembers[1].isSelected).toBe(true);
expect(state.selectedProjectMembersEmails.length).toBe(2);
store.dispatch(PM_ACTIONS.TOGGLE_SELECTION, projectMember1);
expect(state.page.projectMembers[0].isSelected).toBe(false);
expect(state.selectedProjectMembersEmails.length).toBe(1);
});
it('clear selection', function () {
@ -269,7 +303,6 @@ describe('actions', async () => {
describe('getters', () => {
const selectedProjectMember = new ProjectMember('testFullName2', 'testShortName2', 'test2@example.com', 'now2', '2');
selectedProjectMember.isSelected = true;
it('selected project members', function () {
const testProjectMembersPage = new ProjectMembersPage();
@ -278,6 +311,7 @@ describe('getters', () => {
testProjectMembersPage.pageCount = 1;
store.commit(PROJECT_MEMBER_MUTATIONS.FETCH, testProjectMembersPage);
store.commit(PROJECT_MEMBER_MUTATIONS.TOGGLE_SELECTION, selectedProjectMember);
const retrievedProjectMembers = store.getters.selectedProjectMembers;