@trackunit/filters-asset-filter-definitions 1.12.0 → 1.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs.js CHANGED
@@ -12,9 +12,9 @@ var reactFilterComponents = require('@trackunit/react-filter-components');
12
12
  var reactFormComponents = require('@trackunit/react-form-components');
13
13
  var utilizationIndicator = require('@trackunit/utilization-indicator');
14
14
  var geoJsonUtils = require('@trackunit/geo-json-utils');
15
+ var zod = require('zod');
15
16
  var reactComponents = require('@trackunit/react-components');
16
17
  var stringTs = require('string-ts');
17
- var zod = require('zod');
18
18
  var sharedUtils = require('@trackunit/shared-utils');
19
19
  var translationsMachineType = require('@trackunit/translations-machine-type');
20
20
  var criticalityIndicator = require('@trackunit/criticality-indicator');
@@ -2476,28 +2476,35 @@ const useActivityFilter = () => {
2476
2476
  };
2477
2477
 
2478
2478
  /**
2479
- * A button to clear recent searches.
2479
+ * Renders the AreaView component, which manages the UI and logic for an area filter.
2480
2480
  *
2481
- * @param {ClearItemButtonProps} props - The props for the ClearItemButton component
2482
- * @returns {ReactElement} ClearItemButton component
2481
+ * @param {FilterViewProps<AreaFilterGeoJsonGeometry>} props - The props for the component.
2482
+ * @returns {ReactElement} The rendered AreaView component.
2483
2483
  */
2484
- const ClearItemButton = ({ onClick }) => {
2484
+ const AreaView = (props) => {
2485
2485
  const [t] = useTranslation();
2486
- return (jsxRuntime.jsx(reactComponents.Tooltip, { label: t("filters.shared.clear"), children: jsxRuntime.jsx(reactComponents.IconButton, { className: "rounded-full", icon: jsxRuntime.jsx(reactComponents.Icon, { name: "Trash", size: "small" }), onClick: onClick, size: "small", variant: "ghost-neutral" }) }));
2486
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(filtersFilterBar.FilterHeader, { ...props.filterDefinition, ...props.filterBarActions, filterHasChanges: props.filterBarActions.appliedFilterKeys().includes(props.filterDefinition.filterKey) }), jsxRuntime.jsx(reactFilterComponents.FilterBody, { limitSize: true, children: jsxRuntime.jsx("p", { className: "p-2", children: `${t("assetFilters.boundingBoxFilter.value")}` }) })] }));
2487
2487
  };
2488
-
2489
2488
  /**
2490
- * A footer component that displays a count of recent searches and a clear all button.
2489
+ * Area filter definition
2491
2490
  *
2492
- * @param {RecentSearchesFooterProps} props - The props for the ClearAllFooter component
2493
- * @returns {ReactElement} RecentSearchesFooter component
2491
+ * @returns {AreaFilterDefinition} Area filter definition
2494
2492
  */
2495
- const RecentSearchesFooter = ({ count, onClearAll }) => {
2493
+ const useAreaFilter = () => {
2496
2494
  const [t] = useTranslation();
2497
- if (count === 0) {
2498
- return null;
2499
- }
2500
- return (jsxRuntime.jsxs("div", { className: "flex items-baseline justify-between gap-x-2 border-t-2 border-neutral-300 px-2 py-0.5", children: [jsxRuntime.jsx(reactComponents.Text, { className: "py-0.5", size: "small", subtle: true, children: t("filters.shared.recentSearches") }), count > 1 ? (jsxRuntime.jsx(reactComponents.Button, { onClick: onClearAll, size: "small", variant: "ghost", children: t("filters.shared.clearAll") })) : null] }));
2495
+ const result = react.useMemo(() => {
2496
+ return {
2497
+ filterKey: "area",
2498
+ type: "area",
2499
+ group: "CURRENT_LOCATION",
2500
+ title: t("assetFilters.area.label"),
2501
+ valueAsText: value => {
2502
+ return t("assetFilters.boundingBoxFilter.value");
2503
+ },
2504
+ component: props => jsxRuntime.jsx(AreaView, { ...props }),
2505
+ };
2506
+ }, [t]);
2507
+ return result;
2501
2508
  };
2502
2509
 
2503
2510
  const areaFilterInternalGeoJsonGeometrySchema = zod.z.union([
@@ -2679,145 +2686,6 @@ const usePlacesSearch = ({ localStorageKey = SHARED_LOCATIONS_LOCAL_STORAGE_KEY,
2679
2686
  ]);
2680
2687
  };
2681
2688
 
2682
- /**
2683
- * Renders the AreaView component, which manages the UI and logic for an area filter
2684
- * with optional search functionality.
2685
- *
2686
- * @param {FilterViewProps<AreaFilterGeoJsonGeometry> & { showWithSearch: boolean }} props - The props for the component.
2687
- * @returns {ReactElement} The rendered AreaView component.
2688
- */
2689
- const AreaView = (props) => {
2690
- const [t] = useTranslation();
2691
- const { logEvent } = reactCoreHooks.useAnalytics(filtersFilterBar.FilterEvents);
2692
- const { places, recentPlaces, setRecentPlaces, selectedPlaceData, setSelectedPrediction, searchString, setSearchString, debouncedSearchString, loading, hint, } = usePlacesSearch({
2693
- localStorageKey: "shared-recent-locations",
2694
- });
2695
- const placeholderOption = react.useMemo(() => {
2696
- // If no selected option, use this placeholder option
2697
- // Aka if applied from outside the filter UI
2698
- if (!props.value) {
2699
- return null;
2700
- }
2701
- const placeholderBbox = geoJsonUtils.getBboxFromGeoJsonPolygon(props.value);
2702
- return placeholderBbox
2703
- ? {
2704
- bBox: placeholderBbox,
2705
- geometry: props.value,
2706
- properties: { formattedAddress: t("assetFilters.area.origin.mapArea") },
2707
- dateIsoString: undefined,
2708
- placeId: "applied-map-area",
2709
- description: t("assetFilters.area.origin.mapArea"),
2710
- }
2711
- : null;
2712
- }, [props.value, t]);
2713
- const selectedOption = react.useMemo(() => getSelectedOption(props.value, recentPlaces), [props.value, recentPlaces]);
2714
- const setFilter = react.useCallback((place) => {
2715
- const { properties, bBox, geometry, dateIsoString } = place;
2716
- if (geometry.type === "Polygon") {
2717
- // Polygons are stored directly
2718
- props.filterBarActions.setArea(geometry);
2719
- }
2720
- else {
2721
- // All other geometries are stored by the filter as a
2722
- // polygon derived from the features bbox
2723
- props.filterBarActions.setArea(geoJsonUtils.getPolygonFromBbox(bBox));
2724
- }
2725
- logEvent("Filters Applied - V2", {
2726
- type: "AreaFilter",
2727
- value: properties.formattedAddress,
2728
- additionalProperties: {
2729
- geometryType: geometry.type,
2730
- wasPreviousSearch: Boolean(dateIsoString),
2731
- },
2732
- });
2733
- }, [props.filterBarActions, logEvent]);
2734
- react.useEffect(() => {
2735
- if (!selectedPlaceData) {
2736
- return;
2737
- }
2738
- if (selectedPlaceData.placeId === selectedOption?.placeId) {
2739
- return;
2740
- }
2741
- setFilter(selectedPlaceData);
2742
- }, [selectedPlaceData, setFilter, selectedOption]);
2743
- if (!props.showWithSearch) {
2744
- // Temporary while we are testing the new area filter bar behind feature flag
2745
- // This emulated the old look of the boundingbox filter
2746
- // TODO [BUSS] Remove this once the new area filter is ready for prime time
2747
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(filtersFilterBar.FilterHeader, { ...props.filterDefinition, ...props.filterBarActions, filterHasChanges: props.filterBarActions.appliedFilterKeys().includes(props.filterDefinition.filterKey) }), jsxRuntime.jsx(reactFilterComponents.FilterBody, { limitSize: true, children: jsxRuntime.jsx("p", { className: "p-2", children: `${t("assetFilters.boundingBoxFilter.value")}` }) })] }));
2748
- }
2749
- return (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx(filtersFilterBar.FilterHeader, { ...props.filterDefinition, ...props.filterBarActions, filterHasChanges: props.filterBarActions.appliedFilterKeys().includes(props.filterDefinition.filterKey), loading: loading, searchEnabled: true, searchProps: {
2750
- onChange: setSearchString,
2751
- onClear: () => setSearchString(""),
2752
- value: searchString,
2753
- count: places.length,
2754
- placeholder: t("assetFilters.area.searchPlaceholder"),
2755
- } }), jsxRuntime.jsx(reactFilterComponents.FilterBody, { limitSize: true, children: places.length > 0 || placeholderOption ? (jsxRuntime.jsx(filtersFilterBar.FilterResults, { loading: false, results: [
2756
- // Show placeholder option first if selected option is not in filterResult
2757
- // Aka if applied from outside the filter UI
2758
- ...(placeholderOption && !selectedOption && !debouncedSearchString ? [placeholderOption] : []),
2759
- ...places,
2760
- ], children: filterResult => (jsxRuntime.jsx(reactFormComponents.RadioGroup, { className: "m-1", id: "areaFilter", onChange: e => {
2761
- const findOption = filterResult.find(result => result.description === e.currentTarget.value);
2762
- findOption &&
2763
- setSelectedPrediction({
2764
- placeId: findOption.placeId,
2765
- description: findOption.description,
2766
- });
2767
- setSearchString(""); // Clear search input after selection
2768
- }, value: selectedOption?.description ??
2769
- placeholderOption?.description ?? // placeholder if no selected option
2770
- "", children: filterResult.map(({ ...rest }, index) => {
2771
- const selected = selectedOption?.description === rest.description;
2772
- return (jsxRuntime.jsx(reactFilterComponents.RadioFilterItem, { label: rest.description, selected: selected, suffix:
2773
- // Only show clear button if not currently selected and previously searched
2774
- !selected && "dateIsoString" in rest ? (jsxRuntime.jsx(ClearItemButton, { onClick: () => setRecentPlaces(prev => prev.filter(r => r.description !== rest.description)) })) : null, value: rest.description }, index));
2775
- }) })) })) : (jsxRuntime.jsx("p", { className: "py-2 text-center text-sm text-neutral-400", children: hint })) }), debouncedSearchString.length === 0 ? (jsxRuntime.jsx(RecentSearchesFooter, { count: recentPlaces.length, onClearAll: () => setRecentPlaces([]) })) : null] }));
2776
- };
2777
- /**
2778
- * Area filter definition
2779
- *
2780
- * @returns {AreaFilterDefinition} Area filter definition
2781
- */
2782
- const useAreaFilter = ({ showWithSearch } = {
2783
- showWithSearch: () => true,
2784
- }) => {
2785
- const [t] = useTranslation();
2786
- const { recentPlaces } = usePlacesSearch({
2787
- localStorageKey: "shared-recent-locations",
2788
- });
2789
- const result = react.useMemo(() => {
2790
- return {
2791
- filterKey: "area",
2792
- type: "area",
2793
- group: "CURRENT_LOCATION",
2794
- title: t("assetFilters.area.label"),
2795
- showInFilterBar: () => showWithSearch(),
2796
- valueAsText: value => {
2797
- // Match the applied area filter to the places option if it exists in the recent places
2798
- const nameOfSelectedArea = getSelectedOption(value, recentPlaces);
2799
- return showWithSearch()
2800
- ? (nameOfSelectedArea?.description ?? t("assetFilters.area.active"))
2801
- : t("assetFilters.boundingBoxFilter.value");
2802
- },
2803
- component: props => jsxRuntime.jsx(AreaView, { ...props, showWithSearch: showWithSearch() }),
2804
- };
2805
- }, [t, showWithSearch, recentPlaces]);
2806
- return result;
2807
- };
2808
- const getSelectedOption = (value, options) => {
2809
- return options.find(option => {
2810
- // Polygons are compared by their coordinates directly
2811
- if (option.geometry.type === "Polygon" || option.geometry.type === "MultiPolygon") {
2812
- return JSON.stringify(option.geometry.coordinates) === JSON.stringify(value?.coordinates);
2813
- }
2814
- // All other geometries are stored by the filter as a
2815
- // polygon derived from the features bbox
2816
- // therefore we need to compare by that here to reverse the conversion
2817
- return JSON.stringify(geoJsonUtils.getPolygonFromBbox(option.bBox).coordinates) === JSON.stringify(value?.coordinates);
2818
- });
2819
- };
2820
-
2821
2689
  /**
2822
2690
  * Renders a filter view component with a header and optional additional elements for Asset IDs.
2823
2691
  *
@@ -4039,6 +3907,31 @@ const useRentalContractOrderNumberFilter = ({ showInFilterBar } = {
4039
3907
  return result;
4040
3908
  };
4041
3909
 
3910
+ /**
3911
+ * A button to clear recent searches.
3912
+ *
3913
+ * @param {ClearItemButtonProps} props - The props for the ClearItemButton component
3914
+ * @returns {ReactElement} ClearItemButton component
3915
+ */
3916
+ const ClearItemButton = ({ onClick }) => {
3917
+ const [t] = useTranslation();
3918
+ return (jsxRuntime.jsx(reactComponents.Tooltip, { label: t("filters.shared.clear"), children: jsxRuntime.jsx(reactComponents.IconButton, { className: "rounded-full", icon: jsxRuntime.jsx(reactComponents.Icon, { name: "Trash", size: "small" }), onClick: onClick, size: "small", variant: "ghost-neutral" }) }));
3919
+ };
3920
+
3921
+ /**
3922
+ * A footer component that displays a count of recent searches and a clear all button.
3923
+ *
3924
+ * @param {RecentSearchesFooterProps} props - The props for the ClearAllFooter component
3925
+ * @returns {ReactElement} RecentSearchesFooter component
3926
+ */
3927
+ const RecentSearchesFooter = ({ count, onClearAll }) => {
3928
+ const [t] = useTranslation();
3929
+ if (count === 0) {
3930
+ return null;
3931
+ }
3932
+ return (jsxRuntime.jsxs("div", { className: "flex items-baseline justify-between gap-x-2 border-t-2 border-neutral-300 px-2 py-0.5", children: [jsxRuntime.jsx(reactComponents.Text, { className: "py-0.5", size: "small", subtle: true, children: t("filters.shared.recentSearches") }), count > 1 ? (jsxRuntime.jsx(reactComponents.Button, { onClick: onClearAll, size: "small", variant: "ghost", children: t("filters.shared.clearAll") })) : null] }));
3933
+ };
3934
+
4042
3935
  const MAX_RECENT_SEARCHES$1 = 20;
4043
3936
  /**
4044
3937
  * Rental contract reference code description filter view component.
@@ -5336,7 +5229,7 @@ const mockForGetAccessManagementModeDesiredSummaryQuery = (variables, data) => {
5336
5229
  *
5337
5230
  * @returns {FilterBarDefinition} Default asset filter bar definition
5338
5231
  */
5339
- const useDefaultAssetFilterBarDefinition = ({ sitesEnabled, owningDepotEnabled, serviceManagementEnabled, areaShowWithSearch, ownerFilterDefaultValue, showHiddenAssetsEnabled, }) => {
5232
+ const useDefaultAssetFilterBarDefinition = ({ sitesEnabled, owningDepotEnabled, serviceManagementEnabled, ownerFilterDefaultValue, showHiddenAssetsEnabled, }) => {
5340
5233
  const assetType = useAssetTypeFilter();
5341
5234
  const metadataCompleteness = useMetadataCompletenessFilter();
5342
5235
  const groups = useGroupIdsFilter();
@@ -5368,12 +5261,7 @@ const useDefaultAssetFilterBarDefinition = ({ sitesEnabled, owningDepotEnabled,
5368
5261
  const ownerAccountIds = useOwnerAccountIdsFilter(ownerFilterDefaultValue);
5369
5262
  const searchProps = react.useMemo(() => ({ localStorageKey: "assetSearch" }), []);
5370
5263
  const search = useSearchFilter(searchProps);
5371
- const areaEnabledProps = react.useMemo(() => {
5372
- return {
5373
- showWithSearch: () => areaShowWithSearch ?? false,
5374
- };
5375
- }, [areaShowWithSearch]);
5376
- const area = useAreaFilter(areaEnabledProps);
5264
+ const area = useAreaFilter();
5377
5265
  const productionYears = useProductionYearFilter();
5378
5266
  const partner = usePartnerFilter();
5379
5267
  const serviceManagementEnabledProps = react.useMemo(() => {
package/index.esm.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { registerTranslations, useNamespaceTranslation } from '@trackunit/i18n-library-translation';
3
3
  import { gql } from '@apollo/client';
4
- import { DefaultCheckboxFilter, FilterEvents, FilterHeader, FilterResults, HierarchicalCheckboxFilter, DefaultRadioFilter } from '@trackunit/filters-filter-bar';
4
+ import { DefaultCheckboxFilter, FilterEvents, FilterHeader, HierarchicalCheckboxFilter, DefaultRadioFilter } from '@trackunit/filters-filter-bar';
5
5
  import { useActiveAssetFilters, useAssetQueryFilters } from '@trackunit/filters-graphql-hook';
6
6
  import { useQuery } from '@trackunit/react-graphql-hooks';
7
7
  import { useMemo, useState, useCallback, useEffect } from 'react';
@@ -9,10 +9,10 @@ import { useAnalytics, useCurrentUser } from '@trackunit/react-core-hooks';
9
9
  import { FilterBody, RadioFilterItem } from '@trackunit/react-filter-components';
10
10
  import { RadioGroup, RadioItem, Search } from '@trackunit/react-form-components';
11
11
  import { ActivityIndicator } from '@trackunit/utilization-indicator';
12
- import { geoJsonPolygonSchema, geoJsonPointSchema, geoJsonMultiPolygonSchema, geoJsonBboxSchema, getBboxFromGeoJsonPolygon, getPolygonFromBbox } from '@trackunit/geo-json-utils';
13
- import { Tooltip, IconButton, Icon, Text, Button, useLocalStorage, useDebounce, usePrevious, MenuItem, useMeasure, Popover, PopoverTrigger, PopoverContent, MenuList } from '@trackunit/react-components';
14
- import { titleCase, lowerCase } from 'string-ts';
12
+ import { geoJsonPolygonSchema, geoJsonPointSchema, geoJsonMultiPolygonSchema, geoJsonBboxSchema } from '@trackunit/geo-json-utils';
15
13
  import { z } from 'zod';
14
+ import { useLocalStorage, useDebounce, Tooltip, IconButton, Icon, Text, Button, usePrevious, MenuItem, useMeasure, Popover, PopoverTrigger, PopoverContent, MenuList } from '@trackunit/react-components';
15
+ import { titleCase, lowerCase } from 'string-ts';
16
16
  import { objectValues, enumFromValue, nonNullable, hourIntervals } from '@trackunit/shared-utils';
17
17
  import { useMachineTypeTranslations } from '@trackunit/translations-machine-type';
18
18
  import { CriticalityIndicator } from '@trackunit/criticality-indicator';
@@ -2474,28 +2474,35 @@ const useActivityFilter = () => {
2474
2474
  };
2475
2475
 
2476
2476
  /**
2477
- * A button to clear recent searches.
2477
+ * Renders the AreaView component, which manages the UI and logic for an area filter.
2478
2478
  *
2479
- * @param {ClearItemButtonProps} props - The props for the ClearItemButton component
2480
- * @returns {ReactElement} ClearItemButton component
2479
+ * @param {FilterViewProps<AreaFilterGeoJsonGeometry>} props - The props for the component.
2480
+ * @returns {ReactElement} The rendered AreaView component.
2481
2481
  */
2482
- const ClearItemButton = ({ onClick }) => {
2482
+ const AreaView = (props) => {
2483
2483
  const [t] = useTranslation();
2484
- return (jsx(Tooltip, { label: t("filters.shared.clear"), children: jsx(IconButton, { className: "rounded-full", icon: jsx(Icon, { name: "Trash", size: "small" }), onClick: onClick, size: "small", variant: "ghost-neutral" }) }));
2484
+ return (jsxs(Fragment, { children: [jsx(FilterHeader, { ...props.filterDefinition, ...props.filterBarActions, filterHasChanges: props.filterBarActions.appliedFilterKeys().includes(props.filterDefinition.filterKey) }), jsx(FilterBody, { limitSize: true, children: jsx("p", { className: "p-2", children: `${t("assetFilters.boundingBoxFilter.value")}` }) })] }));
2485
2485
  };
2486
-
2487
2486
  /**
2488
- * A footer component that displays a count of recent searches and a clear all button.
2487
+ * Area filter definition
2489
2488
  *
2490
- * @param {RecentSearchesFooterProps} props - The props for the ClearAllFooter component
2491
- * @returns {ReactElement} RecentSearchesFooter component
2489
+ * @returns {AreaFilterDefinition} Area filter definition
2492
2490
  */
2493
- const RecentSearchesFooter = ({ count, onClearAll }) => {
2491
+ const useAreaFilter = () => {
2494
2492
  const [t] = useTranslation();
2495
- if (count === 0) {
2496
- return null;
2497
- }
2498
- return (jsxs("div", { className: "flex items-baseline justify-between gap-x-2 border-t-2 border-neutral-300 px-2 py-0.5", children: [jsx(Text, { className: "py-0.5", size: "small", subtle: true, children: t("filters.shared.recentSearches") }), count > 1 ? (jsx(Button, { onClick: onClearAll, size: "small", variant: "ghost", children: t("filters.shared.clearAll") })) : null] }));
2493
+ const result = useMemo(() => {
2494
+ return {
2495
+ filterKey: "area",
2496
+ type: "area",
2497
+ group: "CURRENT_LOCATION",
2498
+ title: t("assetFilters.area.label"),
2499
+ valueAsText: value => {
2500
+ return t("assetFilters.boundingBoxFilter.value");
2501
+ },
2502
+ component: props => jsx(AreaView, { ...props }),
2503
+ };
2504
+ }, [t]);
2505
+ return result;
2499
2506
  };
2500
2507
 
2501
2508
  const areaFilterInternalGeoJsonGeometrySchema = z.union([
@@ -2677,145 +2684,6 @@ const usePlacesSearch = ({ localStorageKey = SHARED_LOCATIONS_LOCAL_STORAGE_KEY,
2677
2684
  ]);
2678
2685
  };
2679
2686
 
2680
- /**
2681
- * Renders the AreaView component, which manages the UI and logic for an area filter
2682
- * with optional search functionality.
2683
- *
2684
- * @param {FilterViewProps<AreaFilterGeoJsonGeometry> & { showWithSearch: boolean }} props - The props for the component.
2685
- * @returns {ReactElement} The rendered AreaView component.
2686
- */
2687
- const AreaView = (props) => {
2688
- const [t] = useTranslation();
2689
- const { logEvent } = useAnalytics(FilterEvents);
2690
- const { places, recentPlaces, setRecentPlaces, selectedPlaceData, setSelectedPrediction, searchString, setSearchString, debouncedSearchString, loading, hint, } = usePlacesSearch({
2691
- localStorageKey: "shared-recent-locations",
2692
- });
2693
- const placeholderOption = useMemo(() => {
2694
- // If no selected option, use this placeholder option
2695
- // Aka if applied from outside the filter UI
2696
- if (!props.value) {
2697
- return null;
2698
- }
2699
- const placeholderBbox = getBboxFromGeoJsonPolygon(props.value);
2700
- return placeholderBbox
2701
- ? {
2702
- bBox: placeholderBbox,
2703
- geometry: props.value,
2704
- properties: { formattedAddress: t("assetFilters.area.origin.mapArea") },
2705
- dateIsoString: undefined,
2706
- placeId: "applied-map-area",
2707
- description: t("assetFilters.area.origin.mapArea"),
2708
- }
2709
- : null;
2710
- }, [props.value, t]);
2711
- const selectedOption = useMemo(() => getSelectedOption(props.value, recentPlaces), [props.value, recentPlaces]);
2712
- const setFilter = useCallback((place) => {
2713
- const { properties, bBox, geometry, dateIsoString } = place;
2714
- if (geometry.type === "Polygon") {
2715
- // Polygons are stored directly
2716
- props.filterBarActions.setArea(geometry);
2717
- }
2718
- else {
2719
- // All other geometries are stored by the filter as a
2720
- // polygon derived from the features bbox
2721
- props.filterBarActions.setArea(getPolygonFromBbox(bBox));
2722
- }
2723
- logEvent("Filters Applied - V2", {
2724
- type: "AreaFilter",
2725
- value: properties.formattedAddress,
2726
- additionalProperties: {
2727
- geometryType: geometry.type,
2728
- wasPreviousSearch: Boolean(dateIsoString),
2729
- },
2730
- });
2731
- }, [props.filterBarActions, logEvent]);
2732
- useEffect(() => {
2733
- if (!selectedPlaceData) {
2734
- return;
2735
- }
2736
- if (selectedPlaceData.placeId === selectedOption?.placeId) {
2737
- return;
2738
- }
2739
- setFilter(selectedPlaceData);
2740
- }, [selectedPlaceData, setFilter, selectedOption]);
2741
- if (!props.showWithSearch) {
2742
- // Temporary while we are testing the new area filter bar behind feature flag
2743
- // This emulated the old look of the boundingbox filter
2744
- // TODO [BUSS] Remove this once the new area filter is ready for prime time
2745
- return (jsxs(Fragment, { children: [jsx(FilterHeader, { ...props.filterDefinition, ...props.filterBarActions, filterHasChanges: props.filterBarActions.appliedFilterKeys().includes(props.filterDefinition.filterKey) }), jsx(FilterBody, { limitSize: true, children: jsx("p", { className: "p-2", children: `${t("assetFilters.boundingBoxFilter.value")}` }) })] }));
2746
- }
2747
- return (jsxs("div", { children: [jsx(FilterHeader, { ...props.filterDefinition, ...props.filterBarActions, filterHasChanges: props.filterBarActions.appliedFilterKeys().includes(props.filterDefinition.filterKey), loading: loading, searchEnabled: true, searchProps: {
2748
- onChange: setSearchString,
2749
- onClear: () => setSearchString(""),
2750
- value: searchString,
2751
- count: places.length,
2752
- placeholder: t("assetFilters.area.searchPlaceholder"),
2753
- } }), jsx(FilterBody, { limitSize: true, children: places.length > 0 || placeholderOption ? (jsx(FilterResults, { loading: false, results: [
2754
- // Show placeholder option first if selected option is not in filterResult
2755
- // Aka if applied from outside the filter UI
2756
- ...(placeholderOption && !selectedOption && !debouncedSearchString ? [placeholderOption] : []),
2757
- ...places,
2758
- ], children: filterResult => (jsx(RadioGroup, { className: "m-1", id: "areaFilter", onChange: e => {
2759
- const findOption = filterResult.find(result => result.description === e.currentTarget.value);
2760
- findOption &&
2761
- setSelectedPrediction({
2762
- placeId: findOption.placeId,
2763
- description: findOption.description,
2764
- });
2765
- setSearchString(""); // Clear search input after selection
2766
- }, value: selectedOption?.description ??
2767
- placeholderOption?.description ?? // placeholder if no selected option
2768
- "", children: filterResult.map(({ ...rest }, index) => {
2769
- const selected = selectedOption?.description === rest.description;
2770
- return (jsx(RadioFilterItem, { label: rest.description, selected: selected, suffix:
2771
- // Only show clear button if not currently selected and previously searched
2772
- !selected && "dateIsoString" in rest ? (jsx(ClearItemButton, { onClick: () => setRecentPlaces(prev => prev.filter(r => r.description !== rest.description)) })) : null, value: rest.description }, index));
2773
- }) })) })) : (jsx("p", { className: "py-2 text-center text-sm text-neutral-400", children: hint })) }), debouncedSearchString.length === 0 ? (jsx(RecentSearchesFooter, { count: recentPlaces.length, onClearAll: () => setRecentPlaces([]) })) : null] }));
2774
- };
2775
- /**
2776
- * Area filter definition
2777
- *
2778
- * @returns {AreaFilterDefinition} Area filter definition
2779
- */
2780
- const useAreaFilter = ({ showWithSearch } = {
2781
- showWithSearch: () => true,
2782
- }) => {
2783
- const [t] = useTranslation();
2784
- const { recentPlaces } = usePlacesSearch({
2785
- localStorageKey: "shared-recent-locations",
2786
- });
2787
- const result = useMemo(() => {
2788
- return {
2789
- filterKey: "area",
2790
- type: "area",
2791
- group: "CURRENT_LOCATION",
2792
- title: t("assetFilters.area.label"),
2793
- showInFilterBar: () => showWithSearch(),
2794
- valueAsText: value => {
2795
- // Match the applied area filter to the places option if it exists in the recent places
2796
- const nameOfSelectedArea = getSelectedOption(value, recentPlaces);
2797
- return showWithSearch()
2798
- ? (nameOfSelectedArea?.description ?? t("assetFilters.area.active"))
2799
- : t("assetFilters.boundingBoxFilter.value");
2800
- },
2801
- component: props => jsx(AreaView, { ...props, showWithSearch: showWithSearch() }),
2802
- };
2803
- }, [t, showWithSearch, recentPlaces]);
2804
- return result;
2805
- };
2806
- const getSelectedOption = (value, options) => {
2807
- return options.find(option => {
2808
- // Polygons are compared by their coordinates directly
2809
- if (option.geometry.type === "Polygon" || option.geometry.type === "MultiPolygon") {
2810
- return JSON.stringify(option.geometry.coordinates) === JSON.stringify(value?.coordinates);
2811
- }
2812
- // All other geometries are stored by the filter as a
2813
- // polygon derived from the features bbox
2814
- // therefore we need to compare by that here to reverse the conversion
2815
- return JSON.stringify(getPolygonFromBbox(option.bBox).coordinates) === JSON.stringify(value?.coordinates);
2816
- });
2817
- };
2818
-
2819
2687
  /**
2820
2688
  * Renders a filter view component with a header and optional additional elements for Asset IDs.
2821
2689
  *
@@ -4037,6 +3905,31 @@ const useRentalContractOrderNumberFilter = ({ showInFilterBar } = {
4037
3905
  return result;
4038
3906
  };
4039
3907
 
3908
+ /**
3909
+ * A button to clear recent searches.
3910
+ *
3911
+ * @param {ClearItemButtonProps} props - The props for the ClearItemButton component
3912
+ * @returns {ReactElement} ClearItemButton component
3913
+ */
3914
+ const ClearItemButton = ({ onClick }) => {
3915
+ const [t] = useTranslation();
3916
+ return (jsx(Tooltip, { label: t("filters.shared.clear"), children: jsx(IconButton, { className: "rounded-full", icon: jsx(Icon, { name: "Trash", size: "small" }), onClick: onClick, size: "small", variant: "ghost-neutral" }) }));
3917
+ };
3918
+
3919
+ /**
3920
+ * A footer component that displays a count of recent searches and a clear all button.
3921
+ *
3922
+ * @param {RecentSearchesFooterProps} props - The props for the ClearAllFooter component
3923
+ * @returns {ReactElement} RecentSearchesFooter component
3924
+ */
3925
+ const RecentSearchesFooter = ({ count, onClearAll }) => {
3926
+ const [t] = useTranslation();
3927
+ if (count === 0) {
3928
+ return null;
3929
+ }
3930
+ return (jsxs("div", { className: "flex items-baseline justify-between gap-x-2 border-t-2 border-neutral-300 px-2 py-0.5", children: [jsx(Text, { className: "py-0.5", size: "small", subtle: true, children: t("filters.shared.recentSearches") }), count > 1 ? (jsx(Button, { onClick: onClearAll, size: "small", variant: "ghost", children: t("filters.shared.clearAll") })) : null] }));
3931
+ };
3932
+
4040
3933
  const MAX_RECENT_SEARCHES$1 = 20;
4041
3934
  /**
4042
3935
  * Rental contract reference code description filter view component.
@@ -5334,7 +5227,7 @@ const mockForGetAccessManagementModeDesiredSummaryQuery = (variables, data) => {
5334
5227
  *
5335
5228
  * @returns {FilterBarDefinition} Default asset filter bar definition
5336
5229
  */
5337
- const useDefaultAssetFilterBarDefinition = ({ sitesEnabled, owningDepotEnabled, serviceManagementEnabled, areaShowWithSearch, ownerFilterDefaultValue, showHiddenAssetsEnabled, }) => {
5230
+ const useDefaultAssetFilterBarDefinition = ({ sitesEnabled, owningDepotEnabled, serviceManagementEnabled, ownerFilterDefaultValue, showHiddenAssetsEnabled, }) => {
5338
5231
  const assetType = useAssetTypeFilter();
5339
5232
  const metadataCompleteness = useMetadataCompletenessFilter();
5340
5233
  const groups = useGroupIdsFilter();
@@ -5366,12 +5259,7 @@ const useDefaultAssetFilterBarDefinition = ({ sitesEnabled, owningDepotEnabled,
5366
5259
  const ownerAccountIds = useOwnerAccountIdsFilter(ownerFilterDefaultValue);
5367
5260
  const searchProps = useMemo(() => ({ localStorageKey: "assetSearch" }), []);
5368
5261
  const search = useSearchFilter(searchProps);
5369
- const areaEnabledProps = useMemo(() => {
5370
- return {
5371
- showWithSearch: () => areaShowWithSearch ?? false,
5372
- };
5373
- }, [areaShowWithSearch]);
5374
- const area = useAreaFilter(areaEnabledProps);
5262
+ const area = useAreaFilter();
5375
5263
  const productionYears = useProductionYearFilter();
5376
5264
  const partner = usePartnerFilter();
5377
5265
  const serviceManagementEnabledProps = useMemo(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/filters-asset-filter-definitions",
3
- "version": "1.12.0",
3
+ "version": "1.12.4",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -15,23 +15,23 @@
15
15
  "zod": "^3.23.8",
16
16
  "string-ts": "^2.0.0",
17
17
  "tailwind-merge": "^2.0.0",
18
- "@trackunit/iris-app-build-utilities": "1.7.113",
19
- "@trackunit/filters-filter-bar": "1.8.167",
20
- "@trackunit/react-core-hooks": "1.7.121",
21
- "@trackunit/react-filter-components": "1.7.161",
22
- "@trackunit/react-form-components": "1.8.158",
23
- "@trackunit/filters-graphql-hook": "1.11.110",
24
- "@trackunit/utilization-indicator": "1.7.147",
25
- "@trackunit/geo-json-utils": "1.7.107",
26
- "@trackunit/react-components": "1.10.83",
27
- "@trackunit/shared-utils": "1.9.107",
28
- "@trackunit/translations-machine-type": "1.7.140",
29
- "@trackunit/criticality-indicator": "1.7.147",
30
- "@trackunit/iris-app-api": "1.10.0",
31
- "@trackunit/react-core-contexts-test": "1.7.121",
32
- "@trackunit/i18n-library-translation": "1.7.128",
33
- "@trackunit/iris-app-runtime-core-api": "1.7.117",
34
- "@trackunit/react-graphql-hooks": "1.7.148"
18
+ "@trackunit/iris-app-build-utilities": "1.7.116",
19
+ "@trackunit/filters-filter-bar": "1.8.170",
20
+ "@trackunit/react-core-hooks": "1.7.124",
21
+ "@trackunit/react-filter-components": "1.7.164",
22
+ "@trackunit/react-form-components": "1.8.161",
23
+ "@trackunit/filters-graphql-hook": "1.11.113",
24
+ "@trackunit/utilization-indicator": "1.7.150",
25
+ "@trackunit/geo-json-utils": "1.7.110",
26
+ "@trackunit/react-components": "1.10.86",
27
+ "@trackunit/shared-utils": "1.9.110",
28
+ "@trackunit/translations-machine-type": "1.7.143",
29
+ "@trackunit/criticality-indicator": "1.7.150",
30
+ "@trackunit/iris-app-api": "1.10.3",
31
+ "@trackunit/react-core-contexts-test": "1.7.124",
32
+ "@trackunit/i18n-library-translation": "1.7.131",
33
+ "@trackunit/iris-app-runtime-core-api": "1.7.120",
34
+ "@trackunit/react-graphql-hooks": "1.7.151"
35
35
  },
36
36
  "module": "./index.esm.js",
37
37
  "main": "./index.cjs.js",
@@ -1,22 +1,15 @@
1
1
  import { AreaFilterDefinition, AreaFilterGeoJsonGeometry, FilterViewProps } from "@trackunit/filters-filter-bar";
2
2
  import { ReactElement } from "react";
3
3
  /**
4
- * Renders the AreaView component, which manages the UI and logic for an area filter
5
- * with optional search functionality.
4
+ * Renders the AreaView component, which manages the UI and logic for an area filter.
6
5
  *
7
- * @param {FilterViewProps<AreaFilterGeoJsonGeometry> & { showWithSearch: boolean }} props - The props for the component.
6
+ * @param {FilterViewProps<AreaFilterGeoJsonGeometry>} props - The props for the component.
8
7
  * @returns {ReactElement} The rendered AreaView component.
9
8
  */
10
- export declare const AreaView: (props: FilterViewProps<AreaFilterGeoJsonGeometry> & {
11
- showWithSearch: boolean;
12
- }) => ReactElement;
13
- interface AreaFilterProps {
14
- showWithSearch: () => boolean;
15
- }
9
+ export declare const AreaView: (props: FilterViewProps<AreaFilterGeoJsonGeometry>) => ReactElement;
16
10
  /**
17
11
  * Area filter definition
18
12
  *
19
13
  * @returns {AreaFilterDefinition} Area filter definition
20
14
  */
21
- export declare const useAreaFilter: ({ showWithSearch }?: AreaFilterProps) => AreaFilterDefinition;
22
- export {};
15
+ export declare const useAreaFilter: () => AreaFilterDefinition;
@@ -13,10 +13,6 @@ interface DefaultAssetFilterBarDefinitionProps {
13
13
  * A flag indicating whether service management filters are enabled
14
14
  */
15
15
  serviceManagementEnabled: boolean | null;
16
- /**
17
- * A flag indicating whether area filter is enabled
18
- */
19
- areaShowWithSearch: boolean | null;
20
16
  /**
21
17
  * A flag indicating whether showing hidden assets is enabled
22
18
  */
@@ -31,7 +27,7 @@ interface DefaultAssetFilterBarDefinitionProps {
31
27
  *
32
28
  * @returns {FilterBarDefinition} Default asset filter bar definition
33
29
  */
34
- export declare const useDefaultAssetFilterBarDefinition: ({ sitesEnabled, owningDepotEnabled, serviceManagementEnabled, areaShowWithSearch, ownerFilterDefaultValue, showHiddenAssetsEnabled, }: DefaultAssetFilterBarDefinitionProps) => {
30
+ export declare const useDefaultAssetFilterBarDefinition: ({ sitesEnabled, owningDepotEnabled, serviceManagementEnabled, ownerFilterDefaultValue, showHiddenAssetsEnabled, }: DefaultAssetFilterBarDefinitionProps) => {
35
31
  assetType: import("@trackunit/filters-filter-bar").StringArrayFilterDefinition;
36
32
  brands: import("@trackunit/filters-filter-bar").StringArrayFilterDefinition;
37
33
  models: import("@trackunit/filters-filter-bar").StringArrayFilterDefinition;
@@ -163,7 +163,7 @@ var translation = {
163
163
  "fleetlist.column.customerIds": "Клиенты",
164
164
  "fleetlist.column.externalReference": "Внешняя ссылка",
165
165
  "fleetlist.column.groupIds": "Группы",
166
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
166
+ "fleetlist.column.lastSeen": "Последняя передача (GPS)",
167
167
  "fleetlist.column.metadata": "Метаданные",
168
168
  "fleetlist.column.metadataCompleteness": "Полнота метаданных",
169
169
  "fleetlist.column.model": "Модель",
@@ -163,7 +163,7 @@ var translation = {
163
163
  "fleetlist.column.customerIds": "Clienți",
164
164
  "fleetlist.column.externalReference": "Referință externă",
165
165
  "fleetlist.column.groupIds": "Grupuri",
166
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
166
+ "fleetlist.column.lastSeen": "Văzut ultima dată (GPS)",
167
167
  "fleetlist.column.metadata": "Metadate",
168
168
  "fleetlist.column.metadataCompleteness": "Completitudinea metadatelor",
169
169
  "fleetlist.column.model": "Model",
@@ -163,7 +163,7 @@ var translation = {
163
163
  "fleetlist.column.customerIds": "Clientes",
164
164
  "fleetlist.column.externalReference": "Referencia externa",
165
165
  "fleetlist.column.groupIds": "Grupos",
166
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
166
+ "fleetlist.column.lastSeen": "Visto por última vez (GPS)",
167
167
  "fleetlist.column.metadata": "Metadatos",
168
168
  "fleetlist.column.metadataCompleteness": "Exhaustividad de los metadatos",
169
169
  "fleetlist.column.model": "Modelo",
@@ -163,7 +163,7 @@ var translation = {
163
163
  "fleetlist.column.customerIds": "Kunder",
164
164
  "fleetlist.column.externalReference": "Extern referens",
165
165
  "fleetlist.column.groupIds": "Grupper",
166
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
166
+ "fleetlist.column.lastSeen": "Senast sedd (GPS)",
167
167
  "fleetlist.column.metadata": "Metadata",
168
168
  "fleetlist.column.metadataCompleteness": "Metadatans fullständighet",
169
169
  "fleetlist.column.model": "Modell",
@@ -163,7 +163,7 @@ var translation = {
163
163
  "fleetlist.column.customerIds": "ลูกค้า",
164
164
  "fleetlist.column.externalReference": "ข้อมูลอ้างอิงภายนอก",
165
165
  "fleetlist.column.groupIds": "กลุ่ม",
166
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
166
+ "fleetlist.column.lastSeen": "ดูล่าสุด (GPS)",
167
167
  "fleetlist.column.metadata": "ข้อมูลอภิพันธุ์",
168
168
  "fleetlist.column.metadataCompleteness": "ความสมบูรณ์ของข้อมูลจำเพาะ",
169
169
  "fleetlist.column.model": "รุ่น",
@@ -163,7 +163,7 @@ var translation = {
163
163
  "fleetlist.column.customerIds": "Zákazníci",
164
164
  "fleetlist.column.externalReference": "Externí reference",
165
165
  "fleetlist.column.groupIds": "Skupiny",
166
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
166
+ "fleetlist.column.lastSeen": "Naposledy spatřeno (GPS)",
167
167
  "fleetlist.column.metadata": "Metadata",
168
168
  "fleetlist.column.metadataCompleteness": "Úplnost metadat",
169
169
  "fleetlist.column.model": "Model",
@@ -163,7 +163,7 @@ var translation = {
163
163
  "fleetlist.column.customerIds": "Clients",
164
164
  "fleetlist.column.externalReference": "Référence externe",
165
165
  "fleetlist.column.groupIds": "Groupes",
166
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
166
+ "fleetlist.column.lastSeen": "Dernière détection (GPS)",
167
167
  "fleetlist.column.metadata": "Métadonnées",
168
168
  "fleetlist.column.metadataCompleteness": "Niveau de collecte des métadonnées",
169
169
  "fleetlist.column.model": "Modèle",
@@ -163,7 +163,7 @@ var translation = {
163
163
  "fleetlist.column.customerIds": "Clienti",
164
164
  "fleetlist.column.externalReference": "Riferimento esterno",
165
165
  "fleetlist.column.groupIds": "Gruppi",
166
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
166
+ "fleetlist.column.lastSeen": "Ultimo rilevamento (GPS)",
167
167
  "fleetlist.column.metadata": "Metadati",
168
168
  "fleetlist.column.metadataCompleteness": "Completezza dei metadati",
169
169
  "fleetlist.column.model": "Modello",
@@ -161,7 +161,7 @@ var translation = {
161
161
  "fleetlist.column.customerIds": "Клиенты",
162
162
  "fleetlist.column.externalReference": "Внешняя ссылка",
163
163
  "fleetlist.column.groupIds": "Группы",
164
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
164
+ "fleetlist.column.lastSeen": "Последняя передача (GPS)",
165
165
  "fleetlist.column.metadata": "Метаданные",
166
166
  "fleetlist.column.metadataCompleteness": "Полнота метаданных",
167
167
  "fleetlist.column.model": "Модель",
@@ -161,7 +161,7 @@ var translation = {
161
161
  "fleetlist.column.customerIds": "Clienți",
162
162
  "fleetlist.column.externalReference": "Referință externă",
163
163
  "fleetlist.column.groupIds": "Grupuri",
164
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
164
+ "fleetlist.column.lastSeen": "Văzut ultima dată (GPS)",
165
165
  "fleetlist.column.metadata": "Metadate",
166
166
  "fleetlist.column.metadataCompleteness": "Completitudinea metadatelor",
167
167
  "fleetlist.column.model": "Model",
@@ -161,7 +161,7 @@ var translation = {
161
161
  "fleetlist.column.customerIds": "Clientes",
162
162
  "fleetlist.column.externalReference": "Referencia externa",
163
163
  "fleetlist.column.groupIds": "Grupos",
164
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
164
+ "fleetlist.column.lastSeen": "Visto por última vez (GPS)",
165
165
  "fleetlist.column.metadata": "Metadatos",
166
166
  "fleetlist.column.metadataCompleteness": "Exhaustividad de los metadatos",
167
167
  "fleetlist.column.model": "Modelo",
@@ -161,7 +161,7 @@ var translation = {
161
161
  "fleetlist.column.customerIds": "Kunder",
162
162
  "fleetlist.column.externalReference": "Extern referens",
163
163
  "fleetlist.column.groupIds": "Grupper",
164
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
164
+ "fleetlist.column.lastSeen": "Senast sedd (GPS)",
165
165
  "fleetlist.column.metadata": "Metadata",
166
166
  "fleetlist.column.metadataCompleteness": "Metadatans fullständighet",
167
167
  "fleetlist.column.model": "Modell",
@@ -161,7 +161,7 @@ var translation = {
161
161
  "fleetlist.column.customerIds": "ลูกค้า",
162
162
  "fleetlist.column.externalReference": "ข้อมูลอ้างอิงภายนอก",
163
163
  "fleetlist.column.groupIds": "กลุ่ม",
164
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
164
+ "fleetlist.column.lastSeen": "ดูล่าสุด (GPS)",
165
165
  "fleetlist.column.metadata": "ข้อมูลอภิพันธุ์",
166
166
  "fleetlist.column.metadataCompleteness": "ความสมบูรณ์ของข้อมูลจำเพาะ",
167
167
  "fleetlist.column.model": "รุ่น",
@@ -161,7 +161,7 @@ var translation = {
161
161
  "fleetlist.column.customerIds": "Zákazníci",
162
162
  "fleetlist.column.externalReference": "Externí reference",
163
163
  "fleetlist.column.groupIds": "Skupiny",
164
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
164
+ "fleetlist.column.lastSeen": "Naposledy spatřeno (GPS)",
165
165
  "fleetlist.column.metadata": "Metadata",
166
166
  "fleetlist.column.metadataCompleteness": "Úplnost metadat",
167
167
  "fleetlist.column.model": "Model",
@@ -161,7 +161,7 @@ var translation = {
161
161
  "fleetlist.column.customerIds": "Clients",
162
162
  "fleetlist.column.externalReference": "Référence externe",
163
163
  "fleetlist.column.groupIds": "Groupes",
164
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
164
+ "fleetlist.column.lastSeen": "Dernière détection (GPS)",
165
165
  "fleetlist.column.metadata": "Métadonnées",
166
166
  "fleetlist.column.metadataCompleteness": "Niveau de collecte des métadonnées",
167
167
  "fleetlist.column.model": "Modèle",
@@ -161,7 +161,7 @@ var translation = {
161
161
  "fleetlist.column.customerIds": "Clienti",
162
162
  "fleetlist.column.externalReference": "Riferimento esterno",
163
163
  "fleetlist.column.groupIds": "Gruppi",
164
- "fleetlist.column.lastSeen": "Last Seen (GPS)",
164
+ "fleetlist.column.lastSeen": "Ultimo rilevamento (GPS)",
165
165
  "fleetlist.column.metadata": "Metadati",
166
166
  "fleetlist.column.metadataCompleteness": "Completezza dei metadati",
167
167
  "fleetlist.column.model": "Modello",