From d11e8e09f762de102852c74a6748c1547fe0e175 Mon Sep 17 00:00:00 2001 From: Gasper Grom Date: Wed, 7 Jun 2023 16:43:50 +0200 Subject: [PATCH 1/2] Organization filters connect --- frontend/components.d.ts | 2 +- .../list/organization-list-table.vue | 73 +++++----- .../list/organization-list-toolbar.vue | 105 +++++++++----- .../components/organization-dropdown.vue | 20 ++- .../components/organization-name.vue | 4 +- .../config/filters/activeOn/config.ts | 16 ++- .../filters/enrichedOrganization/config.ts | 29 ++++ .../config/filters/founded/config.ts | 24 ++++ .../config/filters/headcount/config.ts | 30 ++++ .../config/filters/headcount/options.ts | 30 ++++ .../config/filters/headline/config.ts | 24 ++++ .../config/filters/industry/config.ts | 24 ++++ .../config/filters/joinedDate/config.ts | 17 ++- .../config/filters/lastActivityDate/config.ts | 17 ++- .../config/filters/location/config.ts | 24 ++++ .../organization/config/filters/main.ts | 28 ++++ .../config/filters/noOfActivities/config.ts | 17 ++- .../config/filters/noOfMembers/config.ts | 22 ++- .../config/filters/type/config.ts | 30 ++++ .../config/filters/type/options.ts | 30 ++++ .../organization/config/saved-views/main.ts | 21 +++ .../settings/common/includeFilterRenderer.ts | 17 +++ .../saved-views/settings/teamOrganization.ts | 15 ++ .../saved-views/settings/types/IncludeEnum.ts | 5 + .../saved-views/views/all-organizations.ts | 19 +++ .../config/saved-views/views/most-members.ts | 19 +++ .../saved-views/views/new-and-active.ts | 26 ++++ .../saved-views/views/team-organizations.ts | 19 +++ .../organization/organization-service.js | 19 +++ .../pages/organization-list-page.vue | 135 +++++++++--------- .../organization/store/pinia/actions.ts | 23 +++ .../organization/store/pinia/getters.ts | 2 + .../modules/organization/store/pinia/index.ts | 13 ++ .../modules/organization/store/pinia/state.ts | 31 ++++ .../organization/types/Organization.ts | 46 ++++++ .../pages/templates/report-template-page.vue | 2 +- .../widget-active-leaderboard-members.vue | 4 +- .../components/filterTypes/NumberFilter.vue | 15 +- .../string.filter.renderer.ts | 17 ++- .../types/filterTypes/NumberFilterConfig.ts | 1 + .../saved-views/components/SavedViews.vue | 2 - 41 files changed, 831 insertions(+), 186 deletions(-) create mode 100644 frontend/src/modules/organization/config/filters/enrichedOrganization/config.ts create mode 100644 frontend/src/modules/organization/config/filters/founded/config.ts create mode 100644 frontend/src/modules/organization/config/filters/headcount/config.ts create mode 100644 frontend/src/modules/organization/config/filters/headcount/options.ts create mode 100644 frontend/src/modules/organization/config/filters/headline/config.ts create mode 100644 frontend/src/modules/organization/config/filters/industry/config.ts create mode 100644 frontend/src/modules/organization/config/filters/location/config.ts create mode 100644 frontend/src/modules/organization/config/filters/type/config.ts create mode 100644 frontend/src/modules/organization/config/filters/type/options.ts create mode 100644 frontend/src/modules/organization/config/saved-views/main.ts create mode 100644 frontend/src/modules/organization/config/saved-views/settings/common/includeFilterRenderer.ts create mode 100644 frontend/src/modules/organization/config/saved-views/settings/teamOrganization.ts create mode 100644 frontend/src/modules/organization/config/saved-views/settings/types/IncludeEnum.ts create mode 100644 frontend/src/modules/organization/config/saved-views/views/all-organizations.ts create mode 100644 frontend/src/modules/organization/config/saved-views/views/most-members.ts create mode 100644 frontend/src/modules/organization/config/saved-views/views/new-and-active.ts create mode 100644 frontend/src/modules/organization/config/saved-views/views/team-organizations.ts create mode 100644 frontend/src/modules/organization/store/pinia/actions.ts create mode 100644 frontend/src/modules/organization/store/pinia/getters.ts create mode 100644 frontend/src/modules/organization/store/pinia/index.ts create mode 100644 frontend/src/modules/organization/store/pinia/state.ts create mode 100644 frontend/src/modules/organization/types/Organization.ts diff --git a/frontend/components.d.ts b/frontend/components.d.ts index adcc186b5e..a4a0d738ae 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -13,13 +13,13 @@ declare module '@vue/runtime-core' { ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElButton: typeof import('element-plus/es')['ElButton'] ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] + ElCard: typeof import('element-plus/es')['ElCard'] ElCarousel: typeof import('element-plus/es')['ElCarousel'] ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCol: typeof import('element-plus/es')['ElCol'] ElCollapse: typeof import('element-plus/es')['ElCollapse'] ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] - ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] ElContainer: typeof import('element-plus/es')['ElContainer'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDialog: typeof import('element-plus/es')['ElDialog'] diff --git a/frontend/src/modules/organization/components/list/organization-list-table.vue b/frontend/src/modules/organization/components/list/organization-list-table.vue index cc84cc4a0c..833fcd3846 100644 --- a/frontend/src/modules/organization/components/list/organization-list-table.vue +++ b/frontend/src/modules/organization/components/list/organization-list-table.vue @@ -17,7 +17,7 @@ />
@@ -77,12 +76,13 @@ 0" + v-if="selectedOrganizations.length > 0" class="app-list-table-bulk-actions" > {{ - pluralize('organization', selectedRows.length, true) + pluralize('organization', selectedOrganizations.length, true) }} selected @@ -22,6 +22,7 @@ { + const ids = selectedOrganizations.value.map((m) => m.id); + return OrganizationService.destroyAll(ids); + }) + .then(() => fetchOrganizations({ reload: true })); + +const handleDoExport = async () => { try { - await ConfirmDialog({ - type: 'danger', - title: 'Delete organizations', - message: - "Are you sure you want to proceed? You can't undo this action", - confirmButtonText: 'Confirm', - cancelButtonText: 'Cancel', - icon: 'ri-delete-bin-line', - }); + const filter = { + id: { + in: selectedOrganizations.value.map((o) => o.id), + }, + }; - await doDestroyAll( - selectedRows.value.map((item) => item.id), + const response = await OrganizationService.list( + filter, + `${filters.value.order.prop}_${filters.value.order.order === 'descending' ? 'DESC' : 'ASC'}`, + null, + null, + false, ); - } catch (error) { - console.error(error); - } -}; -const handleDoExport = async () => { - try { - await doExport(); + Excel.exportAsExcelFile( + response.rows.map((o) => ({ + Id: o.id, + Name: o.name, + Description: o.description, + Headline: o.headline, + Website: o.website, + '# of members': o.memberCount, + '# of activities': o.activityCount, + Location: o.location, + Created: o.createdAt, + Updated: o.updatedAt, + })), + ['Id', 'Name', 'Description', + 'Headline', 'Headline', '# of members', + '# of activities', 'Location', 'Created', 'Updated', + ], + `organizations_${new Date().getTime()}`, + ); + + Message.success('Organizations exported successfully'); } catch (error) { - console.error(error); + Errors.handle(error); + Message.error( + 'There was an error exporting organizations', + ); } }; @@ -156,20 +197,20 @@ const handleCommand = async (command) => { await handleDoDestroyAllWithConfirm(); } else if (command.action === 'markAsTeamOrganization') { Promise.all( - selectedRows.value.map((row) => OrganizationService.update(row.id, { + selectedOrganizations.value.map((row) => OrganizationService.update(row.id, { isTeamOrganization: command.value, })), ).then(() => { Message.success( `${pluralize( 'Organization', - selectedRows.length, + selectedOrganizations.value.length, false, )} updated successfully`, ); - doFetch({ - keepPagination: true, + fetchOrganizations({ + reload: true, }); }); } diff --git a/frontend/src/modules/organization/components/organization-dropdown.vue b/frontend/src/modules/organization/components/organization-dropdown.vue index 5f57bf058d..04cb191330 100644 --- a/frontend/src/modules/organization/components/organization-dropdown.vue +++ b/frontend/src/modules/organization/components/organization-dropdown.vue @@ -97,6 +97,8 @@ import { } from '@/shared/vuex/vuex.helpers'; import ConfirmDialog from '@/shared/dialog/confirm-dialog'; import Message from '@/shared/message/message'; +import { useOrganizationStore } from '@/modules/organization/store/pinia'; +import { i18n } from '@/i18n'; import { OrganizationPermissions } from '../organization-permissions'; import { OrganizationService } from '../organization-service'; @@ -110,7 +112,10 @@ defineProps({ }); const { currentUser, currentTenant } = mapGetters('auth'); -const { doDestroy, doFetch, doFind } = mapActions('organization'); +const { doFind } = mapActions('organization'); + +const organizationStore = useOrganizationStore(); +const { fetchOrganizations } = organizationStore; const isReadOnly = computed( () => new OrganizationPermissions( @@ -144,7 +149,14 @@ const doDestroyWithConfirm = async (id) => { icon: 'ri-delete-bin-line', }); - return doDestroy(id); + await OrganizationService.destroyAll([id]); + + Message.success( + i18n('entities.organization.destroy.success'), + ); + await fetchOrganizations({ + reload: true, + }); } catch (error) { console.error(error); } @@ -170,8 +182,8 @@ const handleCommand = (command) => { if ( router.currentRoute.value.name === 'organization' ) { - doFetch({ - keepPagination: false, + fetchOrganizations({ + reload: true, }); } else { doFind(command.organization.id); diff --git a/frontend/src/modules/organization/components/organization-name.vue b/frontend/src/modules/organization/components/organization-name.vue index 88f50ebade..ff0c8efb8a 100644 --- a/frontend/src/modules/organization/components/organization-name.vue +++ b/frontend/src/modules/organization/components/organization-name.vue @@ -22,7 +22,7 @@
- {{ organization.displayName }} + {{ organization.displayName || organization.name }}
Last activity date ${value.value.join(', ') || '...'}`; + itemLabelRenderer(value: MultiSelectFilterValue, options: MultiSelectFilterOptions): string { + return itemLabelRendererByType[FilterConfigType.MULTISELECT]('Active on', value, options); }, - apiFilterRenderer(value): any[] { - console.log(value); - return []; + apiFilterRenderer(value: MultiSelectFilterValue): any[] { + return apiFilterRendererByType[FilterConfigType.MULTISELECT]('activeOn', value); }, }; diff --git a/frontend/src/modules/organization/config/filters/enrichedOrganization/config.ts b/frontend/src/modules/organization/config/filters/enrichedOrganization/config.ts new file mode 100644 index 0000000000..a36b340305 --- /dev/null +++ b/frontend/src/modules/organization/config/filters/enrichedOrganization/config.ts @@ -0,0 +1,29 @@ +import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; +import { + BooleanFilterConfig, BooleanFilterOptions, + BooleanFilterValue, +} from '@/shared/modules/filters/types/filterTypes/BooleanFilterConfig'; +import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; + +const enrichedOrganization: BooleanFilterConfig = { + id: 'enrichedOrganization', + label: 'Enriched organization', + iconClass: 'ri-sparkling-line', + type: FilterConfigType.BOOLEAN, + options: {}, + itemLabelRenderer(value: BooleanFilterValue, options: BooleanFilterOptions): string { + return itemLabelRendererByType[FilterConfigType.BOOLEAN]('Enriched organization', value, options); + }, + apiFilterRenderer({ value, include }: BooleanFilterValue): any[] { + const filter = { + lastEnrichedAt: { + [value ? 'ne' : 'eq']: null, + }, + }; + return [ + (include ? filter : { not: filter }), + ]; + }, +}; + +export default enrichedOrganization; diff --git a/frontend/src/modules/organization/config/filters/founded/config.ts b/frontend/src/modules/organization/config/filters/founded/config.ts new file mode 100644 index 0000000000..c7fd5cc468 --- /dev/null +++ b/frontend/src/modules/organization/config/filters/founded/config.ts @@ -0,0 +1,24 @@ +import { + NumberFilterConfig, + NumberFilterOptions, + NumberFilterValue, +} from '@/shared/modules/filters/types/filterTypes/NumberFilterConfig'; +import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; +import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; +import { apiFilterRendererByType } from '@/shared/modules/filters/config/apiFilterRendererByType'; + +const founded: NumberFilterConfig = { + id: 'founded', + label: 'Founded', + iconClass: 'ri-flag-2-line', + type: FilterConfigType.NUMBER, + options: {}, + itemLabelRenderer(value: NumberFilterValue, options: NumberFilterOptions): string { + return itemLabelRendererByType[FilterConfigType.NUMBER]('Founded', value, options); + }, + apiFilterRenderer(value: NumberFilterValue): any[] { + return apiFilterRendererByType[FilterConfigType.NUMBER]('founded', value); + }, +}; + +export default founded; diff --git a/frontend/src/modules/organization/config/filters/headcount/config.ts b/frontend/src/modules/organization/config/filters/headcount/config.ts new file mode 100644 index 0000000000..93533c7163 --- /dev/null +++ b/frontend/src/modules/organization/config/filters/headcount/config.ts @@ -0,0 +1,30 @@ +import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; +import { + SelectFilterConfig, SelectFilterOptions, + SelectFilterValue, +} from '@/shared/modules/filters/types/filterTypes/SelectFilterConfig'; +import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; +import options from './options'; + +const headcount: SelectFilterConfig = { + id: 'headcount', + label: 'Headcount', + iconClass: 'ri-group-2-line', + type: FilterConfigType.SELECT, + options: { + options, + }, + itemLabelRenderer(value: SelectFilterValue, options: SelectFilterOptions): string { + return itemLabelRendererByType[FilterConfigType.SELECT]('Headcount', value, options); + }, + apiFilterRenderer({ value, include }: SelectFilterValue): any[] { + const filter = { + size: value, + }; + return [ + (include ? filter : { not: filter }), + ]; + }, +}; + +export default headcount; diff --git a/frontend/src/modules/organization/config/filters/headcount/options.ts b/frontend/src/modules/organization/config/filters/headcount/options.ts new file mode 100644 index 0000000000..0d8012f190 --- /dev/null +++ b/frontend/src/modules/organization/config/filters/headcount/options.ts @@ -0,0 +1,30 @@ +import { MultiSelectFilterOptionGroup } from '@/shared/modules/filters/types/filterTypes/MultiSelectFilterConfig'; + +const options: MultiSelectFilterOptionGroup[] = [ + { + options: [ + { + label: '1-10', + value: '1-10', + }, + { + label: '11-50', + value: '11-50', + }, + { + label: '51-200', + value: '51-200', + }, + { + label: '201-500', + value: '201-500', + }, + { + label: '501-1000', + value: '501-1000', + }, + ], + }, +]; + +export default options; diff --git a/frontend/src/modules/organization/config/filters/headline/config.ts b/frontend/src/modules/organization/config/filters/headline/config.ts new file mode 100644 index 0000000000..813d71c564 --- /dev/null +++ b/frontend/src/modules/organization/config/filters/headline/config.ts @@ -0,0 +1,24 @@ +import { + StringFilterConfig, + StringFilterOptions, + StringFilterValue, +} from '@/shared/modules/filters/types/filterTypes/StringFilterConfig'; +import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; +import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; +import { apiFilterRendererByType } from '@/shared/modules/filters/config/apiFilterRendererByType'; + +const headline: StringFilterConfig = { + id: 'headline', + label: 'Headline', + iconClass: 'ri-menu-2-line', + type: FilterConfigType.STRING, + options: {}, + itemLabelRenderer(value: StringFilterValue, options: StringFilterOptions): string { + return itemLabelRendererByType[FilterConfigType.STRING]('Headline', value, options); + }, + apiFilterRenderer(value: StringFilterValue): any[] { + return apiFilterRendererByType[FilterConfigType.STRING]('headline', value); + }, +}; + +export default headline; diff --git a/frontend/src/modules/organization/config/filters/industry/config.ts b/frontend/src/modules/organization/config/filters/industry/config.ts new file mode 100644 index 0000000000..06d4ca4549 --- /dev/null +++ b/frontend/src/modules/organization/config/filters/industry/config.ts @@ -0,0 +1,24 @@ +import { + StringFilterConfig, + StringFilterOptions, + StringFilterValue, +} from '@/shared/modules/filters/types/filterTypes/StringFilterConfig'; +import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; +import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; +import { apiFilterRendererByType } from '@/shared/modules/filters/config/apiFilterRendererByType'; + +const industry: StringFilterConfig = { + id: 'industry', + label: 'Industry', + iconClass: 'ri-briefcase-2-line', + type: FilterConfigType.STRING, + options: {}, + itemLabelRenderer(value: StringFilterValue, options: StringFilterOptions): string { + return itemLabelRendererByType[FilterConfigType.STRING]('Industry', value, options); + }, + apiFilterRenderer(value: StringFilterValue): any[] { + return apiFilterRendererByType[FilterConfigType.STRING]('industry', value); + }, +}; + +export default industry; diff --git a/frontend/src/modules/organization/config/filters/joinedDate/config.ts b/frontend/src/modules/organization/config/filters/joinedDate/config.ts index 6eb52e1347..e21bb3f481 100644 --- a/frontend/src/modules/organization/config/filters/joinedDate/config.ts +++ b/frontend/src/modules/organization/config/filters/joinedDate/config.ts @@ -1,5 +1,11 @@ import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; -import { DateFilterConfig } from '@/shared/modules/filters/types/filterTypes/DateFilterConfig'; +import { + DateFilterConfig, + DateFilterOptions, + DateFilterValue, +} from '@/shared/modules/filters/types/filterTypes/DateFilterConfig'; +import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; +import { apiFilterRendererByType } from '@/shared/modules/filters/config/apiFilterRendererByType'; const joinedDate: DateFilterConfig = { id: 'joinedDate', @@ -7,12 +13,11 @@ const joinedDate: DateFilterConfig = { iconClass: 'ri-calendar-event-line', type: FilterConfigType.DATE, options: {}, - itemLabelRenderer(value): string { - return `Joined date ${value.value || '...'}`; + itemLabelRenderer(value: DateFilterValue, options: DateFilterOptions): string { + return itemLabelRendererByType[FilterConfigType.DATE]('Joined date', value, options); }, - apiFilterRenderer(value): any[] { - console.log(value); - return []; + apiFilterRenderer(value: DateFilterValue): any[] { + return apiFilterRendererByType[FilterConfigType.DATE]('joinedAt', value); }, }; diff --git a/frontend/src/modules/organization/config/filters/lastActivityDate/config.ts b/frontend/src/modules/organization/config/filters/lastActivityDate/config.ts index 116af70a16..7de4734bea 100644 --- a/frontend/src/modules/organization/config/filters/lastActivityDate/config.ts +++ b/frontend/src/modules/organization/config/filters/lastActivityDate/config.ts @@ -1,5 +1,11 @@ import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; -import { DateFilterConfig } from '@/shared/modules/filters/types/filterTypes/DateFilterConfig'; +import { + DateFilterConfig, + DateFilterOptions, + DateFilterValue, +} from '@/shared/modules/filters/types/filterTypes/DateFilterConfig'; +import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; +import { apiFilterRendererByType } from '@/shared/modules/filters/config/apiFilterRendererByType'; const lastActivityDate: DateFilterConfig = { id: 'lastActivityDate', @@ -7,12 +13,11 @@ const lastActivityDate: DateFilterConfig = { iconClass: 'ri-calendar-event-line', type: FilterConfigType.DATE, options: {}, - itemLabelRenderer(value): string { - return `Last activity date ${value.value || '...'}`; + itemLabelRenderer(value: DateFilterValue, options: DateFilterOptions): string { + return itemLabelRendererByType[FilterConfigType.DATE]('Last activity date', value, options); }, - apiFilterRenderer(value): any[] { - console.log(value); - return []; + apiFilterRenderer(value: DateFilterValue): any[] { + return apiFilterRendererByType[FilterConfigType.DATE]('lastActive', value); }, }; diff --git a/frontend/src/modules/organization/config/filters/location/config.ts b/frontend/src/modules/organization/config/filters/location/config.ts new file mode 100644 index 0000000000..a3fc53db03 --- /dev/null +++ b/frontend/src/modules/organization/config/filters/location/config.ts @@ -0,0 +1,24 @@ +import { + StringFilterConfig, + StringFilterOptions, + StringFilterValue, +} from '@/shared/modules/filters/types/filterTypes/StringFilterConfig'; +import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; +import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; +import { apiFilterRendererByType } from '@/shared/modules/filters/config/apiFilterRendererByType'; + +const location: StringFilterConfig = { + id: 'location', + label: 'Location', + iconClass: 'ri-map-pin-2-line', + type: FilterConfigType.STRING, + options: {}, + itemLabelRenderer(value: StringFilterValue, options: StringFilterOptions): string { + return itemLabelRendererByType[FilterConfigType.STRING]('Location', value, options); + }, + apiFilterRenderer(value: StringFilterValue): any[] { + return apiFilterRendererByType[FilterConfigType.STRING]('location', value); + }, +}; + +export default location; diff --git a/frontend/src/modules/organization/config/filters/main.ts b/frontend/src/modules/organization/config/filters/main.ts index 74142ba2ff..34e66bee49 100644 --- a/frontend/src/modules/organization/config/filters/main.ts +++ b/frontend/src/modules/organization/config/filters/main.ts @@ -1,14 +1,42 @@ import { FilterConfig } from '@/shared/modules/filters/types/FilterConfig'; +import { SearchFilterConfig } from '@/shared/modules/filters/types/filterTypes/SearchFilterConfig'; import noOfMembers from './noOfMembers/config'; import noOfActivities from './noOfActivities/config'; import activeOn from './activeOn/config'; +import enrichedOrganization from './enrichedOrganization/config'; +import founded from './founded/config'; +import headcount from './headcount/config'; +import headline from './headline/config'; +import industry from './industry/config'; import joinedDate from './joinedDate/config'; import lastActivityDate from './lastActivityDate/config'; +import location from './location/config'; +import type from './type/config'; export const organizationFilters: Record = { noOfActivities, noOfMembers, activeOn, + enrichedOrganization, + founded, + headcount, + headline, + industry, joinedDate, lastActivityDate, + location, + type, +}; + +export const organizationSearchFilter: SearchFilterConfig = { + placeholder: 'Search organizations', + apiFilterRenderer(value: string): any[] { + return [ + { + or: [ + { name: { textContains: value } }, + ], + }, + ]; + }, }; diff --git a/frontend/src/modules/organization/config/filters/noOfActivities/config.ts b/frontend/src/modules/organization/config/filters/noOfActivities/config.ts index 248730f9b6..7ab53a370e 100644 --- a/frontend/src/modules/organization/config/filters/noOfActivities/config.ts +++ b/frontend/src/modules/organization/config/filters/noOfActivities/config.ts @@ -1,5 +1,11 @@ -import { NumberFilterConfig } from '@/shared/modules/filters/types/filterTypes/NumberFilterConfig'; +import { + NumberFilterConfig, + NumberFilterOptions, + NumberFilterValue, +} from '@/shared/modules/filters/types/filterTypes/NumberFilterConfig'; import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; +import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; +import { apiFilterRendererByType } from '@/shared/modules/filters/config/apiFilterRendererByType'; const noOfActivities: NumberFilterConfig = { id: 'noOfActivities', @@ -7,12 +13,11 @@ const noOfActivities: NumberFilterConfig = { iconClass: 'ri-radar-line', type: FilterConfigType.NUMBER, options: {}, - itemLabelRenderer(value): string { - return `# of activities ${value?.value || '...'}`; + itemLabelRenderer(value: NumberFilterValue, options: NumberFilterOptions): string { + return itemLabelRendererByType[FilterConfigType.NUMBER]('# of activities', value, options); }, - apiFilterRenderer(value): any[] { - console.log(value); - return []; + apiFilterRenderer(value: NumberFilterValue): any[] { + return apiFilterRendererByType[FilterConfigType.NUMBER]('activityCount', value); }, }; diff --git a/frontend/src/modules/organization/config/filters/noOfMembers/config.ts b/frontend/src/modules/organization/config/filters/noOfMembers/config.ts index aa1fa9b802..9a78500276 100644 --- a/frontend/src/modules/organization/config/filters/noOfMembers/config.ts +++ b/frontend/src/modules/organization/config/filters/noOfMembers/config.ts @@ -1,18 +1,26 @@ -import { NumberFilterConfig } from '@/shared/modules/filters/types/filterTypes/NumberFilterConfig'; +import { + NumberFilterConfig, + NumberFilterOptions, + NumberFilterValue, +} from '@/shared/modules/filters/types/filterTypes/NumberFilterConfig'; import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; +import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; +import { apiFilterRendererByType } from '@/shared/modules/filters/config/apiFilterRendererByType'; +import { FilterNumberOperator } from '@/shared/modules/filters/config/constants/number.constants'; const noOfMembers: NumberFilterConfig = { id: 'noOfMembers', label: '# of members', iconClass: 'ri-group-2-line', type: FilterConfigType.NUMBER, - options: {}, - itemLabelRenderer(value): string { - return `# of members ${value.value || '...'}`; + options: { + forceOperator: FilterNumberOperator.BETWEEN, }, - apiFilterRenderer(value): any[] { - console.log(value); - return []; + itemLabelRenderer(value: NumberFilterValue, options: NumberFilterOptions): string { + return itemLabelRendererByType[FilterConfigType.NUMBER]('# of members', value, options); + }, + apiFilterRenderer(value: NumberFilterValue): any[] { + return apiFilterRendererByType[FilterConfigType.NUMBER]('memberCount', value); }, }; diff --git a/frontend/src/modules/organization/config/filters/type/config.ts b/frontend/src/modules/organization/config/filters/type/config.ts new file mode 100644 index 0000000000..477e32d3f7 --- /dev/null +++ b/frontend/src/modules/organization/config/filters/type/config.ts @@ -0,0 +1,30 @@ +import { FilterConfigType } from '@/shared/modules/filters/types/FilterConfig'; +import { + SelectFilterConfig, SelectFilterOptions, + SelectFilterValue, +} from '@/shared/modules/filters/types/filterTypes/SelectFilterConfig'; +import { itemLabelRendererByType } from '@/shared/modules/filters/config/itemLabelRendererByType'; +import options from './options'; + +const type: SelectFilterConfig = { + id: 'type', + label: 'Type', + iconClass: 'ri-bank-line', + type: FilterConfigType.SELECT, + options: { + options, + }, + itemLabelRenderer(value: SelectFilterValue, options: SelectFilterOptions): string { + return itemLabelRendererByType[FilterConfigType.SELECT]('Type', value, options); + }, + apiFilterRenderer({ value, include }: SelectFilterValue): any[] { + const filter = { + type: { eq: value }, + }; + return [ + (include ? filter : { not: filter }), + ]; + }, +}; + +export default type; diff --git a/frontend/src/modules/organization/config/filters/type/options.ts b/frontend/src/modules/organization/config/filters/type/options.ts new file mode 100644 index 0000000000..2a5cd07de5 --- /dev/null +++ b/frontend/src/modules/organization/config/filters/type/options.ts @@ -0,0 +1,30 @@ +import { MultiSelectFilterOptionGroup } from '@/shared/modules/filters/types/filterTypes/MultiSelectFilterConfig'; + +const options: MultiSelectFilterOptionGroup[] = [ + { + options: [ + { + label: 'Educational', + value: 'educational', + }, + { + label: 'Government', + value: 'government', + }, + { + label: 'Nonprofit', + value: 'nonprofit', + }, + { + label: 'Private', + value: 'private', + }, + { + label: 'Public', + value: 'public', + }, + ], + }, +]; + +export default options; diff --git a/frontend/src/modules/organization/config/saved-views/main.ts b/frontend/src/modules/organization/config/saved-views/main.ts new file mode 100644 index 0000000000..e7ad4086a0 --- /dev/null +++ b/frontend/src/modules/organization/config/saved-views/main.ts @@ -0,0 +1,21 @@ +import { SavedView, SavedViewsConfig } from '@/shared/modules/saved-views/types/SavedViewsConfig'; +import allOrganizations from './views/all-organizations'; +import newAndActive from './views/new-and-active'; +import mostMembers from './views/most-members'; +import teamOrganizations from './views/team-organizations'; + +import teamOrganization from './settings/teamOrganization'; + +export const organizationSavedViews: SavedViewsConfig = { + defaultView: allOrganizations, + settings: { + teamOrganization, + }, +}; + +// Hardcoded views until we have backend done for it +export const organizationViews: SavedView[] = [ + newAndActive, + mostMembers, + teamOrganizations, +]; diff --git a/frontend/src/modules/organization/config/saved-views/settings/common/includeFilterRenderer.ts b/frontend/src/modules/organization/config/saved-views/settings/common/includeFilterRenderer.ts new file mode 100644 index 0000000000..ab54350309 --- /dev/null +++ b/frontend/src/modules/organization/config/saved-views/settings/common/includeFilterRenderer.ts @@ -0,0 +1,17 @@ +import { IncludeEnum } from '@/modules/member/config/saved-views/settings/types/IncludeEnum'; + +export const includeFilterRenderer = (property: string, value: IncludeEnum) => { + if (value === IncludeEnum.FILTER) { + return [ + { + [property]: { eq: true }, + }, + ]; + } + if (value === IncludeEnum.EXCLUDE) { + return [ + { [property]: { not: true } }, + ]; + } + return []; +}; diff --git a/frontend/src/modules/organization/config/saved-views/settings/teamOrganization.ts b/frontend/src/modules/organization/config/saved-views/settings/teamOrganization.ts new file mode 100644 index 0000000000..e83114c04a --- /dev/null +++ b/frontend/src/modules/organization/config/saved-views/settings/teamOrganization.ts @@ -0,0 +1,15 @@ +import { SavedViewsSetting } from '@/shared/modules/saved-views/types/SavedViewsConfig'; +import { includeFilterRenderer } from '@/modules/organization/config/saved-views/settings/common/includeFilterRenderer'; +import { IncludeEnum } from '@/modules/organization/config/saved-views/settings/types/IncludeEnum'; + +const teamOrganization: SavedViewsSetting = { + defaultValue: IncludeEnum.EXCLUDE, + queryUrlParser(value: string): IncludeEnum { + return value as IncludeEnum; + }, + apiFilterRenderer(value: IncludeEnum): any[] { + return includeFilterRenderer('isTeamOrganization', value); + }, +}; + +export default teamOrganization; diff --git a/frontend/src/modules/organization/config/saved-views/settings/types/IncludeEnum.ts b/frontend/src/modules/organization/config/saved-views/settings/types/IncludeEnum.ts new file mode 100644 index 0000000000..f3b3e6e3d2 --- /dev/null +++ b/frontend/src/modules/organization/config/saved-views/settings/types/IncludeEnum.ts @@ -0,0 +1,5 @@ +export enum IncludeEnum { + INCLUDE = 'include', + EXCLUDE = 'exclude', + FILTER = 'filter' +} diff --git a/frontend/src/modules/organization/config/saved-views/views/all-organizations.ts b/frontend/src/modules/organization/config/saved-views/views/all-organizations.ts new file mode 100644 index 0000000000..85191ce26c --- /dev/null +++ b/frontend/src/modules/organization/config/saved-views/views/all-organizations.ts @@ -0,0 +1,19 @@ +import { SavedView } from '@/shared/modules/saved-views/types/SavedViewsConfig'; + +const allOrganizations: SavedView = { + id: 'all-organizations', + label: 'All organizations', + filter: { + search: '', + relation: 'and', + order: { + prop: 'activityCount', + order: 'descending', + }, + settings: { + teamOrganization: 'exclude', + }, + }, +}; + +export default allOrganizations; diff --git a/frontend/src/modules/organization/config/saved-views/views/most-members.ts b/frontend/src/modules/organization/config/saved-views/views/most-members.ts new file mode 100644 index 0000000000..8fd8df717e --- /dev/null +++ b/frontend/src/modules/organization/config/saved-views/views/most-members.ts @@ -0,0 +1,19 @@ +import { SavedView } from '@/shared/modules/saved-views/types/SavedViewsConfig'; + +const mostMembers: SavedView = { + id: 'most-members', + label: 'Most members', + filter: { + search: '', + relation: 'and', + order: { + prop: 'memberCount', + order: 'descending', + }, + settings: { + teamOrganization: 'exclude', + }, + }, +}; + +export default mostMembers; diff --git a/frontend/src/modules/organization/config/saved-views/views/new-and-active.ts b/frontend/src/modules/organization/config/saved-views/views/new-and-active.ts new file mode 100644 index 0000000000..84bd63bf2f --- /dev/null +++ b/frontend/src/modules/organization/config/saved-views/views/new-and-active.ts @@ -0,0 +1,26 @@ +import { SavedView } from '@/shared/modules/saved-views/types/SavedViewsConfig'; +import moment from 'moment'; + +const newAndActive: SavedView = { + id: 'new-and-active', + label: 'New and active', + filter: { + search: '', + relation: 'and', + order: { + prop: 'joinedAt', + order: 'descending', + }, + settings: { + teamOrganization: 'exclude', + }, + + joinedDate: { + include: true, + operator: 'gt', + value: moment().subtract(1, 'month').format('YYYY-MM-DD'), + }, + }, +}; + +export default newAndActive; diff --git a/frontend/src/modules/organization/config/saved-views/views/team-organizations.ts b/frontend/src/modules/organization/config/saved-views/views/team-organizations.ts new file mode 100644 index 0000000000..b20c3ad48d --- /dev/null +++ b/frontend/src/modules/organization/config/saved-views/views/team-organizations.ts @@ -0,0 +1,19 @@ +import { SavedView } from '@/shared/modules/saved-views/types/SavedViewsConfig'; + +const teamOrganizations: SavedView = { + id: 'team-organizations', + label: 'Team organizations', + filter: { + search: '', + relation: 'and', + order: { + prop: 'lastActive', + order: 'descending', + }, + settings: { + teamOrganization: 'filter', + }, + }, +}; + +export default teamOrganizations; diff --git a/frontend/src/modules/organization/organization-service.js b/frontend/src/modules/organization/organization-service.js index 44e91f7aa7..f716ddbaa1 100644 --- a/frontend/src/modules/organization/organization-service.js +++ b/frontend/src/modules/organization/organization-service.js @@ -93,6 +93,25 @@ export class OrganizationService { return response.data; } + static async listOrganizations( + body, + ) { + const sampleTenant = AuthCurrentTenant.getSampleTenantData(); + const tenantId = sampleTenant?.id || AuthCurrentTenant.get(); + + const response = await authAxios.post( + `/tenant/${tenantId}/organization/query`, + body, + { + headers: { + Authorization: sampleTenant?.token, + }, + }, + ); + + return response.data; + } + static async listAutocomplete(query, limit) { const params = { query, diff --git a/frontend/src/modules/organization/pages/organization-list-page.vue b/frontend/src/modules/organization/pages/organization-list-page.vue index 9a740ec260..fb0cbacc9b 100644 --- a/frontend/src/modules/organization/pages/organization-list-page.vue +++ b/frontend/src/modules/organization/pages/organization-list-page.vue @@ -32,35 +32,47 @@
- - - + + - diff --git a/frontend/src/modules/organization/store/pinia/actions.ts b/frontend/src/modules/organization/store/pinia/actions.ts new file mode 100644 index 0000000000..e7aaf21e10 --- /dev/null +++ b/frontend/src/modules/organization/store/pinia/actions.ts @@ -0,0 +1,23 @@ +import { Pagination } from '@/shared/types/Pagination'; +import { OrganizationState } from '@/modules/organization/store/pinia/state'; +import { Organization } from '@/modules/organization/types/Organization'; +import { OrganizationService } from '@/modules/organization/organization-service'; + +export default { + fetchOrganizations(this: OrganizationState, { body = {}, reload = false } :{ body?: any, reload?: boolean }): Promise> { + const mappedBody = reload ? this.savedFilterBody : body; + this.selectedOrganizations = []; + return OrganizationService.listOrganizations(mappedBody) + .then((data: Pagination) => { + this.organizations = data.rows; + this.totalOrganizations = data.count; + this.savedFilterBody = mappedBody; + return Promise.resolve(data); + }) + .catch((err: Error) => { + this.organizations = []; + this.totalOrganizations = 0; + return Promise.reject(err); + }); + }, +}; diff --git a/frontend/src/modules/organization/store/pinia/getters.ts b/frontend/src/modules/organization/store/pinia/getters.ts new file mode 100644 index 0000000000..02a61bed5e --- /dev/null +++ b/frontend/src/modules/organization/store/pinia/getters.ts @@ -0,0 +1,2 @@ +export default { +}; diff --git a/frontend/src/modules/organization/store/pinia/index.ts b/frontend/src/modules/organization/store/pinia/index.ts new file mode 100644 index 0000000000..49d1990fe6 --- /dev/null +++ b/frontend/src/modules/organization/store/pinia/index.ts @@ -0,0 +1,13 @@ +import { defineStore } from 'pinia'; +import state from './state'; +import getters from './getters'; +import actions from './actions'; + +export const useOrganizationStore = defineStore( + 'organization', + { + state, + getters, + actions, + }, +); diff --git a/frontend/src/modules/organization/store/pinia/state.ts b/frontend/src/modules/organization/store/pinia/state.ts new file mode 100644 index 0000000000..b447b2d94d --- /dev/null +++ b/frontend/src/modules/organization/store/pinia/state.ts @@ -0,0 +1,31 @@ +import { Filter } from '@/shared/modules/filters/types/FilterConfig'; +import { Organization } from '@/modules/organization/types/Organization'; + +export interface OrganizationState { + filters: Filter, + savedFilterBody: any, + organizations: Organization[]; + selectedOrganizations: Organization[]; + totalOrganizations: number; +} + +const state: OrganizationState = { + filters: { + search: '', + relation: 'and', + pagination: { + page: 1, + perPage: 20, + }, + order: { + prop: 'lastActive', + order: 'descending', + }, + } as Filter, + savedFilterBody: {}, + organizations: [], + selectedOrganizations: [], + totalOrganizations: 0, +}; + +export default () => state; diff --git a/frontend/src/modules/organization/types/Organization.ts b/frontend/src/modules/organization/types/Organization.ts new file mode 100644 index 0000000000..84f9174868 --- /dev/null +++ b/frontend/src/modules/organization/types/Organization.ts @@ -0,0 +1,46 @@ +export interface Organization{ + activeOn: string[]; + activityCount: number; + address: Record; + createdAt: string; + createdById: string; + crunchbase: Record | null; + deletedAt: string; + description: string; + displayName: string; + emails: string[] | null; + employeeCountByCountry: Record | null; + employees: number | null; + founded: string | null; + geoLocation: string | null; + github: Record | null; + headline: string; + id: string; + identities: string[]; + importHash: string | null; + industry: string; + isTeamOrganization: boolean; + joinedAt: string; + lastActive: string; + lastEnrichedAt: string; + linkedin: Record | null; + location: string; + logo: string; + memberCount: number; + naics: any[] + name: string; + parentUrl: string | null; + phoneNumbers: string[] | null; + profiles: string[]; + revenueRange: string | null; + size: string; + tags: string[] | null; + tenantId: string; + ticker: Record | null; + twitter: Record | null; + type: string; + updatedAt: string; + updatedById: string; + url: string; + website: string; +} diff --git a/frontend/src/modules/report/pages/templates/report-template-page.vue b/frontend/src/modules/report/pages/templates/report-template-page.vue index 7c80f9acdb..b3f2752d23 100644 --- a/frontend/src/modules/report/pages/templates/report-template-page.vue +++ b/frontend/src/modules/report/pages/templates/report-template-page.vue @@ -137,7 +137,7 @@ const initialPlatformValue = { }; const platform = ref(initialPlatformValue); -const teamMembers = ref(false); +const teamOrganizations = ref(false); const teamActivities = ref(false); const currentTemplate = computed(() => templates.find((t) => t.config.nameAsId === report.value?.name)?.config); diff --git a/frontend/src/modules/widget/components/member/widget-active-leaderboard-members.vue b/frontend/src/modules/widget/components/member/widget-active-leaderboard-members.vue index 975336051e..8f94848866 100644 --- a/frontend/src/modules/widget/components/member/widget-active-leaderboard-members.vue +++ b/frontend/src/modules/widget/components/member/widget-active-leaderboard-members.vue @@ -126,7 +126,7 @@ const empty = computed( const getActiveMembers = async ( period = selectedPeriod.value, platforms = props.filters.platform.value, - teamMembers = props.filters.teamMembers, + teamOrganizations = props.filters.teamMembers, ) => { loading.value = true; error.value = false; @@ -224,7 +224,7 @@ onMounted(async () => { // Each time filter changes, query a new response watch( () => [props.filters.platform.value, props.filters.teamMembers], - async ([platforms, teamMembers]) => { + async ([platforms, teamOrganizations]) => { activeMembers.value = await getActiveMembers( selectedPeriod.value, platforms, diff --git a/frontend/src/shared/modules/filters/components/filterTypes/NumberFilter.vue b/frontend/src/shared/modules/filters/components/filterTypes/NumberFilter.vue index 5414fb5a0f..6a0771ab38 100644 --- a/frontend/src/shared/modules/filters/components/filterTypes/NumberFilter.vue +++ b/frontend/src/shared/modules/filters/components/filterTypes/NumberFilter.vue @@ -4,6 +4,7 @@
({ const $v = useVuelidate(rules, form); watch(() => form.value.operator, (operator) => { - console.log(operator); if (operator !== FilterNumberOperator.BETWEEN) { form.value.valueTo = undefined; } else { @@ -128,8 +128,17 @@ watch(() => form.value.operator, (operator) => { }); onMounted(() => { - if (!form.value || Object.keys(form.value).length < 3>) { - form.value = defaultForm; + if (props.forceOperator && props.forceOperator.length > 0) { + form.value = { + ...defaultForm, + ...form.value, + operator: props.forceOperator, + }; + } else { + form.value = { + ...defaultForm, + ...form.value, + }; } }); diff --git a/frontend/src/shared/modules/filters/config/apiFilterRenderer/string.filter.renderer.ts b/frontend/src/shared/modules/filters/config/apiFilterRenderer/string.filter.renderer.ts index 7bb7e637cb..1bd3bd3f41 100644 --- a/frontend/src/shared/modules/filters/config/apiFilterRenderer/string.filter.renderer.ts +++ b/frontend/src/shared/modules/filters/config/apiFilterRenderer/string.filter.renderer.ts @@ -3,13 +3,20 @@ import { FilterStringOperator } from '@/shared/modules/filters/config/constants/ export const stringApiFilterRenderer = (property: string, { include, value, operator }: StringFilterValue): any[] => { // Exception for "not contains" where there isn't a specific operator - const filter = operator === FilterStringOperator.NLIKE ? { - not: { - contains: value, - }, - } : { + let filter: any = { [operator]: value, }; + if (operator === FilterStringOperator.NLIKE) { + filter = { + not: { + like: `%${value}%`, + }, + }; + } else if (operator === FilterStringOperator.LIKE) { + filter = { + [operator]: `%${value}%`, + }; + } return [ { diff --git a/frontend/src/shared/modules/filters/types/filterTypes/NumberFilterConfig.ts b/frontend/src/shared/modules/filters/types/filterTypes/NumberFilterConfig.ts index 8ad52eb5ae..494407ef76 100644 --- a/frontend/src/shared/modules/filters/types/filterTypes/NumberFilterConfig.ts +++ b/frontend/src/shared/modules/filters/types/filterTypes/NumberFilterConfig.ts @@ -4,6 +4,7 @@ import { FilterNumberOperator } from '@/shared/modules/filters/config/constants/ export interface NumberFilterOptions { hideIncludeSwitch?: boolean; + forceOperator?: FilterNumberOperator; } export interface NumberFilterValue { operator: FilterNumberOperator, diff --git a/frontend/src/shared/modules/saved-views/components/SavedViews.vue b/frontend/src/shared/modules/saved-views/components/SavedViews.vue index eb34c5ed96..8e9cc9e857 100644 --- a/frontend/src/shared/modules/saved-views/components/SavedViews.vue +++ b/frontend/src/shared/modules/saved-views/components/SavedViews.vue @@ -69,9 +69,7 @@ const compareFilterToCurrentValues = (filter: FilterObject): boolean => { ...props.modelValue, }; delete currentFilter.pagination; - delete currentFilter.order; delete compareFilter.pagination; - delete compareFilter.order; return JSON.stringify(compareFilter) === JSON.stringify(currentFilter); }; From a0bfa6e8c78a734326618b4366b941443f52ac37 Mon Sep 17 00:00:00 2001 From: Gasper Grom Date: Wed, 7 Jun 2023 17:03:31 +0200 Subject: [PATCH 2/2] Revert name changes --- .../modules/report/pages/templates/report-template-page.vue | 2 +- .../components/member/widget-active-leaderboard-members.vue | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/modules/report/pages/templates/report-template-page.vue b/frontend/src/modules/report/pages/templates/report-template-page.vue index b3f2752d23..7c80f9acdb 100644 --- a/frontend/src/modules/report/pages/templates/report-template-page.vue +++ b/frontend/src/modules/report/pages/templates/report-template-page.vue @@ -137,7 +137,7 @@ const initialPlatformValue = { }; const platform = ref(initialPlatformValue); -const teamOrganizations = ref(false); +const teamMembers = ref(false); const teamActivities = ref(false); const currentTemplate = computed(() => templates.find((t) => t.config.nameAsId === report.value?.name)?.config); diff --git a/frontend/src/modules/widget/components/member/widget-active-leaderboard-members.vue b/frontend/src/modules/widget/components/member/widget-active-leaderboard-members.vue index 8f94848866..975336051e 100644 --- a/frontend/src/modules/widget/components/member/widget-active-leaderboard-members.vue +++ b/frontend/src/modules/widget/components/member/widget-active-leaderboard-members.vue @@ -126,7 +126,7 @@ const empty = computed( const getActiveMembers = async ( period = selectedPeriod.value, platforms = props.filters.platform.value, - teamOrganizations = props.filters.teamMembers, + teamMembers = props.filters.teamMembers, ) => { loading.value = true; error.value = false; @@ -224,7 +224,7 @@ onMounted(async () => { // Each time filter changes, query a new response watch( () => [props.filters.platform.value, props.filters.teamMembers], - async ([platforms, teamOrganizations]) => { + async ([platforms, teamMembers]) => { activeMembers.value = await getActiveMembers( selectedPeriod.value, platforms,