296 lines
7.9 KiB
JavaScript
296 lines
7.9 KiB
JavaScript
import React from 'react'
|
|
|
|
import * as storage from '../../util/storage'
|
|
import CountriesMap from './map'
|
|
|
|
import * as api from '../../api'
|
|
import { apiPath } from '../../util/url'
|
|
import ListReport from '../reports/list'
|
|
import * as metrics from '../reports/metrics'
|
|
import {
|
|
hasConversionGoalFilter,
|
|
getFiltersByKeyPrefix
|
|
} from '../../util/filters'
|
|
import ImportedQueryUnsupportedWarning from '../imported-query-unsupported-warning'
|
|
import { citiesRoute, countriesRoute, regionsRoute } from '../../router'
|
|
import { useQueryContext } from '../../query-context'
|
|
import { useSiteContext } from '../../site-context'
|
|
import { TabButton, TabWrapper } from '../../components/tabs'
|
|
|
|
function Countries({ query, site, onClick, afterFetchData }) {
|
|
function fetchData() {
|
|
return api.get(apiPath(site, '/countries'), query, { limit: 9 })
|
|
}
|
|
|
|
function renderIcon(country) {
|
|
return <span className="mr-2">{country.flag}</span>
|
|
}
|
|
|
|
function getFilterInfo(listItem) {
|
|
return {
|
|
prefix: 'country',
|
|
filter: ['is', 'country', [listItem['code']]],
|
|
labels: { [listItem['code']]: listItem['name'] }
|
|
}
|
|
}
|
|
|
|
function chooseMetrics() {
|
|
return [
|
|
metrics.createVisitors({ meta: { plot: true } }),
|
|
!hasConversionGoalFilter(query) &&
|
|
metrics.createPercentage({ meta: { showOnHover: true } }),
|
|
hasConversionGoalFilter(query) && metrics.createConversionRate()
|
|
].filter((metric) => !!metric)
|
|
}
|
|
|
|
return (
|
|
<ListReport
|
|
fetchData={fetchData}
|
|
afterFetchData={afterFetchData}
|
|
getFilterInfo={getFilterInfo}
|
|
onClick={onClick}
|
|
keyLabel="Country"
|
|
metrics={chooseMetrics()}
|
|
detailsLinkProps={{
|
|
path: countriesRoute.path,
|
|
search: (search) => search
|
|
}}
|
|
renderIcon={renderIcon}
|
|
color="bg-orange-50 group-hover/row:bg-orange-100"
|
|
/>
|
|
)
|
|
}
|
|
|
|
function Regions({ query, site, onClick, afterFetchData }) {
|
|
function fetchData() {
|
|
return api.get(apiPath(site, '/regions'), query, { limit: 9 })
|
|
}
|
|
|
|
function renderIcon(region) {
|
|
return <span className="mr-2">{region.country_flag}</span>
|
|
}
|
|
|
|
function getFilterInfo(listItem) {
|
|
return {
|
|
prefix: 'region',
|
|
filter: ['is', 'region', [listItem['code']]],
|
|
labels: { [listItem['code']]: listItem['name'] }
|
|
}
|
|
}
|
|
|
|
function chooseMetrics() {
|
|
return [
|
|
metrics.createVisitors({ meta: { plot: true } }),
|
|
!hasConversionGoalFilter(query) &&
|
|
metrics.createPercentage({ meta: { showOnHover: true } }),
|
|
hasConversionGoalFilter(query) && metrics.createConversionRate()
|
|
].filter((metric) => !!metric)
|
|
}
|
|
|
|
return (
|
|
<ListReport
|
|
fetchData={fetchData}
|
|
afterFetchData={afterFetchData}
|
|
getFilterInfo={getFilterInfo}
|
|
onClick={onClick}
|
|
keyLabel="Region"
|
|
metrics={chooseMetrics()}
|
|
detailsLinkProps={{ path: regionsRoute.path, search: (search) => search }}
|
|
renderIcon={renderIcon}
|
|
color="bg-orange-50 group-hover/row:bg-orange-100"
|
|
/>
|
|
)
|
|
}
|
|
|
|
function Cities({ query, site, afterFetchData }) {
|
|
function fetchData() {
|
|
return api.get(apiPath(site, '/cities'), query, { limit: 9 })
|
|
}
|
|
|
|
function renderIcon(city) {
|
|
return <span className="mr-2">{city.country_flag}</span>
|
|
}
|
|
|
|
function getFilterInfo(listItem) {
|
|
return {
|
|
prefix: 'city',
|
|
filter: ['is', 'city', [listItem['code']]],
|
|
labels: { [listItem['code']]: listItem['name'] }
|
|
}
|
|
}
|
|
|
|
function chooseMetrics() {
|
|
return [
|
|
metrics.createVisitors({ meta: { plot: true } }),
|
|
!hasConversionGoalFilter(query) &&
|
|
metrics.createPercentage({ meta: { showOnHover: true } }),
|
|
hasConversionGoalFilter(query) && metrics.createConversionRate()
|
|
].filter((metric) => !!metric)
|
|
}
|
|
|
|
return (
|
|
<ListReport
|
|
fetchData={fetchData}
|
|
afterFetchData={afterFetchData}
|
|
getFilterInfo={getFilterInfo}
|
|
keyLabel="City"
|
|
metrics={chooseMetrics()}
|
|
detailsLinkProps={{ path: citiesRoute.path, search: (search) => search }}
|
|
renderIcon={renderIcon}
|
|
color="bg-orange-50 group-hover/row:bg-orange-100"
|
|
/>
|
|
)
|
|
}
|
|
|
|
const labelFor = {
|
|
countries: 'Countries',
|
|
regions: 'Regions',
|
|
cities: 'Cities'
|
|
}
|
|
|
|
class Locations extends React.Component {
|
|
constructor(props) {
|
|
super(props)
|
|
this.onCountryFilter = this.onCountryFilter.bind(this)
|
|
this.onRegionFilter = this.onRegionFilter.bind(this)
|
|
this.afterFetchData = this.afterFetchData.bind(this)
|
|
this.tabKey = `geoTab__${props.site.domain}`
|
|
const storedTab = storage.getItem(this.tabKey)
|
|
this.state = {
|
|
mode: storedTab || 'map',
|
|
loading: true,
|
|
skipImportedReason: null
|
|
}
|
|
}
|
|
|
|
componentDidUpdate(prevProps, prevState) {
|
|
const isRemovingFilter = (filterName) => {
|
|
return (
|
|
getFiltersByKeyPrefix(prevProps.query, filterName).length > 0 &&
|
|
getFiltersByKeyPrefix(this.props.query, filterName).length == 0
|
|
)
|
|
}
|
|
|
|
if (this.state.mode === 'cities' && isRemovingFilter('region')) {
|
|
this.setMode('regions')()
|
|
}
|
|
|
|
if (this.state.mode === 'regions' && isRemovingFilter('country')) {
|
|
this.setMode(this.countriesRestoreMode || 'countries')()
|
|
}
|
|
|
|
if (
|
|
this.props.query !== prevProps.query ||
|
|
this.state.mode !== prevState.mode
|
|
) {
|
|
this.setState({ loading: true })
|
|
}
|
|
}
|
|
|
|
setMode(mode) {
|
|
return () => {
|
|
storage.setItem(this.tabKey, mode)
|
|
this.setState({ mode })
|
|
}
|
|
}
|
|
|
|
onCountryFilter(mode) {
|
|
return () => {
|
|
this.countriesRestoreMode = mode
|
|
this.setMode('regions')()
|
|
}
|
|
}
|
|
|
|
onRegionFilter() {
|
|
this.setMode('cities')()
|
|
}
|
|
|
|
afterFetchData(apiResponse) {
|
|
this.setState({
|
|
loading: false,
|
|
skipImportedReason: apiResponse.skip_imported_reason
|
|
})
|
|
}
|
|
|
|
renderContent() {
|
|
switch (this.state.mode) {
|
|
case 'cities':
|
|
return (
|
|
<Cities
|
|
site={this.props.site}
|
|
query={this.props.query}
|
|
afterFetchData={this.afterFetchData}
|
|
/>
|
|
)
|
|
case 'regions':
|
|
return (
|
|
<Regions
|
|
onClick={this.onRegionFilter}
|
|
site={this.props.site}
|
|
query={this.props.query}
|
|
afterFetchData={this.afterFetchData}
|
|
/>
|
|
)
|
|
case 'countries':
|
|
return (
|
|
<Countries
|
|
onClick={this.onCountryFilter('countries')}
|
|
site={this.props.site}
|
|
query={this.props.query}
|
|
afterFetchData={this.afterFetchData}
|
|
/>
|
|
)
|
|
case 'map':
|
|
default:
|
|
return (
|
|
<CountriesMap
|
|
onCountrySelect={this.onCountryFilter('map')}
|
|
afterFetchData={this.afterFetchData}
|
|
/>
|
|
)
|
|
}
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div className="group/report overflow-x-hidden">
|
|
<div className="w-full flex justify-between">
|
|
<div className="flex gap-x-1">
|
|
<h3 className="font-bold dark:text-gray-100">
|
|
{labelFor[this.state.mode] || 'Locations'}
|
|
</h3>
|
|
<ImportedQueryUnsupportedWarning
|
|
loading={this.state.loading}
|
|
skipImportedReason={this.state.skipImportedReason}
|
|
/>
|
|
</div>
|
|
<TabWrapper>
|
|
{[
|
|
{ label: 'Map', value: 'map' },
|
|
{ label: 'Countries', value: 'countries' },
|
|
{ label: 'Regions', value: 'regions' },
|
|
{ label: 'Cities', value: 'cities' }
|
|
].map(({ value, label }) => (
|
|
<TabButton
|
|
key={value}
|
|
onClick={this.setMode(value)}
|
|
active={this.state.mode === value}
|
|
>
|
|
{label}
|
|
</TabButton>
|
|
))}
|
|
</TabWrapper>
|
|
</div>
|
|
{this.renderContent()}
|
|
</div>
|
|
)
|
|
}
|
|
}
|
|
|
|
function LocationsWithContext() {
|
|
const { query } = useQueryContext()
|
|
const site = useSiteContext()
|
|
return <Locations site={site} query={query} />
|
|
}
|
|
export default LocationsWithContext
|