lgtm
This commit is contained in:
parent
084117cea8
commit
a8de0a614d
36 changed files with 1329 additions and 522 deletions
|
|
@ -1,8 +1,13 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ts } from '../../i18n/server';
|
||||
import type { FeatureFilters, FeatureMeta, HexagonStatsResponse } from '../../types';
|
||||
import type { TravelTimeEntry } from '../../hooks/useTravelTime';
|
||||
import type {
|
||||
FeatureFilters,
|
||||
FeatureMeta,
|
||||
FilterExclusion,
|
||||
HexagonStatsResponse,
|
||||
} from '../../types';
|
||||
import { travelFieldKey, type TravelTimeEntry } from '../../hooks/useTravelTime';
|
||||
import type { HexagonLocation } from '../../lib/external-search';
|
||||
import {
|
||||
formatValue,
|
||||
|
|
@ -61,6 +66,21 @@ function normalizePercentageSegments<T extends { value: number }>(segments: T[])
|
|||
return segments.map((segment, index) => ({ ...segment, value: normalizedValues[index] }));
|
||||
}
|
||||
|
||||
function filterValueFormat(feature?: FeatureMeta) {
|
||||
if (!feature) return undefined;
|
||||
return {
|
||||
prefix: feature.prefix,
|
||||
suffix: feature.suffix,
|
||||
raw: feature.raw,
|
||||
};
|
||||
}
|
||||
|
||||
function formatExclusionPercent(value: number): string {
|
||||
const percent = value * 100;
|
||||
if (percent < 10) return `${percent.toFixed(1)}%`;
|
||||
return `${Math.round(percent)}%`;
|
||||
}
|
||||
|
||||
export default function AreaPane({
|
||||
stats,
|
||||
globalFeatures,
|
||||
|
|
@ -103,6 +123,36 @@ export default function AreaPane({
|
|||
() => new Map(globalFeatures.map((f) => [f.name, f])),
|
||||
[globalFeatures]
|
||||
);
|
||||
const travelEntryByField = useMemo(() => {
|
||||
const map = new Map<string, TravelTimeEntry>();
|
||||
for (const entry of travelTimeEntries ?? []) {
|
||||
map.set(travelFieldKey(entry), entry);
|
||||
}
|
||||
return map;
|
||||
}, [travelTimeEntries]);
|
||||
const filterExclusions = stats?.filter_exclusions ?? [];
|
||||
|
||||
const getExclusionLabel = (exclusion: FilterExclusion) => {
|
||||
const travelEntry = travelEntryByField.get(exclusion.name);
|
||||
if (travelEntry) return t('areaPane.travelTo', { destination: travelEntry.label });
|
||||
return ts(exclusion.name);
|
||||
};
|
||||
|
||||
const formatExclusionValue = (exclusion: FilterExclusion, value: number) => {
|
||||
if (exclusion.kind === 'travel') return `${Math.round(value)} ${t('common.min')}`;
|
||||
return formatFilterValue(value, filterValueFormat(globalFeatureByName.get(exclusion.name)));
|
||||
};
|
||||
|
||||
const getExclusionAdjustment = (exclusion: FilterExclusion) => {
|
||||
if (exclusion.direction === 'allow_value') {
|
||||
return t('areaPane.allowCategory', { value: ts(exclusion.category ?? '') });
|
||||
}
|
||||
if (exclusion.value == null) return '';
|
||||
const value = formatExclusionValue(exclusion, exclusion.value);
|
||||
return exclusion.direction === 'lower_min'
|
||||
? t('areaPane.lowerMinTo', { value })
|
||||
: t('areaPane.raiseMaxTo', { value });
|
||||
};
|
||||
|
||||
if (!hexagonId) {
|
||||
return (
|
||||
|
|
@ -205,6 +255,31 @@ export default function AreaPane({
|
|||
>
|
||||
{t('areaPane.showAllStats')}
|
||||
</button>
|
||||
{filterExclusions.length > 0 && (
|
||||
<div className="mt-2 border-t border-amber-200 pt-2 dark:border-amber-800/70">
|
||||
<p className="font-semibold">{t('areaPane.closestBlockingFilters')}</p>
|
||||
<ol className="mt-1.5 space-y-1.5">
|
||||
{filterExclusions.map((exclusion) => (
|
||||
<li
|
||||
key={`${exclusion.kind}:${exclusion.name}:${exclusion.direction}:${exclusion.category ?? ''}`}
|
||||
className="rounded bg-white/70 px-2 py-1.5 dark:bg-navy-950/40"
|
||||
>
|
||||
<div className="flex items-baseline justify-between gap-2">
|
||||
<span className="min-w-0 truncate font-medium">
|
||||
{getExclusionLabel(exclusion)}
|
||||
</span>
|
||||
<span className="shrink-0 tabular-nums text-amber-700 dark:text-amber-200">
|
||||
{formatExclusionPercent(exclusion.relative_difference)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-0.5 text-amber-800/80 dark:text-amber-100/80">
|
||||
{getExclusionAdjustment(exclusion)}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{canViewProperties && (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue