diff --git a/src/components/Grid/GridView.jsx b/src/components/Grid/GridView.jsx index ede933a73..d555490f0 100644 --- a/src/components/Grid/GridView.jsx +++ b/src/components/Grid/GridView.jsx @@ -35,20 +35,17 @@ const GridView = props => { ) : (
- {resultSet.length ? ( -
-
- - {resultSet.map(renderItem)} -
- -
- ) : ( -
- {/* TODO replace this with proper style */} -


No results



+
+
+ + {resultSet.length ? ( + resultSet.map(renderItem) + ) : ( +


No results



+ )}
- )} + +
) ) @@ -77,6 +74,15 @@ const GridView = props => { > {[...resultSet, ...placeholders].map(renderItem)} + {totalCount === 0 &&
+
+ No results found based on current search criteria.
Please modify your search criteria and/or search across all projects by selecting the " + { applyFilters({status: null }) }} className="tc-btn-all-projects" > + All Projects + + " filter. +
+
}
@@ -94,20 +100,6 @@ const GridView = props => { ) } - if (totalCount === 0) { - return ( -
-
- No results found based on current search criteria.
Please modify your search criteria and/or search across all projects by selecting the " - { applyFilters({status: null }) }} className="tc-btn-all-projects" > - All Projects - - " filter. -
-
- ) - } - return (
{infiniteScroll ? renderGridWithInfiniteScroll() : renderGridWithPagination()} diff --git a/src/components/TopBar/Filters.js b/src/components/TopBar/Filters.js deleted file mode 100644 index 81033920f..000000000 --- a/src/components/TopBar/Filters.js +++ /dev/null @@ -1,70 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import _ from 'lodash' -import Select from '../../components/Select/Select' - -const Filters = ({ criteria, applyFilters, projectTypes }) => { - const types = _.map(projectTypes, type => { - return { value: type.key, label: type.displayName } - }) - - // TODO add segments list - // const segments = _.map([], segment => { - // return { value: segment.id, label: segment.value } - // }) - - return ( -
-

Filters

-
-
- -
- applyFilters({ segment: selectedOptions })} - /> -
-
*/} -
- -
- ) -} - -Filters.propTypes = { - criteria: PropTypes.object.isRequired, - applyFilters: PropTypes.func.isRequired, - projectTypes: PropTypes.arrayOf(PropTypes.shape({ - key: PropTypes.string.isRequired, - displayName: PropTypes.string.isRequired, - })) -} - -export default Filters diff --git a/src/components/TopBar/ProjectsToolBar.js b/src/components/TopBar/ProjectsToolBar.js index 4a2d860d1..4a84d2683 100644 --- a/src/components/TopBar/ProjectsToolBar.js +++ b/src/components/TopBar/ProjectsToolBar.js @@ -5,17 +5,13 @@ import PropTypes from 'prop-types' import querystring from 'query-string' import { withRouter, Prompt } from 'react-router-dom' import { connect } from 'react-redux' -import cn from 'classnames' import _ from 'lodash' import SearchBar from 'appirio-tech-react-components/components/SearchBar/SearchBar' import MenuBar from 'appirio-tech-react-components/components/MenuBar/MenuBar' -import Filters from './Filters' import NotificationsDropdown from '../NotificationsDropdown/NotificationsDropdownContainer' import NewProjectNavLink from './NewProjectNavLink' import MobileMenu from '../MobileMenu/MobileMenu' import MobileMenuToggle from '../MobileMenu/MobileMenuToggle' -import SearchFilter from '../../assets/icons/ui-filters.svg' -import SearchIcon from '../../assets/icons/ui-16px-1_zoom.svg' import { projectSuggestions, loadProjects, setInfiniteAutoload } from '../../projects/actions/loadProjects' import { loadProjectsMetadata } from '../../actions/templates' import { getNewProjectLink } from '../../helpers/projectHelper' @@ -26,17 +22,13 @@ class ProjectsToolBar extends Component { super(props) this.state = { errorCreatingProject: false, - isFilterVisible: false, isMobileMenuOpen: false, isMobileSearchVisible: false } - this.state.isFilterVisible = sessionStorage.getItem('isFilterVisible') === 'true' this.applyFilters = this.applyFilters.bind(this) - this.toggleFilter = this.toggleFilter.bind(this) this.handleTermChange = this.handleTermChange.bind(this) this.handleSearch = this.handleSearch.bind(this) this.toggleMobileMenu = this.toggleMobileMenu.bind(this) - this.toggleMobileSearch = this.toggleMobileSearch.bind(this) this.onLeave = this.onLeave.bind(this) } @@ -74,10 +66,6 @@ class ProjectsToolBar extends Component { } componentDidMount() { - const contentDiv = document.getElementById('wrapper-main') - if (this.state.isFilterVisible) { - contentDiv.classList.add('with-filters') - } // sets window unload hook to show unsaved changes alert and persist incomplete project window.addEventListener('beforeunload', this.onLeave) } @@ -125,38 +113,10 @@ class ProjectsToolBar extends Component { this.routeWithParams(criteria) } - toggleFilter() { - const {isFilterVisible, isMobileSearchVisible} = this.state - const contentDiv = document.getElementById('wrapper-main') - this.setState({isFilterVisible: !isFilterVisible}, () => { - sessionStorage.setItem('isFilterVisible', (!isFilterVisible).toString()) - if (this.state.isFilterVisible) { - contentDiv.classList.add('with-filters') - } else { - contentDiv.classList.remove('with-filters') - } - }) - // if open filters, close search panel on mobile - if (!isFilterVisible && isMobileSearchVisible) { - this.toggleMobileSearch() - } - } - toggleMobileMenu() { this.setState({ isMobileMenuOpen: !this.state.isMobileMenuOpen }) } - toggleMobileSearch() { - const { isFilterVisible, isMobileSearchVisible } = this.state - - // if open mobile search and filter is visible, then close filter - if (!isMobileSearchVisible && isFilterVisible) { - this.toggleFilter() - } - - this.setState({ isMobileSearchVisible: !isMobileSearchVisible }) - } - routeWithParams(criteria) { // because criteria is changed disable infinite autoload this.props.setInfiniteAutoload(false) @@ -171,7 +131,7 @@ class ProjectsToolBar extends Component { shouldComponentUpdate(nextProps, nextState) { const { user, criteria, creatingProject, projectCreationError, searchTermTag, projectTypes } = this.props - const { errorCreatingProject, isFilterVisible, isMobileMenuOpen, isMobileSearchVisible } = this.state + const { errorCreatingProject, isMobileMenuOpen, isMobileSearchVisible } = this.state return (nextProps.user || {}).handle !== (user || {}).handle || (nextProps.user || {}).photoURL !== (this.props.user || {}).photoURL || JSON.stringify(nextProps.criteria) !== JSON.stringify(criteria) @@ -180,24 +140,15 @@ class ProjectsToolBar extends Component { || nextProps.searchTermTag !== searchTermTag || !!nextProps.projectTypes && !projectTypes || nextState.errorCreatingProject !== errorCreatingProject - || nextState.isFilterVisible !== isFilterVisible || nextState.isMobileMenuOpen !== isMobileMenuOpen || nextState.isMobileSearchVisible !== isMobileSearchVisible } render() { - const { renderLogoSection, userMenu, userRoles, criteria, isPowerUser, user, mobileMenu, location, projectTypes, orgConfig } = this.props - const { isFilterVisible, isMobileMenuOpen, isMobileSearchVisible } = this.state + const { renderLogoSection, userMenu, userRoles, isPowerUser, user, mobileMenu, location, orgConfig } = this.props + const { isMobileMenuOpen, isMobileSearchVisible } = this.state const isLoggedIn = !!(userRoles && userRoles.length) - let excludedFiltersCount = 1 // 1 for default sort criteria - if (criteria.memberOnly) { - // https://github.com/appirio-tech/connect-app/issues/1319 - // The switch should not count as a filter in the menu! - excludedFiltersCount++ - } - // Ignore status from filters count - const noOfFilters = _.keys(_.omit(criteria, ['status', 'keyword'])).length - excludedFiltersCount const onLeaveMessage = this.onLeave() || '' const primaryNavigationItems = [ @@ -240,20 +191,6 @@ class ProjectsToolBar extends Component { onSearch={ this.handleSearch } onClearSearch={ this.handleSearch } /> -
- - { !!projectTypes && - Filters { noOfFilters > 0 && { noOfFilters } } - } -
}
@@ -277,15 +214,6 @@ class ProjectsToolBar extends Component { />
} - { !!projectTypes && isFilterVisible && isLoggedIn && -
- -
- } {isMobileMenuOpen && } ) diff --git a/src/components/TopBar/ProjectsToolBar.scss b/src/components/TopBar/ProjectsToolBar.scss index 1f8f15286..c74eadc23 100644 --- a/src/components/TopBar/ProjectsToolBar.scss +++ b/src/components/TopBar/ProjectsToolBar.scss @@ -172,104 +172,6 @@ } } - .search-filter { - width: 97px; - margin-left: 2 * $base-unit; - - @media screen and (max-width: $screen-md - 1px) { - border: 1px solid $tc-gray-80; - border-radius: 2px; - display: flex; - height: 32px; - margin: 0 auto; - width: auto; - } - - .tc-btn { - display: flex; - align-items: center; - // justify-content: center; - padding: 0 2 * $base-unit 0 10px; - text-align: left; - color: $tc-gray-40; - background-color: transparent; - border-radius: 2px; - border: 0; - font-size: 12px; - position: relative; - - @media screen and (max-width: $screen-md - 1px) { - text-align: center; - width: 46px; - - &:first-child { - border-right: 1px solid $tc-gray-80; - } - - > svg { - margin: 0 auto; - } - } - - &.mobile-search-toggle { - display: none; - - @media screen and (max-width: $screen-md - 1px) { - display: flex; - } - - > svg { - fill: $tc-gray-50; - } - - &.active > svg { - fill: $tc-white; - } - } - - svg.icon-search-filter { - margin-right: 10px; - - @media screen and (max-width: $screen-md - 1px) { - margin-right: auto; - } - } - - &.active { - background: $tc-gray-80; - box-shadow: inset 0px 1px 3px 0px $tc-gray-80; - - svg.icon-search-filter g { - fill: $tc-white; - } - } - } - - .filter-text { - @media screen and (max-width: $screen-md - 1px) { - display: none; - } - } - - .filter-indicator { - width: 15px; - height: 15px; - margin-left: $base-unit; - background-color: $tc-dark-blue; - border-radius: 26px; - color: $tc-white; - display: flex; - justify-content: center; - align-items: center; - - @media screen and (max-width: $screen-md - 1px) { - right: -8px; - position: absolute; - top: 3px; - } - } - } - /* .bar__search */ .bar__search { align-items: center; diff --git a/src/projects/list/components/Projects/ProjectListFilterColHeader.jsx b/src/projects/list/components/Projects/ProjectListFilterColHeader.jsx new file mode 100644 index 000000000..5e060984d --- /dev/null +++ b/src/projects/list/components/Projects/ProjectListFilterColHeader.jsx @@ -0,0 +1,61 @@ +import React from 'react' +import PropTypes from 'prop-types' +import _ from 'lodash' +import Dropdown from 'appirio-tech-react-components/components/Dropdown/Dropdown' +import IconCarretDownNormal from '../../../../assets/icons/arrow-6px-carret-down-normal.svg' + +class ProjectListFilterColHeader extends React.Component { + + constructor(props) { + super(props) + this.state = { value: '' } + this.setFilter = this.setFilter.bind(this) + this.onChange = this.onChange.bind(this) + // debounced setFilter function to prevent polluting server with requests + this.debouncedSetFilterCheck = _.debounce(this.setFilter, 500) + } + + setFilter(newfilter) { + this.props.setFilter(this.props.filterName, newfilter) + } + + onChange(event) { + this.setState({ value: event.target.value }, () => { + this.debouncedSetFilterCheck(this.state.value) + }) + } + + componentDidMount() { + this.setState({ + value: this.props.value || '' + }) + } + + render() { + const { + title + } = this.props + + return ( +
+ + + {title} + + +
+ +
+
+
+ ) + } +} + +ProjectListFilterColHeader.propTypes = { + title: PropTypes.string, + filterName: PropTypes.string, + setFilter: PropTypes.func, + value: PropTypes.string +} +export default ProjectListFilterColHeader diff --git a/src/projects/list/components/Projects/Projects.jsx b/src/projects/list/components/Projects/Projects.jsx index 73e4256e8..cea4a57b5 100755 --- a/src/projects/list/components/Projects/Projects.jsx +++ b/src/projects/list/components/Projects/Projects.jsx @@ -40,6 +40,7 @@ class Projects extends Component { this.onPageChange = this.onPageChange.bind(this) this.applyFilters = this.applyFilters.bind(this) this.applySearchFilter = this.applySearchFilter.bind(this) + this.setFilter = this.setFilter.bind(this) this.changeView = this.changeView.bind(this) this.init = this.init.bind(this) this.removeScrollPosition = this.removeScrollPosition.bind(this) @@ -164,6 +165,19 @@ class Projects extends Component { this.routeWithParams(criteria) } + setFilter(name, filter) { + let criteria = _.assign({}, this.props.criteria) + if(filter && filter !== '') { + const temp = {} + temp[`${name}`] = `*${filter}*` + criteria = _.assign({}, criteria, temp) + } else if(_.has(criteria, name)){ + criteria = _.omit(criteria, name) + } + + this.props.loadProjects(criteria) + } + changeView(view) { this.setState({selectedView : view}) } @@ -198,6 +212,8 @@ class Projects extends Component { onChangeStatus={this.onChangeStatus} projectsStatus={getStatusCriteriaText(criteria)} newProjectLink={getNewProjectLink(orgConfig)} + setFilter={this.setFilter} + criteria={criteria} /> ) const cardView = ( diff --git a/src/projects/list/components/Projects/ProjectsGridView.jsx b/src/projects/list/components/Projects/ProjectsGridView.jsx index 9744225e7..3640e15cf 100644 --- a/src/projects/list/components/Projects/ProjectsGridView.jsx +++ b/src/projects/list/components/Projects/ProjectsGridView.jsx @@ -8,6 +8,7 @@ import { filterNotificationsByProjectId, } from '../../../../routes/notifications/helpers/notifications' import ProjectListTimeSortColHeader from './ProjectListTimeSortColHeader' +import ProjectListFilterColHeader from './ProjectListFilterColHeader' import GridView from '../../../../components/Grid/GridView' import UserTooltip from '../../../../components/User/UserTooltip' import { PROJECTS_LIST_PER_PAGE, SORT_OPTIONS, PROJECT_STATUS_COMPLETED, DATE_TO_USER_FIELD_MAP } from '../../../../config/constants' @@ -26,7 +27,7 @@ const EnhancedProjectStatus = editableProjectStatus(ProjectStatus) const ProjectsGridView = props => { const { projects, members, totalCount, criteria, pageNum, sortHandler, currentUser, onPageChange, error, isLoading, infiniteAutoload, setInfiniteAutoload, projectsStatus, onChangeStatus, - applyFilters, projectTemplates, notifications, newProjectLink } = props + applyFilters, projectTemplates, notifications, newProjectLink, setFilter } = props const currentSortField = _.get(criteria, 'sort', '') // This 'little' array is the heart of the list component. @@ -70,12 +71,11 @@ const ProjectsGridView = props => { } }, { id: 'projects', - headerLabel: 'Project', + headerLabel: , classes: 'item-projects', sortable: false, renderText: item => { const url = `/projects/${item.id}` - const code = _.get(item, 'details.utm.code', '') // project notifications const notReadNotifications = filterReadNotifications(notifications) const unreadProjectUpdate = filterNotificationsByProjectId(notReadNotifications, item.id) @@ -85,7 +85,6 @@ const ProjectsGridView = props => { {(recentlyCreated || unreadProjectUpdate.length > 0) && }
{_.unescape(item.name)} - {code && { applyFilters({ keyword: code }) }} dangerouslySetInnerHTML={{ __html: code }} />}
{ ) } + }, { + id: 'reference', + headerLabel: , + sortable: false, + classes: 'item-ref-code', + renderText: item => { + const code = _.get(item, 'details.utm.code', '') + return ( +
+ {code} +
+ ) + } }, { id: 'updatedAt', headerLabel: , @@ -121,7 +133,7 @@ const ProjectsGridView = props => { } }, { id: 'customer', - headerLabel: 'Customer', + headerLabel: , sortable: false, classes: 'item-customer', renderText: item => { @@ -166,7 +178,7 @@ const ProjectsGridView = props => { } }, { id: 'managers', - headerLabel: 'Managers', + headerLabel: , sortable: false, classes: 'item-manager', renderText: item => { @@ -256,6 +268,7 @@ ProjectsGridView.propTypes = { pageNum: PropTypes.number.isRequired, criteria: PropTypes.object.isRequired, projectTemplates: PropTypes.array.isRequired, + setFilter: PropTypes.func, } export default ProjectsGridView diff --git a/src/projects/list/components/Projects/ProjectsGridView.scss b/src/projects/list/components/Projects/ProjectsGridView.scss index a87fce816..a8b1382d4 100644 --- a/src/projects/list/components/Projects/ProjectsGridView.scss +++ b/src/projects/list/components/Projects/ProjectsGridView.scss @@ -21,7 +21,11 @@ $screen-one-column: 720px; .item-projects { flex: 8 1 505px; min-width: 505px; - overflow: hidden; + } + + .item-ref-code { + flex: 1 3 142px; + min-width: 142px; } .item-status-date { @@ -32,7 +36,6 @@ $screen-one-column: 720px; .item-customer { flex: 2 2 150px; min-width: 150px; - .spacing { width: 100%; } @@ -115,6 +118,17 @@ $screen-one-column: 720px; } } } + + .filter-drop-down { + a.dropdown-menu-header.txt-link { + font-size: 13px; + color: $tc-gray-50; + + &:focus { + color: $tc-dark-blue; + } + } + } } } } @@ -179,19 +193,6 @@ $screen-one-column: 720px; color: $tc-black; font-size: 15px; } - - .item-ref-code { - flex: none; - justify-content: flex-end; - background: $tc-gray-10; - @include roboto-medium; - font-size: 13px; - color: $tc-gray-50; - border-radius: 2px; - line-height: 20px; - padding: 0px 2 * $base-unit; - cursor: pointer; - } } .project-description { @@ -590,4 +591,52 @@ $screen-one-column: 720px; } } } + + .filter-drop-down { + position: relative; + display: inline-block; + margin-left: 0px; + + a { + outline: 0; + } + + .txt-link { + position: relative; + padding-right: 16px; + display: block; + z-index: 3; + + &:focus { + color: $tc-dark-blue; + outline: 0; + z-index: 3; + } + + .icon-carret-down-normal { + font-size: 0; + line-height: 0; + width: 10px; + height: 6px; + content: ""; + display: block; + position: absolute; + top: 5px; + right: 0; + } + } + + .down-layer { + background: $tc-white; + border-radius: 5px; + min-width: 130px; + min-height: 45px; + position: absolute; + top: -18px; + left: -20px; + box-shadow: 0 0 10px rgba($tc-black, 0.2); + z-index: 3; + padding: 32px 5px 10px; + } + } }