From f69f740bdf53b658814e6c12ed37704287f9783d Mon Sep 17 00:00:00 2001 From: Nicolas Pennec Date: Fri, 15 Sep 2023 16:33:14 +0200 Subject: [PATCH] WIP: [filter] add filter group --- src/App.vue | 14 - .../modals/EditSearchFilterGroupModal.vue | 131 ++++++++ .../modals/EditSearchFilterModal.vue | 59 ++-- src/components/pages/Assets.vue | 22 ++ src/components/widgets/SearchQueryList.vue | 307 +++++++++++++++--- src/locales/en.js | 5 + src/store/api/people.js | 32 +- src/store/modules/assets.js | 52 ++- src/store/modules/user.js | 38 +++ src/store/mutation-types.js | 9 + 10 files changed, 575 insertions(+), 94 deletions(-) create mode 100644 src/components/modals/EditSearchFilterGroupModal.vue diff --git a/src/App.vue b/src/App.vue index c8367af887..059ebc43ec 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1169,20 +1169,6 @@ textarea.input:focus { max-width: 95%; } -.query-list .tag { - margin-right: 1em; - margin-bottom: 0.2em; - border: 1px solid transparent; -} - -.query-list .tag .delete { - transform: rotate(45deg) scale(0.7); -} - -.query-list .tag:hover { - transform: scale(1.1); -} - .fixed-page { position: fixed; left: 0; diff --git a/src/components/modals/EditSearchFilterGroupModal.vue b/src/components/modals/EditSearchFilterGroupModal.vue new file mode 100644 index 0000000000..7da0a6fd1c --- /dev/null +++ b/src/components/modals/EditSearchFilterGroupModal.vue @@ -0,0 +1,131 @@ + + + diff --git a/src/components/modals/EditSearchFilterModal.vue b/src/components/modals/EditSearchFilterModal.vue index a01c286673..d3ba57a401 100644 --- a/src/components/modals/EditSearchFilterModal.vue +++ b/src/components/modals/EditSearchFilterModal.vue @@ -17,17 +17,21 @@ + + @@ -49,14 +53,16 @@ filter label when it's too complex to read or too long. */ import { modalMixin } from '@/components/modals/base_modal' +import ModalFooter from '@/components/modals/ModalFooter' +import Combobox from '@/components/widgets/Combobox' import TextField from '@/components/widgets/TextField' -import ModalFooter from '@/components/modals/ModalFooter' export default { name: 'edit-search-filter-modal', mixins: [modalMixin], components: { + Combobox, ModalFooter, TextField }, @@ -77,31 +83,32 @@ export default { searchQueryToEdit: { type: Object, default: () => {} + }, + groupOptions: { + type: Array, + default: () => [] } }, data() { - if (this.searchQueryToEdit && this.searchQueryToEdit.id) { - return { - form: { - text: this.searchQueryToEdit.text, - task_status_id: this.searchQueryToEdit.task_status_id - } - } - } else { - return { - form: { - text: '', - task_status_id: null - } + return { + form: { + id: null, + name: '', + search_filter_group_id: null, + search_query: '', + task_status_id: null } } }, - computed: {}, - methods: { runConfirmation(event) { + if (!this.form.name.length) { + this.$refs.nameField.focus() + return + } + if (!event || event.keyCode === 13 || !event.keyCode) { this.$emit('confirm', { id: this.searchQueryToEdit.id, @@ -113,15 +120,19 @@ export default { watch: { searchQueryToEdit() { - if (this.searchQueryToEdit && this.searchQueryToEdit.id) { + if (this.searchQueryToEdit?.id) { this.form.id = this.searchQueryToEdit.id this.form.name = this.searchQueryToEdit.name + this.form.search_filter_group_id = + this.searchQueryToEdit.search_filter_group_id this.form.search_query = this.searchQueryToEdit.search_query } else { this.form = { - id: '', + id: null, name: '', - search_query: '' + search_filter_group_id: null, + search_query: '', + task_status_id: null } } }, @@ -136,5 +147,3 @@ export default { } } - - diff --git a/src/components/pages/Assets.vue b/src/components/pages/Assets.vue index 2779c7313f..0cca1855c2 100644 --- a/src/components/pages/Assets.vue +++ b/src/components/pages/Assets.vue @@ -61,8 +61,11 @@
@@ -423,6 +426,7 @@ export default { 'assetListScrollPosition', 'assetsCsvFormData', 'assetSearchText', + 'assetSearchFilterGroups', 'assetSearchQueries', 'assetTypes', 'assetValidationColumns', @@ -532,8 +536,10 @@ export default { 'loadEpisodes', 'newAsset', 'removeAssetSearch', + 'removeAssetSearchFilterGroup', 'restoreAsset', 'saveAssetSearch', + 'saveAssetSearchFilterGroup', 'setLastProductionScreen', 'setAssetSearch', 'setPreview', @@ -819,6 +825,22 @@ export default { }) }, + saveSearchFilterGroup(filterGroup) { + this.saveAssetSearchFilterGroup(filterGroup) + .then(() => {}) + .catch(err => { + if (err) console.error(err) + }) + }, + + removeSearchFilterGroup(filterGroup) { + this.removeAssetSearchFilterGroup(filterGroup) + .then(() => {}) + .catch(err => { + if (err) console.error(err) + }) + }, + removeSearchQuery(searchQuery) { this.removeAssetSearch(searchQuery) .then(() => {}) diff --git a/src/components/widgets/SearchQueryList.vue b/src/components/widgets/SearchQueryList.vue index 22e9c851c1..22896d9b1e 100644 --- a/src/components/widgets/SearchQueryList.vue +++ b/src/components/widgets/SearchQueryList.vue @@ -1,34 +1,90 @@ @@ -38,10 +94,16 @@ * results. It allows to modify each query too. */ import { mapActions } from 'vuex' -import { Edit2Icon } from 'vue-feather-icons' +import { + ChevronDownIcon, + Edit2Icon, + FolderPlusIcon, + Trash2Icon +} from 'vue-feather-icons' import { sortByName } from '@/lib/sorting' import EditSearchFilterModal from '@/components/modals/EditSearchFilterModal' +import EditSearchFilterGroupModal from '@/components/modals/EditSearchFilterGroupModal' export default { name: 'search-query-list', @@ -49,41 +111,76 @@ export default { queries: { type: Array, default: () => [] + }, + groups: { + type: Array, + default: () => [] } }, components: { + ChevronDownIcon, Edit2Icon, - EditSearchFilterModal + EditSearchFilterModal, + EditSearchFilterGroupModal, + FolderPlusIcon, + Trash2Icon }, data() { return { + groupToEdit: {}, searchQueryToEdit: {}, errors: { - edit: false + edit: false, + group: false }, loading: { - edit: false + edit: false, + group: false }, modals: { - edit: false - } + edit: false, + group: false + }, + toggleIndex: null } }, computed: { - userFilters() { + sortedFilters() { return sortByName([...this.queries]) + }, + userFilters() { + return this.sortedFilters.filter(query => !query.search_filter_group_id) + }, + userFilterGroups() { + return sortByName([...this.groups]).map(group => { + return { + ...group, + queries: this.sortedFilters.filter( + query => query.search_filter_group_id === group.id + ) + } + }) + }, + groupOptions() { + return [ + { label: '', value: '' }, + ...this.userFilterGroups.map(group => ({ + label: group.name, + value: group.id + })) + ] } }, methods: { - ...mapActions(['updateSearchFilter']), - - changeSearch(event, searchQuery) { - const isButtonClicked = ['delete flexrow', 'edit flexrow'].includes( - event.target.className - ) - if (!isButtonClicked) { - this.$emit('change-search', searchQuery) - } + ...mapActions(['updateSearchFilter', 'updateSearchFilterGroup']), + + changeSearch(searchQuery) { + this.$emit('change-search', searchQuery) + }, + + editGroup(group) { + this.groupToEdit = group + this.modals.group = true }, editSearch(searchQuery) { @@ -91,69 +188,175 @@ export default { this.modals.edit = true }, - confirmEditSearch(searchFilter) { - this.loading.edit = true - this.updateSearchFilter(searchFilter) - .then(() => { - this.loading.edit = false - this.modals.edit = false - }) - .catch(err => { - console.error(err) - this.loading.edit = false - this.errors.edit = true - }) + async confirmEditFilterGroup(filterGroup) { + if (!filterGroup.id) { + this.$emit('create-group', filterGroup) + // TODO: fix modal close from outsied + // this.modals.group = false + return + } + + try { + this.loading.group = true + await this.updateSearchFilterGroup(filterGroup) + this.modals.group = false + } catch (err) { + console.error(err) + this.errors.group = true + } finally { + this.loading.group = false + } + }, + + async confirmEditSearch(searchFilter) { + try { + this.loading.edit = true + await this.updateSearchFilter(searchFilter) + this.modals.edit = false + } catch (err) { + console.error(err) + this.errors.edit = true + } finally { + this.loading.edit = false + } + }, + + openFilterGroupModal() { + this.modals.group = true }, removeSearch(searchQuery) { this.$emit('remove-search', searchQuery) + }, + + removeGroup(group) { + this.$emit('remove-group', group) + }, + + toggleFilterGroup(index) { + this.toggleIndex = this.toggleIndex !== index ? index : null } } } - diff --git a/src/locales/en.js b/src/locales/en.js index fc131f71d3..6042b49011 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -391,6 +391,7 @@ export default { cancel: 'Cancel', clear_selection: 'Clear current selection', close: 'Close', + color: 'Color', column_visibility: 'Visible columns', confirmation: 'Confirm', confirmation_and_stay: 'Confirm and stay', @@ -410,6 +411,10 @@ export default { estimation: 'Estimation', estimation_short: 'Est.', files_selected: 'files selected', + filter_group: 'Filter Group', + filter_group_add: 'Add A Filter Group', + filter_group_edit: 'Edit A Filter Group', + filter_group_error: 'An error occurred while saving this filter group.', for: 'For', fps: 'FPS', frames: 'Frames', diff --git a/src/store/api/people.js b/src/store/api/people.js index 5ae02867fa..3f216426ce 100644 --- a/src/store/api/people.js +++ b/src/store/api/people.js @@ -231,6 +231,33 @@ export default { client.get(`/api/data/persons/${personId}/done-tasks`, callback) }, + getUserSearchFilterGroups() { + return client.pget('/api/data/user/filter-groups') + }, + + createFilterGroup(listType, name, color, productionId, entityType) { + const data = { + list_type: listType, + name, + color, + project_id: productionId, + entity_type: entityType + } + return client.ppost('/api/data/user/filter-groups', data) + }, + + updateFilterGroup(filterGroup) { + const data = { + name: filterGroup.name, + color: filterGroup.color + } + return client.pput(`/api/data/user/filter-groups/${filterGroup.id}`, data) + }, + + removeFilterGroup(filterGroup) { + return client.pdel(`/api/data/user/filter-groups/${filterGroup.id}`) + }, + getUserSearchFilters(callback) { client.get('/api/data/user/filters', callback) }, @@ -238,7 +265,8 @@ export default { updateFilter(searchFilter) { const data = { name: searchFilter.name, - search_query: searchFilter.search_query + search_query: searchFilter.search_query, + search_filter_group_id: searchFilter.search_filter_group_id } return client.pput(`/api/data/user/filters/${searchFilter.id}`, data) }, @@ -254,7 +282,7 @@ export default { return client.ppost('/api/data/user/filters', data) }, - removeFilter(searchFilter, callback) { + removeFilter(searchFilter) { return client.pdel(`/api/data/user/filters/${searchFilter.id}`) }, diff --git a/src/store/modules/assets.js b/src/store/modules/assets.js index 58bb2af7d6..c67a2777ec 100644 --- a/src/store/modules/assets.js +++ b/src/store/modules/assets.js @@ -64,6 +64,8 @@ import { SAVE_ASSET_SEARCH_END, REMOVE_ASSET_SEARCH_END, SET_ASSET_TYPE_SEARCH, + SAVE_ASSET_SEARCH_FILTER_GROUP_END, + REMOVE_ASSET_SEARCH_FILTER_GROUP_END, COMPUTE_ASSET_TYPE_STATS, UPDATE_METADATA_DESCRIPTOR_END, SET_CURRENT_EPISODE, @@ -271,6 +273,7 @@ const initialState = { assetSearchText: '', assetSelectionGrid: {}, assetSearchQueries: [], + assetSearchFilterGroups: [], assetSorting: [], displayedAssetTypes: [], @@ -302,6 +305,7 @@ const getters = { assetMap: state => state.assetMap, assetSearchText: state => state.assetSearchText, assetSearchQueries: state => state.assetSearchQueries, + assetSearchFilterGroups: state => state.assetSearchFilterGroups, assetSelectionGrid: state => state.assetSelectionGrid, assetValidationColumns: state => state.assetValidationColumns, @@ -350,6 +354,7 @@ const actions = { let episode = rootGetters.currentEpisode const isTVShow = rootGetters.isTVShow const userFilters = rootGetters.userFilters + const userFilterGroups = rootGetters.userFilterGroups const personMap = rootGetters.personMap const taskTypeMap = rootGetters.taskTypeMap const taskMap = rootGetters.taskMap @@ -382,6 +387,7 @@ const actions = { production, assets, userFilters, + userFilterGroups, personMap, taskMap, taskTypeMap @@ -558,6 +564,39 @@ const actions = { } }, + saveAssetSearchFilterGroup({ commit, state, rootGetters }, filterGroup) { + // const groupExist = state.assetSearchFilterGroups.some( + // query => query.name === filterGroup.name + // ) + // if (groupExist) { + // return + // } + + const production = rootGetters.currentProduction + return peopleApi + .createFilterGroup( + 'asset', + filterGroup.name, + filterGroup.color, + production.id, + null + ) + .then(filterGroup => { + // commit(SAVE_ASSET_SEARCH_FILTER_GROUP_END, { filterGroup, production }) + return filterGroup + }) + // } else { + // Promise.resolve() + // } + }, + + removeAssetSearchFilterGroup({ commit, rootGetters }, filterGroup) { + // const production = rootGetters.currentProduction + return peopleApi.removeFilterGroup(filterGroup).then(() => { + // commit(REMOVE_ASSET_SEARCH_FILTER_GROUP_END, { filterGroup, production }) + }) + }, + removeAssetSearch({ commit, rootGetters }, searchQuery) { const production = rootGetters.currentProduction return peopleApi.removeFilter(searchQuery).then(() => { @@ -754,7 +793,15 @@ const mutations = { [LOAD_ASSETS_END]( state, - { production, assets, userFilters, personMap, taskMap, taskTypeMap } + { + production, + assets, + userFilters, + userFilterGroups, + personMap, + taskMap, + taskTypeMap + } ) { const validationColumns = {} const assetTypeMap = new Map() @@ -814,6 +861,9 @@ const mutations = { const maxY = state.nbValidationColumns state.assetSelectionGrid = buildSelectionGrid(maxX, maxY) + state.assetSearchFilterGroups = + userFilterGroups.asset?.[production.id] || [] + if (userFilters.asset && userFilters.asset[production.id]) { state.assetSearchQueries = userFilters.asset[production.id] } else { diff --git a/src/store/modules/user.js b/src/store/modules/user.js index 2682456496..4ad9080baa 100644 --- a/src/store/modules/user.js +++ b/src/store/modules/user.js @@ -43,6 +43,9 @@ import { LOAD_USER_FILTERS_END, LOAD_USER_FILTERS_ERROR, UPDATE_USER_FILTER, + LOAD_USER_FILTER_GROUPS_END, + LOAD_USER_FILTER_GROUPS_ERROR, + UPDATE_USER_FILTER_GROUP, SAVE_TODO_SEARCH_END, REMOVE_TODO_SEARCH_END, ADD_SELECTED_TASK, @@ -109,6 +112,7 @@ const initialState = { todoSelectionGrid: {}, todoSearchQueries: [], userFilters: {}, + userFilterGroups: {}, todoListScrollPosition: 0, timeSpentMap: {}, @@ -144,6 +148,7 @@ const getters = { todoSelectionGrid: state => state.todoSelectionGrid, todoSearchQueries: state => state.todoSearchQueries, userFilters: state => state.userFilters, + userFilterGroups: state => state.userFilterGroups, isTodosLoading: state => state.isTodosLoading, isTodosLoadingError: state => state.isTodosLoadingError, todoListScrollPosition: state => state.todoListScrollPosition, @@ -271,6 +276,7 @@ const actions = { loadTodos({ commit, state, rootGetters }, { callback, forced, date }) { const userFilters = rootGetters.userFilters + // const userFilterGroups = rootGetters.userFilterGroups // TODO: useful to handle this case ??? const taskTypeMap = rootGetters.taskTypeMap if (state.todos.length === 0 || forced) { @@ -334,6 +340,11 @@ const actions = { commit(SET_TODOS_SEARCH, searchText) }, + updateSearchFilterGroup({ commit }, searchFilterGroup) { + commit(UPDATE_USER_FILTER_GROUP, searchFilterGroup) + return peopleApi.updateFilterGroup(searchFilterGroup) + }, + updateSearchFilter({ commit }, searchFilter) { commit(UPDATE_USER_FILTER, searchFilter) return peopleApi.updateFilter(searchFilter) @@ -345,6 +356,13 @@ const actions = { else commit(LOAD_USER_FILTERS_END, searchFilters) callback(err) }) + // return peopleApi.getUserSearchFilters() + // .then((searchFilters) => { + // commit(LOAD_USER_FILTERS_END, searchFilters); + // }) + // .catch((err) => { + // commit(LOAD_USER_FILTERS_ERROR); + // }) }, saveTodoSearch({ commit, rootGetters }, searchQuery) { @@ -378,6 +396,7 @@ const actions = { loadContext({ commit, rootGetters }, callback) { return peopleApi.getContext().then(context => { commit(LOAD_USER_FILTERS_END, context.search_filters) + commit(LOAD_USER_FILTER_GROUPS_END, context.search_filter_groups) commit(LOAD_PRODUCTION_STATUS_END, context.project_status) commit(LOAD_DEPARTMENTS_END, context.departments) commit(LOAD_TASK_STATUSES_END, context.task_status) @@ -701,6 +720,25 @@ const mutations = { }) }, + [LOAD_USER_FILTER_GROUPS_ERROR](state) {}, + [LOAD_USER_FILTER_GROUPS_END](state, userFilterGroups) { + state.userFilterGroups = userFilterGroups + }, + [UPDATE_USER_FILTER_GROUP](state, userFilterGroup) { + debugger + // TODO: implement this for group update + + // Object.keys(state.userFilters).forEach(typeName => { + // Object.keys(state.userFilters[typeName]).forEach(projectId => { + // const projectFilters = state.userFilters[typeName][projectId] + // const filter = projectFilters.find(f => f.id === userFilter.id) + // if (filter) { + // Object.assign(filter, userFilter) + // } + // }) + // }) + }, + [SET_TIME_SPENT](state, timeSpent) { if (state.user.id === timeSpent.person_id) { state.timeSpentMap[timeSpent.task_id] = timeSpent diff --git a/src/store/mutation-types.js b/src/store/mutation-types.js index 3bc8469311..f9a0fc381c 100644 --- a/src/store/mutation-types.js +++ b/src/store/mutation-types.js @@ -93,6 +93,10 @@ export const SET_TODOS_SEARCH = 'SET_TODOS_SEARCH' export const LOAD_USER_FILTERS_END = 'LOAD_USER_FILTERS_END' export const LOAD_USER_FILTERS_ERROR = 'LOAD_USER_FILTERS_ERROR' export const UPDATE_USER_FILTER = 'UPDATE_USER_FILTER' +export const LOAD_USER_FILTER_GROUPS_END = 'LOAD_USER_FILTER_GROUPS_END' +export const LOAD_USER_FILTER_GROUPS_ERROR = 'LOAD_USER_FILTER_GROUPS_ERROR' +export const UPDATE_USER_FILTER_GROUP = 'UPDATE_USER_FILTER_GROUP' + export const SET_TODO_LIST_SCROLL_POSITION = 'SET_TODO_LIST_SCROLL_POSITION' // Tasks @@ -337,6 +341,11 @@ export const SAVE_ASSET_SEARCH_END = 'SAVE_ASSET_SEARCH_END' export const REMOVE_ASSET_SEARCH_END = 'REMOVE_ASSET_SEARCH_END' export const DISPLAY_MORE_ASSETS = 'DISPLAY_MORE_ASSETS' +export const SAVE_ASSET_SEARCH_FILTER_GROUP_END = + 'SAVE_ASSET_SEARCH_FILTER_GROUP_END' +export const REMOVE_ASSET_SEARCH_FILTER_GROUP_END = + 'REMOVE_ASSET_SEARCH_FILTER_GROUP_END' + export const COMPUTE_ASSET_TYPE_STATS = 'COMPUTE_ASSET_TYPE_STATS' export const SET_ASSET_TYPE_SEARCH = 'SET_ASSET_TYPE_SEARCH'