Second pass - mostly everything user-facing is done
Still needs: - Tests - Potentially negating other filters - Potentially some code cleanup
This commit is contained in:
parent
cb7b5b9fbd
commit
007d44df38
|
|
@ -102,6 +102,9 @@ class Filters extends React.Component {
|
|||
};
|
||||
|
||||
filterText(key, value, query) {
|
||||
const negated = value[0] == '!' && ['page', 'entry_page', 'exit_page'].includes(key)
|
||||
value = negated ? value.slice(1) : value
|
||||
|
||||
if (key === "goal") {
|
||||
return <span className="inline-block max-w-2xs md:max-w-xs truncate">Completed goal <b>{value}</b></span>
|
||||
}
|
||||
|
|
@ -148,13 +151,13 @@ class Filters extends React.Component {
|
|||
return <span className="inline-block max-w-2xs md:max-w-xs truncate">Country: <b>{selectedCountry.properties.name}</b></span>
|
||||
}
|
||||
if (key === "page") {
|
||||
return <span className="inline-block max-w-2xs md:max-w-xs truncate">Page: <b>{value}</b></span>
|
||||
return <span className="inline-block max-w-2xs md:max-w-xs truncate">Page{negated ? ' (not)' : ''}: <b>{value}</b></span>
|
||||
}
|
||||
if (key === "entry_page") {
|
||||
return <span className="inline-block max-w-2xs md:max-w-xs truncate">Entry Page: <b>{value}</b></span>
|
||||
return <span className="inline-block max-w-2xs md:max-w-xs truncate">Entry Page{negated ? ' (not)' : ''}: <b>{value}</b></span>
|
||||
}
|
||||
if (key === "exit_page") {
|
||||
return <span className="inline-block max-w-2xs md:max-w-xs truncate">Exit Page: <b>{value}</b></span>
|
||||
return <span className="inline-block max-w-2xs md:max-w-xs truncate">Exit Page{negated ? ' (not)' : ''}: <b>{value}</b></span>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +177,13 @@ class Filters extends React.Component {
|
|||
return (
|
||||
<div className="px-4 sm:py-2 py-3 md:text-sm leading-tight flex items-center justify-between" key={key + value}>
|
||||
{this.filterText(key, value, query)}
|
||||
<b className="ml-1 cursor-pointer hover:text-indigo-700 dark:hover:text-indigo-500" onClick={() => this.removeFilter(key, history, query)}>✕</b>
|
||||
<span className="ml-2 flex items-center">
|
||||
{!['goal', 'props'].includes(key) &&
|
||||
<Link to={{ pathname: `/${encodeURIComponent(this.props.site.domain)}/filter/${key}`, search: window.location.search }}>
|
||||
<svg className="cursor-pointer hover:text-indigo-700 dark:hover:text-indigo-500 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
|
||||
</Link>}
|
||||
<b className="ml-2 cursor-pointer hover:text-indigo-700 dark:hover:text-indigo-500" onClick={() => this.removeFilter(key, history, query)}>✕</b>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -182,7 +191,14 @@ class Filters extends React.Component {
|
|||
renderListFilter(history, [key, value], query) {
|
||||
return (
|
||||
<span key={key} title={value} className="flex bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 shadow text-sm rounded py-2 px-3 mr-2">
|
||||
{this.filterText(key, value, query)} <b className="ml-1 cursor-pointer hover:text-indigo-500" onClick={() => this.removeFilter(key, history, query)}>✕</b>
|
||||
{this.filterText(key, value, query)}
|
||||
<span className="ml-2 flex items-center">
|
||||
{!['goal', 'props'].includes(key) &&
|
||||
<Link to={{ pathname: `/${encodeURIComponent(this.props.site.domain)}/filter/${key}`, search: window.location.search }}>
|
||||
<svg className="cursor-pointer hover:text-indigo-700 dark:hover:text-indigo-500 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
|
||||
</Link>}
|
||||
<b className="ml-2 cursor-pointer hover:text-indigo-700 dark:hover:text-indigo-500" onClick={() => this.removeFilter(key, history, query)}>✕</b>
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
|
@ -203,8 +219,8 @@ class Filters extends React.Component {
|
|||
return (
|
||||
<div className="absolute mt-2 rounded shadow-md z-10" style={{ width: viewport <= 768 ? '320px' : '350px', right: '-5px' }} ref={node => this.dropDownNode = node}>
|
||||
<div className="rounded bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 font-medium text-gray-800 dark:text-gray-200 flex flex-col">
|
||||
<Link to={`/${encodeURIComponent(site.domain)}/filter${window.location.search}`} className="border-b flex border-gray-200 dark:border-gray-500 px-4 sm:py-2 py-3 md:text-sm leading-tight hover:text-indigo-700 dark:hover:text-indigo-500 hover:cursor-pointer">
|
||||
<svg className="mr-2 h-4 w-4 text-gray-500 dark:text-gray-200" fill="none" stroke="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
|
||||
<Link to={`/${encodeURIComponent(site.domain)}/filter${window.location.search}`} className="group border-b flex border-gray-200 dark:border-gray-500 px-4 sm:py-2 py-3 md:text-sm leading-tight hover:text-indigo-700 dark:hover:text-indigo-500 hover:cursor-pointer">
|
||||
<svg className="mr-2 h-4 w-4 text-gray-500 dark:text-gray-200 group-hover:text-indigo-700 dark:group-hover:text-indigo-500 hover:cursor-pointer" fill="none" stroke="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
|
||||
Add Filter
|
||||
</Link>
|
||||
{this.appliedFilters.map((filter) => this.renderDropdownFilter(history, filter, query))}
|
||||
|
|
@ -247,10 +263,10 @@ class Filters extends React.Component {
|
|||
const {viewport} = this.state;
|
||||
|
||||
return (
|
||||
<div id="filters" className="flex flex-grow pl-3 flex-wrap">
|
||||
<div id="filters" className="flex flex-grow pl-2 flex-wrap">
|
||||
{(this.appliedFilters.map((filter) => this.renderListFilter(history, filter, query)))}
|
||||
<Link to={`/${encodeURIComponent(site.domain)}/filter${window.location.search}`} className={`button ${viewport <= 768 ? "px-2 mr-1" : "px-3 mr-2"} py-2 cursor-pointer ml-auto`}>
|
||||
<svg className={`${viewport <= 768 ? "mr-1" : "mr-2"} h-4 w-4 text-gray-500 dark:text-gray-200 group-hover:text-gray-600 dark:group-hover:text-gray-400 group-focus:text-gray-500 dark:group-focus:text-gray-200`} fill="none" stroke="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
|
||||
<svg className={`${viewport <= 768 ? "mr-1" : "mr-2"} h-4 w-4`} fill="none" stroke="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
|
||||
{viewport <= 768 ? "Filter" : "Add Filter"}
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,91 +1,117 @@
|
|||
import React from "react";
|
||||
import { withRouter, Link } from 'react-router-dom'
|
||||
import { withRouter, Redirect } from 'react-router-dom'
|
||||
|
||||
import Modal from './modal'
|
||||
import { parseQuery, formattedFilters } from '../../query'
|
||||
import { parseQuery, formattedFilters, navigateToQuery } from '../../query'
|
||||
|
||||
class FilterModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
query: parseQuery(props.location.search, props.site),
|
||||
selectedFilter: this.props.match.params.field || "",
|
||||
selectedFilter: "",
|
||||
negated: false,
|
||||
updatedValue: "",
|
||||
negated: false
|
||||
filterSaved: false
|
||||
}
|
||||
|
||||
this.editableGoals = Object.keys(this.state.query.filters).filter(filter => !['goal', 'props'].includes(filter))
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({ selectedFilter: this.props.match.params.field })
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { query, selectedFilter } = this.state
|
||||
|
||||
if (prevState.selectedFilter !== selectedFilter) {
|
||||
const negated = query.filters[selectedFilter] && query.filters[selectedFilter][0] == '!' && this.negationSupported(selectedFilter)
|
||||
const updatedValue = negated ? query.filters[selectedFilter].slice(1) : (query.filters[selectedFilter] || "")
|
||||
|
||||
this.setState({ updatedValue, negated })
|
||||
}
|
||||
}
|
||||
|
||||
negationSupported(filter) {
|
||||
return ['page', 'entry_page', 'exit_page'].includes(filter)
|
||||
}
|
||||
|
||||
renderBody() {
|
||||
const { selectedFilter, negated, updatedValue, query } = this.state;
|
||||
|
||||
const updatedQuery = new URLSearchParams(window.location.search)
|
||||
const finalFilterValue = (['page', 'entry_page', 'exit_page'].includes(selectedFilter) && negated ? '!' : '') + updatedValue
|
||||
const validQuery = this.editableGoals.includes(selectedFilter) && updatedValue
|
||||
updatedQuery.set(selectedFilter, finalFilterValue)
|
||||
const finalFilterValue = (this.negationSupported(selectedFilter) && negated ? '!' : '') + updatedValue
|
||||
const finalizedQuery = new URLSearchParams(window.location.search)
|
||||
const validFilter = this.editableGoals.includes(selectedFilter) && updatedValue
|
||||
finalizedQuery.set(selectedFilter, finalFilterValue)
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h1 className="text-xl font-bold dark:text-gray-100">{query.filters[selectedFilter] ? 'Edit' : 'Add'} a Filter</h1>
|
||||
<h1 className="text-xl font-bold dark:text-gray-100">{query.filters[selectedFilter] ? 'Edit' : 'Add'} Filter</h1>
|
||||
|
||||
<div className="my-4 border-b border-gray-300"></div>
|
||||
<main className="modal__content flex flex-col">
|
||||
<select
|
||||
value={selectedFilter}
|
||||
className="block w-full py-2 pl-3 pr-10 mt-1 text-base border-gray-300 dark:border-gray-700 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-900 dark:text-gray-300"
|
||||
placeholder="Select a Filter"
|
||||
onChange={(e) => {
|
||||
const negated = query.filters[e.target.value][0] == '!' && ['page', 'entry_page', 'exit_page'].includes(e.target.value)
|
||||
const updatedValue = negated ? query.filters[e.target.value].slice(1) : (query.filters[e.target.value] || "")
|
||||
this.setState({ selectedFilter: e.target.value, updatedValue, negated })
|
||||
}}
|
||||
>
|
||||
<option disabled value="" className="hidden">Select a Filter</option>
|
||||
{this.editableGoals.map(filter => <option key={filter} value={filter}>{formattedFilters[filter]}</option>)}
|
||||
</select>
|
||||
<main className="modal__content">
|
||||
<form className="flex flex-col" onSubmit={() => {
|
||||
if (validFilter) {
|
||||
this.setState({ finalizedQuery })
|
||||
}
|
||||
}}>
|
||||
<select
|
||||
value={selectedFilter}
|
||||
className="my-2 block w-full py-2 pl-3 pr-10 text-base border-gray-300 dark:border-gray-700 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-900 dark:text-gray-300"
|
||||
placeholder="Select a Filter"
|
||||
onChange={(e) => this.setState({ selectedFilter: e.target.value })}
|
||||
>
|
||||
<option disabled value="" className="hidden">Select a Filter</option>
|
||||
{this.editableGoals.map(filter => <option key={filter} value={filter}>{formattedFilters[filter]}</option>)}
|
||||
</select>
|
||||
|
||||
{['page', 'entry_page', 'exit_page'].includes(selectedFilter) &&
|
||||
<div className="my-4 flex items-center">
|
||||
<label className="text-gray-700 dark:text-gray-300 text-sm cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className={`${negated ? 'bg-indigo-600' : 'bg-gray-200 dark:bg-gray-700'} mr-2 relative inline-flex flex-shrink-0 h-6 w-8 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring`}
|
||||
checked={negated}
|
||||
name="exclude"
|
||||
onClick={(e) => this.setState({ negated: e.target.checked })}
|
||||
/>
|
||||
{this.negationSupported(selectedFilter) &&
|
||||
<div className="mt-4 flex items-center">
|
||||
<label className="text-gray-700 dark:text-gray-300 text-sm cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className={"text-indigo-600 bg-gray-100 dark:bg-gray-700 mr-2 relative inline-flex flex-shrink-0 h-6 w-8 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none"}
|
||||
checked={negated}
|
||||
name="exclude"
|
||||
onChange={(e) => this.setState({ negated: e.target.checked })}
|
||||
/>
|
||||
Exclude pages matching this filter
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
{selectedFilter &&
|
||||
<input
|
||||
type="text"
|
||||
className="bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 text-gray-700 dark:text-gray-300 leading-normal focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500"
|
||||
value={updatedValue}
|
||||
placeholder="Filter value"
|
||||
onChange={(e) => { this.setState({ updatedValue: e.target.value }) }}
|
||||
/>
|
||||
}
|
||||
{selectedFilter &&
|
||||
<input
|
||||
type="text"
|
||||
className="mt-4 bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 text-gray-700 dark:text-gray-300 leading-normal focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500"
|
||||
value={updatedValue}
|
||||
placeholder="Filter value"
|
||||
onChange={(e) => { this.setState({ updatedValue: e.target.value }) }}
|
||||
/>
|
||||
}
|
||||
|
||||
<Link to={{ pathname: `/${encodeURIComponent(this.props.site.domain)}`, search: updatedQuery.toString() }}
|
||||
className={"relative button my-4 w-2/3"}
|
||||
>
|
||||
{query.filters[selectedFilter] ? 'Update' : 'Add'} Filter
|
||||
</Link>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!validFilter}
|
||||
className={"button mt-4 w-2/3 mx-auto"}
|
||||
>
|
||||
{query.filters[selectedFilter] ? 'Update' : 'Add'} Filter
|
||||
</button>
|
||||
|
||||
</form>
|
||||
</main>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { finalizedQuery } = this.state
|
||||
|
||||
if (finalizedQuery) {
|
||||
return <Redirect to={{ pathname: `/${encodeURIComponent(this.props.site.domain)}`, search: finalizedQuery.toString() }} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal site={this.props.site} maxWidth="460px">
|
||||
{ this.renderBody()}
|
||||
|
|
|
|||
Loading…
Reference in New Issue