LGTM
This commit is contained in:
parent
9248e26af2
commit
f2a2651b8a
95 changed files with 3993 additions and 1471 deletions
|
|
@ -1,12 +1,7 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ts } from '../../i18n/server';
|
||||
import type {
|
||||
FeatureFilters,
|
||||
FeatureMeta,
|
||||
HexagonStatsResponse,
|
||||
PostcodeFeature,
|
||||
} from '../../types';
|
||||
import type { FeatureFilters, FeatureMeta, HexagonStatsResponse } from '../../types';
|
||||
import type { TravelTimeEntry } from '../../hooks/useTravelTime';
|
||||
import type { HexagonLocation } from '../../lib/external-search';
|
||||
import {
|
||||
|
|
@ -28,7 +23,7 @@ import StackedBarChart from './StackedBarChart';
|
|||
import StackedEnumChart from './StackedEnumChart';
|
||||
import PriceHistoryChart from './PriceHistoryChart';
|
||||
import ExternalSearchLinks from './ExternalSearchLinks';
|
||||
import { FilterIcon, InfoIcon } from '../ui/icons';
|
||||
import { InfoIcon } from '../ui/icons';
|
||||
import { CollapsibleGroupHeader } from '../ui/CollapsibleGroupHeader';
|
||||
import { FeatureInfoPopup } from '../ui/FeatureInfoPopup';
|
||||
import { EmptyState } from '../ui/EmptyState';
|
||||
|
|
@ -43,12 +38,12 @@ interface AreaPaneProps {
|
|||
loading: boolean;
|
||||
hexagonId: string | null;
|
||||
isPostcode?: boolean;
|
||||
postcodeData?: PostcodeFeature | null;
|
||||
onViewProperties: () => void;
|
||||
onClearFilters?: () => void;
|
||||
hexagonLocation: HexagonLocation | null;
|
||||
filters: FeatureFilters;
|
||||
unfilteredCount?: number | null;
|
||||
statsUseFilters: boolean;
|
||||
onStatsUseFiltersChange: (useFilters: boolean) => void;
|
||||
onNavigateToSource?: (slug: string, featureName: string) => void;
|
||||
travelTimeEntries?: TravelTimeEntry[];
|
||||
isGroupExpanded: (name: string) => boolean;
|
||||
|
|
@ -71,21 +66,24 @@ export default function AreaPane({
|
|||
loading,
|
||||
hexagonId,
|
||||
isPostcode = false,
|
||||
postcodeData,
|
||||
onViewProperties,
|
||||
onClearFilters,
|
||||
hexagonLocation,
|
||||
filters,
|
||||
unfilteredCount,
|
||||
statsUseFilters,
|
||||
onStatsUseFiltersChange,
|
||||
onNavigateToSource,
|
||||
travelTimeEntries,
|
||||
isGroupExpanded,
|
||||
onToggleGroup,
|
||||
}: AreaPaneProps) {
|
||||
const { t } = useTranslation();
|
||||
const propertyCount = isPostcode && postcodeData ? postcodeData.properties.count : stats?.count;
|
||||
const propertyCount = stats?.count;
|
||||
const activeFilterCount = Object.keys(filters).length + (travelTimeEntries?.length ?? 0);
|
||||
const hasFilteredOutArea = activeFilterCount > 0 && stats?.count === 0;
|
||||
const filtersActive = activeFilterCount > 0;
|
||||
const filteredStatsEmpty = filtersActive && statsUseFilters && stats?.count === 0;
|
||||
const showFlipToggleCallout = filteredStatsEmpty && unfilteredCount !== 0;
|
||||
const canViewProperties = stats && stats.count > 0 && (statsUseFilters || !filtersActive);
|
||||
const featureGroups = useMemo(() => groupFeaturesByCategory(globalFeatures), [globalFeatures]);
|
||||
const [infoFeature, setInfoFeature] = useState<FeatureMeta | null>(null);
|
||||
|
||||
|
|
@ -148,37 +146,66 @@ export default function AreaPane({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 border-l-2 border-teal-500 bg-warm-50 px-2.5 py-2 text-xs leading-snug text-warm-700 dark:bg-navy-900 dark:text-warm-300">
|
||||
<FilterIcon className="mt-0.5 h-3.5 w-3.5 shrink-0 text-teal-700 dark:text-teal-300" />
|
||||
<p>
|
||||
{activeFilterCount > 0
|
||||
? t('areaPane.filtersAffectStats', { count: activeFilterCount })
|
||||
<div className="rounded border border-warm-200 bg-warm-50 px-2.5 py-2 dark:border-navy-700 dark:bg-navy-900">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-xs font-semibold text-warm-700 dark:text-warm-200">
|
||||
{t('areaPane.statsBasis')}
|
||||
</span>
|
||||
<div className="inline-flex shrink-0 rounded-md bg-warm-200 p-0.5 dark:bg-navy-800">
|
||||
<button
|
||||
type="button"
|
||||
disabled={!filtersActive}
|
||||
aria-pressed={statsUseFilters && filtersActive}
|
||||
onClick={() => onStatsUseFiltersChange(true)}
|
||||
className={`rounded px-2 py-1 text-xs font-medium ${
|
||||
statsUseFilters && filtersActive
|
||||
? 'bg-white text-teal-700 shadow-sm dark:bg-navy-700 dark:text-teal-300'
|
||||
: 'text-warm-600 hover:text-warm-900 disabled:cursor-not-allowed disabled:opacity-50 dark:text-warm-400 dark:hover:text-warm-100'
|
||||
}`}
|
||||
>
|
||||
{t('areaPane.matchingFiltersOption')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed={!statsUseFilters || !filtersActive}
|
||||
onClick={() => onStatsUseFiltersChange(false)}
|
||||
className={`rounded px-2 py-1 text-xs font-medium ${
|
||||
!statsUseFilters || !filtersActive
|
||||
? 'bg-white text-teal-700 shadow-sm dark:bg-navy-700 dark:text-teal-300'
|
||||
: 'text-warm-600 hover:text-warm-900 dark:text-warm-400 dark:hover:text-warm-100'
|
||||
}`}
|
||||
>
|
||||
{t('areaPane.allPropertiesOption')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-1.5 text-xs leading-snug text-warm-500 dark:text-warm-400">
|
||||
{filtersActive
|
||||
? statsUseFilters
|
||||
? t('areaPane.filtersAffectStats', { count: activeFilterCount })
|
||||
: t('areaPane.filtersIgnoredForStats')
|
||||
: t('areaPane.noFiltersAffectStats')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{hasFilteredOutArea && (
|
||||
{showFlipToggleCallout && (
|
||||
<div className="mt-2 rounded border border-amber-200 bg-amber-50 px-2.5 py-2 text-xs leading-snug text-amber-900 dark:border-amber-800/70 dark:bg-amber-950/40 dark:text-amber-100">
|
||||
<p className="font-semibold">{t('areaPane.noFilteredMatches')}</p>
|
||||
<p className="font-semibold">{t('areaPane.filteredStatsEmpty')}</p>
|
||||
<p className="mt-1">
|
||||
{unfilteredCount != null && unfilteredCount > 0
|
||||
? t('areaPane.unfilteredAreaCount', { count: unfilteredCount })
|
||||
: unfilteredCount === 0
|
||||
? t('areaPane.noUnfilteredAreaProperties')
|
||||
: t('areaPane.relaxFiltersHint')}
|
||||
{unfilteredCount != null
|
||||
? t('areaPane.showAllStatsHint', { count: unfilteredCount })
|
||||
: t('areaPane.showAllStatsFallback')}
|
||||
</p>
|
||||
{onClearFilters && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClearFilters}
|
||||
className="mt-2 rounded bg-amber-600 px-2 py-1 text-xs font-medium text-white hover:bg-amber-700 dark:bg-amber-500 dark:text-amber-950 dark:hover:bg-amber-400"
|
||||
>
|
||||
{t('filters.clearAll')}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onStatsUseFiltersChange(false)}
|
||||
className="mt-2 rounded bg-amber-600 px-2 py-1 text-xs font-medium text-white hover:bg-amber-700 dark:bg-amber-500 dark:text-amber-950 dark:hover:bg-amber-400"
|
||||
>
|
||||
{t('areaPane.showAllStats')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{stats && stats.count > 0 && (
|
||||
{canViewProperties && (
|
||||
<button
|
||||
onClick={onViewProperties}
|
||||
className="w-full text-sm py-1.5 rounded bg-teal-600 hover:bg-teal-700 text-white font-medium"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue