From 97b90ae283285a7301f0f5eaed90215041190e0b Mon Sep 17 00:00:00 2001 From: Skasix00 Date: Wed, 29 Jan 2025 09:37:15 +0000 Subject: [PATCH 01/22] Created the base structure for vehicles page --- .../app/(views)/(website)/vehicles/layout.tsx | 14 ++ .../app/(views)/(website)/vehicles/page.tsx | 11 ++ .../components/news/NewsListToolbar/index.tsx | 85 +++++++++++ .../vehicles/VehicleListToolbar/index.tsx | 80 ++++++++++ .../vehicles/VehiclesList/index.tsx | 33 ++++ .../vehicles/VehiclesList/styles.module.css | 48 ++++++ .../vehicles/VehiclesListGroups/index.tsx | 88 +++++++++++ .../vehicles/VehiclesListMap/index.tsx | 137 +++++++++++++++++ frontend/contexts/NewsList.context.tsx | 141 ++++++++++++++++++ frontend/i18n/translations/en.json | 3 +- frontend/i18n/translations/pt.json | 3 +- frontend/settings/navigation.settings.tsx | 3 +- 12 files changed, 643 insertions(+), 3 deletions(-) create mode 100644 frontend/app/(views)/(website)/vehicles/layout.tsx create mode 100644 frontend/app/(views)/(website)/vehicles/page.tsx create mode 100644 frontend/components/news/NewsListToolbar/index.tsx create mode 100644 frontend/components/vehicles/VehicleListToolbar/index.tsx create mode 100644 frontend/components/vehicles/VehiclesList/index.tsx create mode 100644 frontend/components/vehicles/VehiclesList/styles.module.css create mode 100644 frontend/components/vehicles/VehiclesListGroups/index.tsx create mode 100644 frontend/components/vehicles/VehiclesListMap/index.tsx create mode 100644 frontend/contexts/NewsList.context.tsx diff --git a/frontend/app/(views)/(website)/vehicles/layout.tsx b/frontend/app/(views)/(website)/vehicles/layout.tsx new file mode 100644 index 00000000..e2116570 --- /dev/null +++ b/frontend/app/(views)/(website)/vehicles/layout.tsx @@ -0,0 +1,14 @@ +/* * */ + +import { StoresListContextProvider } from '@/contexts/StoresList.context'; +import { VehiclesContextProvider } from '@/contexts/Vehicles.context'; + +/* * */ + +export default function Layout({ children }) { + return ( + + {children} + + ); +} diff --git a/frontend/app/(views)/(website)/vehicles/page.tsx b/frontend/app/(views)/(website)/vehicles/page.tsx new file mode 100644 index 00000000..d9819f13 --- /dev/null +++ b/frontend/app/(views)/(website)/vehicles/page.tsx @@ -0,0 +1,11 @@ +/* * */ + +import VehiclesList from '@/components/vehicles/VehiclesList'; + +/* * */ + +export default function Page() { + return ( + + ); +} diff --git a/frontend/components/news/NewsListToolbar/index.tsx b/frontend/components/news/NewsListToolbar/index.tsx new file mode 100644 index 00000000..319bc1c7 --- /dev/null +++ b/frontend/components/news/NewsListToolbar/index.tsx @@ -0,0 +1,85 @@ +'use client'; + +/* * */ + +import { FoundItemsCounter } from '@/components/common/FoundItemsCounter'; +import { Section } from '@/components/layout/Section'; +import { useNewsListContext } from '@/contexts/NewsList.context'; +import { TextInput } from '@mantine/core'; +import { DatePickerInput } from '@mantine/dates'; +import { IconArrowLoopRight, IconCalendarEvent } from '@tabler/icons-react'; +import { useTranslations } from 'next-intl'; +import { useEffect, useRef, useState } from 'react'; + +import styles from './styles.module.css'; + +/* * */ + +export function NewsListToolbar() { + // A. Setup variables + const newsListContext = useNewsListContext(); + const t = useTranslations('news.NewsListToolbar'); + const typingTimeoutRef = useRef>(null); + const [textInput, setTextInput] = useState(null); + const [dateInput, setDateInput] = useState(null); + // + // B . Handle Actions + + const handleTextInputChange = ({ currentTarget }) => { + setTextInput(currentTarget.value); + if (!newsListContext.flags.is_loading && newsListContext.data.raw) { + try { + if (typingTimeoutRef.current) { + clearTimeout(typingTimeoutRef.current); + } + typingTimeoutRef.current = setTimeout(() => { + newsListContext.actions.updateFilterByTitle(currentTarget.value); + console.log('News filtered with success ✅'); + }, 500); + } + catch (error) { + console.error('Filtering failed ❌: ', error); + } + } + }; + + const handleDateInputChange = (value: Date | null) => { + setDateInput(value); + newsListContext.actions.updateFilterByDate(value || new Date()); + }; + + // C. Cleanup; + useEffect(() => { + return () => { + if (typingTimeoutRef.current) { + clearTimeout(typingTimeoutRef.current); + } + }; + }, []); + + // D. Render Components + + return ( + <> +
+
+ } onChange={handleTextInputChange} placeholder={t('SearchInput.placeholder')} type="search" value={textInput || ''} w="70%" /> + } + onChange={handleDateInputChange} + placeholder={t('DateInput.placeholder')} + size="lg" + value={dateInput} + valueFormat="DD MMM YYYY" + variant="unstyled" + /> +
+ +
+ + + ); + // +} diff --git a/frontend/components/vehicles/VehicleListToolbar/index.tsx b/frontend/components/vehicles/VehicleListToolbar/index.tsx new file mode 100644 index 00000000..19e5eaba --- /dev/null +++ b/frontend/components/vehicles/VehicleListToolbar/index.tsx @@ -0,0 +1,80 @@ +'use client'; + +/* * */ + +import { FoundItemsCounter } from '@/components/common/FoundItemsCounter'; +import SortButton from '@/components/common/SortButton'; +import { Grid } from '@/components/layout/Grid'; +import { Section } from '@/components/layout/Section'; +import { Surface } from '@/components/layout/Surface'; +import { useStoresListContext } from '@/contexts/StoresList.context'; +import { SegmentedControl, Select } from '@mantine/core'; +import { IconMap } from '@tabler/icons-react'; +import { useTranslations } from 'next-intl'; +import { useMemo } from 'react'; + +/* * */ + +export default function Component() { + // + + // + // A. Setup variables + + const t = useTranslations('stores.StoresListToolbar'); + const storesContext = useStoresListContext(); + + // + // B. Transform data + + const byCurrentStatusOptions = [ + { label: t('by_current_status.open', { count: storesContext.counters.by_current_status.open }), value: 'open' }, + { label: t('by_current_status.all'), value: 'all' }, + ]; + + const filterByOptions = [ + { label: t('filter_by.municipality_name'), value: 'municipality_name' }, + { label: t('filter_by.capacity'), value: 'capacity' }, + { label: t('filter_by.time'), value: 'wait_time' }, + ]; + + const byMunicipalityOptions = useMemo(() => { + if (!storesContext.data.raw) return []; + const uniqueMunicipalities = new Set(); + const result: { disabled: boolean, label: string, value: string }[] = []; + storesContext.data.raw.forEach((store) => { + if (!uniqueMunicipalities.has(store.municipality_id)) { + uniqueMunicipalities.add(store.municipality_id); + result.push({ + disabled: storesContext.data.raw.filter(item => item.municipality_id === store.municipality_id).length === 0, + label: store.municipality_name, + value: store.municipality_id, + }); + } + }); + return result.sort((a, b) => a.label.localeCompare(b.label)); + }, [storesContext.data.raw]); + + // + // C. Render components + + return ( + +
+ + + } onChange={storesContext.actions.updateFilterByMunicipality} placeholder={t('by_municipality.label')} value={storesContext.filters.by_municipality} clearable searchable /> - + +
diff --git a/frontend/i18n/translations/en.json b/frontend/i18n/translations/en.json index b2a911d8..28321da0 100644 --- a/frontend/i18n/translations/en.json +++ b/frontend/i18n/translations/en.json @@ -1512,5 +1512,15 @@ }, "utils": { "number": "{value, number}" + }, + "vehicles": { + "VehiclesListToolbar": { + "filter_by": { + "search": "Search by plates, number os passengers, vehicle number." + }, + "found_items_counter": "{count, plural, =0 {No vehicle found.} one {Found # vehicle} other {Found # vehicles}}", + "heading": "Veículos", + "subheading": "The pass navegante municipal Familia allows that each family pays at most the value of 2 municipal navegante pass indenpendently of the number of elemnts of the family. It is valid in all the companies of the public means of transport, in all of the 18 municipalities of the Lisbon metropolitan area." + } } } diff --git a/frontend/i18n/translations/pt.json b/frontend/i18n/translations/pt.json index 07fe29a7..e4ddad8a 100644 --- a/frontend/i18n/translations/pt.json +++ b/frontend/i18n/translations/pt.json @@ -1512,5 +1512,15 @@ }, "utils": { "number": "{value, number}" + }, + "vehicles": { + "VehiclesListToolbar": { + "filter_by": { + "search": "Pesquise por marticula, numero de passsageiros, por numero." + }, + "found_items_counter": "{count, plural, =0 {Nenhum Veículo encontrado.} one {Encontrado # veículo} other {Encontrados # veículos}}", + "heading": "Veículos", + "subheading": "O passe navegante municipal Família permite que cada agregado familiar pague no máximo o valor de 2 passes navegante municipal, independentemente do número de elementos do agregado familiar. É válido em todas as empresas de serviço público de transporte regular de passageiros, em todos os 18 municípios da Área Metropolitana de Lisboa." + } } } From 380d44d6fec236c319ab62283ab86dfd431b6cfa Mon Sep 17 00:00:00 2001 From: Skasix00 Date: Wed, 29 Jan 2025 11:47:42 +0000 Subject: [PATCH 03/22] Context mods --- .../app/(views)/(website)/vehicles/layout.tsx | 5 +- .../vehicles/VehiclesList/index.tsx | 10 +-- .../vehicles/VehiclesListGroup/index.tsx | 17 ++++ .../vehicles/VehiclesListGroups/index.tsx | 88 ------------------- .../vehicles/VehiclesListMap/index.tsx | 45 +++++----- .../index.tsx | 6 +- frontend/contexts/Vehicles.context.tsx | 19 +++- 7 files changed, 65 insertions(+), 125 deletions(-) create mode 100644 frontend/components/vehicles/VehiclesListGroup/index.tsx delete mode 100644 frontend/components/vehicles/VehiclesListGroups/index.tsx rename frontend/components/vehicles/{VehicleListToolbar => VehiclesListToolbar}/index.tsx (79%) diff --git a/frontend/app/(views)/(website)/vehicles/layout.tsx b/frontend/app/(views)/(website)/vehicles/layout.tsx index e2116570..c90a5bea 100644 --- a/frontend/app/(views)/(website)/vehicles/layout.tsx +++ b/frontend/app/(views)/(website)/vehicles/layout.tsx @@ -1,14 +1,13 @@ /* * */ -import { StoresListContextProvider } from '@/contexts/StoresList.context'; import { VehiclesContextProvider } from '@/contexts/Vehicles.context'; /* * */ export default function Layout({ children }) { return ( - + {children} - + ); } diff --git a/frontend/components/vehicles/VehiclesList/index.tsx b/frontend/components/vehicles/VehiclesList/index.tsx index 7bbc26e8..6527a960 100644 --- a/frontend/components/vehicles/VehiclesList/index.tsx +++ b/frontend/components/vehicles/VehiclesList/index.tsx @@ -4,9 +4,9 @@ import { Section } from '@/components/layout/Section'; import { Surface } from '@/components/layout/Surface'; -import VehiclesListToolbar from '@/components/vehicles/VehicleListToolbar'; -import VehiclesListGroups from '@/components/vehicles/VehiclesListGroups'; +import VehiclesListGroup from '@/components/vehicles/VehiclesListGroup'; import VehiclesListMap from '@/components/vehicles/VehiclesListMap'; +import VehiclesListToolbar from '@/components/vehicles/VehiclesListToolbar'; import styles from './styles.module.css'; @@ -16,12 +16,12 @@ export default function Component() { return ( <> + + +
-
- -
diff --git a/frontend/components/vehicles/VehiclesListGroup/index.tsx b/frontend/components/vehicles/VehiclesListGroup/index.tsx new file mode 100644 index 00000000..5db13296 --- /dev/null +++ b/frontend/components/vehicles/VehiclesListGroup/index.tsx @@ -0,0 +1,17 @@ +'use client'; + +/* * */ +import { Section } from '@/components/layout/Section'; +/* * */ + +export default function Component() { + // A. Render components + + return ( +
+

Teste de informação

+
+ ); + + // +} diff --git a/frontend/components/vehicles/VehiclesListGroups/index.tsx b/frontend/components/vehicles/VehiclesListGroups/index.tsx deleted file mode 100644 index 052100fe..00000000 --- a/frontend/components/vehicles/VehiclesListGroups/index.tsx +++ /dev/null @@ -1,88 +0,0 @@ -'use client'; - -/* * */ - -import type { StoreGroupByMunicipality } from '@/types/stores.types'; - -import { GroupedListItem } from '@/components/layout/GroupedListItem'; -import GroupedListSkeleton from '@/components/layout/GroupedListSkeleton'; -import { StoresListEmpty } from '@/components/stores/StoresListEmpty'; -import StoresListGroupItem from '@/components/stores/StoresListGroupItem'; -import StoresListItemSkeleton from '@/components/stores/StoresListGroupItemSkeleton'; -import { useStoresListContext } from '@/contexts/StoresList.context'; -import { useTranslations } from 'next-intl'; -import { useMemo } from 'react'; - -/* * */ - -export default function Component() { - // - - // - // A. Setup variables - - const t = useTranslations('stores.StoresListGroups'); - const storesListContext = useStoresListContext(); - - // - // B. Transform data - - const allStoresGroupedByMunicipality = useMemo(() => { - // - if (!storesListContext.data) return []; - // - const groupedStores: StoreGroupByMunicipality[] = storesListContext.data.filtered.reduce((result: StoreGroupByMunicipality[], item) => { - const existingGroup = result.find(group => group.municipality_id === item.municipality_id); - if (existingGroup) { - existingGroup.stores.push(item); - } - else { - result.push({ - municipality_id: item.municipality_id, - municipality_name: item.municipality_name, - stores: [item], - }); - } - return result; - }, []) || []; - // - // const sortedGroups = groupedStores.sort((a, b) => collator.compare(a.municipality_name, b.municipality_name)); - // - return groupedStores; - // - }, [storesListContext.data]); - - // - // C. Handle actions - - const handleSelectStore = (storeId: string) => { - storesListContext.actions.updateSelectedStore(storeId); - }; - - // - // D. Render components - - if (storesListContext.flags.is_loading) { - return } />; - } - - if (allStoresGroupedByMunicipality.length > 0 && storesListContext.filters.order_by === 'municipality_name') { - return allStoresGroupedByMunicipality.map(storeGroup => ( - - {storeGroup.stores.map(store => ( - - ))} - - )); - } - - if (storesListContext.data.filtered.length > 0) { - return storesListContext.data.filtered.map(store => ( - - )); - } - - return ; - - // -} diff --git a/frontend/components/vehicles/VehiclesListMap/index.tsx b/frontend/components/vehicles/VehiclesListMap/index.tsx index 5c97631f..3cec64c1 100644 --- a/frontend/components/vehicles/VehiclesListMap/index.tsx +++ b/frontend/components/vehicles/VehiclesListMap/index.tsx @@ -5,7 +5,7 @@ import type { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson'; import { MapView } from '@/components/map/MapView'; -import { useStoresListContext } from '@/contexts/StoresList.context'; +import { useVehiclesContext } from '@/contexts/Vehicles.context'; import { centerMap } from '@/utils/map.utils'; import { Layer, Source, useMap } from '@vis.gl/react-maplibre'; import { useTranslations } from 'next-intl'; @@ -20,26 +20,24 @@ export default function Component() { // A. Setup variables const { storesListMap } = useMap(); - const storesListContext = useStoresListContext(); + const vehiclesListContext = useVehiclesContext(); const t = useTranslations('stores.StoresListMap'); // // B. Transform data - const allStoresFeatureCollection: FeatureCollection | null = useMemo(() => { - if (!storesListContext.data.raw.length) return null; - const formattedFeatures: Feature[] = storesListContext.data.raw.map((storeItem) => { - const expectedWaitTimeInMinutes = Math.round((storeItem.realtime?.expected_wait_time ?? 0) / 60); - const textValue = storeItem.realtime?.current_status !== 'closed' ? t('expected_wait_time', { count: expectedWaitTimeInMinutes }) : ''; + const allVehiclesCollection: FeatureCollection | null = useMemo(() => { + if (!vehiclesListContext.data.vehicles) return null; + const formattedFeatures: Feature[] = vehiclesListContext.data.vehicles.map((vehicleItem) => { return { geometry: { - coordinates: [storeItem.lon, storeItem.lat], + coordinates: [vehicleItem.lon, vehicleItem.lat], type: 'Point', }, properties: { - current_status: storeItem.realtime?.current_status, + current_status: vehicleItem.realtime?.current_status, store_id: storeItem.id, - text_value: textValue, + license_plate: vehicleItem.license_plate, }, type: 'Feature', }; @@ -50,19 +48,19 @@ export default function Component() { }; }, [storesListContext.data.raw, t]); - useEffect(() => { - if (!allStoresFeatureCollection || !storesListMap) return; - centerMap(storesListMap, allStoresFeatureCollection.features); - }, [allStoresFeatureCollection, storesListMap]); + // useEffect(() => { + // if (!allStoresFeatureCollection || !storesListMap) return; + // centerMap(storesListMap, allStoresFeatureCollection.features); + // }, [allStoresFeatureCollection, storesListMap]); // // C. Handle actions - const handleMapClick = (event) => { - if (event?.features[0]) { - storesListContext.actions.updateSelectedStore(event.features[0].properties.store_id); - } - }; + // const handleMapClick = (event) => { + // if (event?.features[0]) { + // storesListContext.actions.updateSelectedStore(event.features[0].properties.store_id); + // } + // }; // // D. Render component @@ -71,14 +69,14 @@ export default function Component() { {allStoresFeatureCollection && ( )} + // ); - - // } diff --git a/frontend/components/vehicles/VehicleListToolbar/index.tsx b/frontend/components/vehicles/VehiclesListToolbar/index.tsx similarity index 79% rename from frontend/components/vehicles/VehicleListToolbar/index.tsx rename to frontend/components/vehicles/VehiclesListToolbar/index.tsx index 4aa7ced8..96707e85 100644 --- a/frontend/components/vehicles/VehicleListToolbar/index.tsx +++ b/frontend/components/vehicles/VehiclesListToolbar/index.tsx @@ -6,7 +6,7 @@ import { FoundItemsCounter } from '@/components/common/FoundItemsCounter'; import { Grid } from '@/components/layout/Grid'; import { Section } from '@/components/layout/Section'; import { Surface } from '@/components/layout/Surface'; -import { useStoresListContext } from '@/contexts/StoresList.context'; +import { useVehiclesContext } from '@/contexts/Vehicles.context'; import { TextInput } from '@mantine/core'; import { useTranslations } from 'next-intl'; @@ -19,7 +19,7 @@ export default function Component() { // A. Setup variables const t = useTranslations('vehicles.VehiclesListToolbar'); - const storesContext = useStoresListContext(); + const vehiclesContext = useVehiclesContext(); // // B. Transform data @@ -37,7 +37,7 @@ export default function Component() { - +
); diff --git a/frontend/contexts/Vehicles.context.tsx b/frontend/contexts/Vehicles.context.tsx index a297dc15..52231873 100644 --- a/frontend/contexts/Vehicles.context.tsx +++ b/frontend/contexts/Vehicles.context.tsx @@ -2,7 +2,7 @@ /* * */ -import type { Vehicle } from '@/types/vehicles.types'; +import type { Vehicle } from '@carrismetropolitana/api-types/vehicles'; import { getBaseGeoJsonFeatureCollection } from '@/utils/map.utils'; import { Routes } from '@/utils/routes'; @@ -16,6 +16,7 @@ interface VehiclesContextState { actions: { getVehicleById: (vehicleId: string) => undefined | Vehicle getVehicleByIdGeoJsonFC: (vehicleId: string) => GeoJSON.FeatureCollection | undefined + getVehicleBySearch: (searchData: string) => null | string getVehiclesByLineId: (lineId: string) => Vehicle[] getVehiclesByLineIdGeoJsonFC: (lineId: string) => GeoJSON.FeatureCollection | undefined getVehiclesByPatternId: (patternId: string) => Vehicle[] @@ -24,6 +25,7 @@ interface VehiclesContextState { getVehiclesByTripIdGeoJsonFC: (tripId: string) => GeoJSON.FeatureCollection | undefined } data: { + filtered: Vehicle[] vehicles: Vehicle[] } flags: { @@ -55,7 +57,7 @@ export const VehiclesContextProvider = ({ children }) => { const allVehiclesData = useMemo(() => { const now = DateTime.now().toSeconds(); - return fetchedVehiclesData?.filter((vehicle: Vehicle) => vehicle.timestamp > now - 180) || []; + return fetchedVehiclesData?.filter((vehicle: Vehicle) => vehicle.timestamp ?? 0 > now - 180) || []; }, [fetchedVehiclesData]); // @@ -109,6 +111,17 @@ export const VehiclesContextProvider = ({ children }) => { return collection; }; + const getVehicleBySearch = (searchData: string): Vehicle[] => { + return allVehiclesData?.filter(); + }; + const getVehicleBySearchFC = (seachData: string): void => { + const vehicle = getVehicleBySearch(seachData); + if (!vehicle) return; + const collection = getBaseGeoJsonFeatureCollection(); + collection.features.push(transformVehicleDataIntoGeoJsonFeature(vehicle)); + return collection; + }; + // // C. Define context value @@ -116,6 +129,7 @@ export const VehiclesContextProvider = ({ children }) => { actions: { getVehicleById, getVehicleByIdGeoJsonFC, + getVehicleBySearch, getVehiclesByLineId, getVehiclesByLineIdGeoJsonFC, getVehiclesByPatternId, @@ -124,6 +138,7 @@ export const VehiclesContextProvider = ({ children }) => { getVehiclesByTripIdGeoJsonFC, }, data: { + filtered: allVehiclesData || [], vehicles: allVehiclesData || [], }, flags: { From 9c7ce2869a311913ac4f451cd20be2394e703a63 Mon Sep 17 00:00:00 2001 From: Skasix00 Date: Fri, 31 Jan 2025 09:07:43 +0000 Subject: [PATCH 04/22] Added lines shapes and popup handling is fixed --- .../app/(views)/(website)/vehicles/layout.tsx | 12 +- .../map/MapViewStyleVehicles/index.tsx | 2 +- .../vehicles/VehiclesList/index.tsx | 4 +- .../vehicles/VehiclesList/styles.module.css | 21 +- .../vehicles/VehiclesListMap/index.tsx | 226 ++++++++++-------- .../VehiclesListMap/styles.module.css | 23 ++ frontend/contexts/Vehicles.context.tsx | 44 ++-- frontend/contexts/VehiclesList.context.tsx | 145 +++++++++++ 8 files changed, 330 insertions(+), 147 deletions(-) create mode 100644 frontend/components/vehicles/VehiclesListMap/styles.module.css create mode 100644 frontend/contexts/VehiclesList.context.tsx diff --git a/frontend/app/(views)/(website)/vehicles/layout.tsx b/frontend/app/(views)/(website)/vehicles/layout.tsx index c90a5bea..dd89aa1a 100644 --- a/frontend/app/(views)/(website)/vehicles/layout.tsx +++ b/frontend/app/(views)/(website)/vehicles/layout.tsx @@ -1,13 +1,19 @@ /* * */ +import { StopsContextProvider } from '@/contexts/Stops.context'; import { VehiclesContextProvider } from '@/contexts/Vehicles.context'; +import { VehiclesListContextProvider } from '@/contexts/VehiclesList.context'; /* * */ export default function Layout({ children }) { return ( - - {children} - + + + + {children} + + + ); } diff --git a/frontend/components/map/MapViewStyleVehicles/index.tsx b/frontend/components/map/MapViewStyleVehicles/index.tsx index 9dffd14b..e4e3e2f5 100644 --- a/frontend/components/map/MapViewStyleVehicles/index.tsx +++ b/frontend/components/map/MapViewStyleVehicles/index.tsx @@ -12,7 +12,7 @@ import styles from './styles.module.css'; /* * */ export const MapViewStyleVehiclesPrimaryLayerId = 'default-layer-vehicles-regular'; -export const MapViewStyleVehiclesInteractiveLayerId = ''; +export const MapViewStyleVehiclesInteractiveLayerId = 'default-layer-vehicles-regular'; /* * */ diff --git a/frontend/components/vehicles/VehiclesList/index.tsx b/frontend/components/vehicles/VehiclesList/index.tsx index 6527a960..a61fc5da 100644 --- a/frontend/components/vehicles/VehiclesList/index.tsx +++ b/frontend/components/vehicles/VehiclesList/index.tsx @@ -22,9 +22,7 @@ export default function Component() {
-
- -
+
diff --git a/frontend/components/vehicles/VehiclesList/styles.module.css b/frontend/components/vehicles/VehiclesList/styles.module.css index b75f8c94..e3af5a0b 100644 --- a/frontend/components/vehicles/VehiclesList/styles.module.css +++ b/frontend/components/vehicles/VehiclesList/styles.module.css @@ -4,7 +4,7 @@ .contentWrapper { display: grid; grid-template: - "a b" minmax(500px, auto) / 1fr 1fr; + "a b" minmax(500px, auto) / 1fr; align-items: flex-start; justify-content: flex-start; width: 100%; @@ -28,21 +28,4 @@ overflow: hidden; } - -/* * */ -/* MAP WRAPPER */ - -.mapWrapper { - position: sticky; - top: var(--size-height-header); - grid-area: b; - height: 100%; - max-height: calc(100vh - var(--size-height-header)); -} - -@media (width < 1000px) { - .mapWrapper { - position: static; - height: 300px; - } -} \ No newline at end of file +/* * */ \ No newline at end of file diff --git a/frontend/components/vehicles/VehiclesListMap/index.tsx b/frontend/components/vehicles/VehiclesListMap/index.tsx index 3cec64c1..4a977c35 100644 --- a/frontend/components/vehicles/VehiclesListMap/index.tsx +++ b/frontend/components/vehicles/VehiclesListMap/index.tsx @@ -1,134 +1,148 @@ 'use client'; /* * */ - -import type { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson'; - +import { CopyBadge } from '@/components/common/CopyBadge'; import { MapView } from '@/components/map/MapView'; +import { MapViewStylePath } from '@/components/map/MapViewStylePath'; +import { MapViewStyleVehicles, MapViewStyleVehiclesInteractiveLayerId, MapViewStyleVehiclesPrimaryLayerId } from '@/components/map/MapViewStyleVehicles'; +import { transformStopDataIntoGeoJsonFeature, useStopsContext } from '@/contexts/Stops.context'; import { useVehiclesContext } from '@/contexts/Vehicles.context'; -import { centerMap } from '@/utils/map.utils'; -import { Layer, Source, useMap } from '@vis.gl/react-maplibre'; -import { useTranslations } from 'next-intl'; -import { useEffect, useMemo } from 'react'; +import { useVehiclesListContext } from '@/contexts/VehiclesList.context'; +import { getBaseGeoJsonFeatureCollection } from '@/utils/map.utils'; +import { Routes } from '@/utils/routes'; +import { IconBike, IconBikeOff, IconWheelchair, IconWheelchairOff } from '@tabler/icons-react'; +import { Popup, useMap } from '@vis.gl/react-maplibre'; +import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson'; +import { DateTime } from 'luxon'; +import { useEffect, useMemo, useState } from 'react'; + +import styles from './styles.module.css'; /* * */ export default function Component() { - // - - // // A. Setup variables + const { vehiclesListMap } = useMap(); + const vehiclesListContext = useVehiclesListContext(); + const vehiclesContext = useVehiclesContext(); + const stopsContext = useStopsContext(); + const [activePathShapeGeoJson, setActivePathShapeGeoJson] = useState | FeatureCollection | undefined>(undefined); - const { storesListMap } = useMap(); - const vehiclesListContext = useVehiclesContext(); - const t = useTranslations('stores.StoresListMap'); + // B. Fetch Data + const findTodaysDate = () => { + return DateTime.now().toFormat('yyyyLLdd'); + }; - // - // B. Transform data + const fetchPattern = async (patternId) => { + const date = await findTodaysDate(); + console.log(date); + if (patternId) { + const actualPattern = await fetch(`${Routes.API}/patterns/${patternId}`).then(res => res.json()); + const isAvailable = actualPattern.filter(item => item.valid_on.includes(date)); + return isAvailable; + } + }; + + const fetchShape = async (id) => { + if (id) { + const shape = await fetch(`${Routes.API}/shapes/${id[0].shape_id}`).then(res => res.json()); + return shape.geojson; + } + }; - const allVehiclesCollection: FeatureCollection | null = useMemo(() => { - if (!vehiclesListContext.data.vehicles) return null; - const formattedFeatures: Feature[] = vehiclesListContext.data.vehicles.map((vehicleItem) => { - return { - geometry: { - coordinates: [vehicleItem.lon, vehicleItem.lat], - type: 'Point', - }, - properties: { - current_status: vehicleItem.realtime?.current_status, - store_id: storeItem.id, - license_plate: vehicleItem.license_plate, - }, - type: 'Feature', + const activeVehiclesGeoJson = useMemo(() => { + return vehiclesContext.actions.getAllVehiclesGeoJsonFC(); + }, [vehiclesContext.data.vehicles]); + + const activePathWaypointsGeoJson = useMemo(() => { + const patternId = vehiclesListContext.data.selected?.pattern_id; + const vehiclesByPatternId = vehiclesContext.actions.getVehiclesByPatternId(patternId || ''); + const collection = getBaseGeoJsonFeatureCollection(); + + if (!vehiclesListContext.data.selected?.pattern_id) return; + + vehiclesByPatternId.forEach((pathStop) => { + const stopData = stopsContext.actions.getStopById(pathStop.stop_id || ''); + if (!stopData) return; + const result = transformStopDataIntoGeoJsonFeature(stopData); + result.properties = { + ...result.properties, }; + collection.features.push(result); }); - return { - features: formattedFeatures || [], - type: 'FeatureCollection', + + return collection; + }, [vehiclesListContext.data.selected, vehiclesContext.data.vehicles]); + + // B. Transform data + + useEffect(() => { + const fetchData = async () => { + const patternId = vehiclesListContext.data.selected?.pattern_id; + + if (!patternId) return; + + const shapeId = await fetchPattern(patternId); + if (!shapeId) return; + + const shapeGeoJson = await fetchShape(shapeId); + + setActivePathShapeGeoJson(shapeGeoJson); }; - }, [storesListContext.data.raw, t]); - // useEffect(() => { - // if (!allStoresFeatureCollection || !storesListMap) return; - // centerMap(storesListMap, allStoresFeatureCollection.features); - // }, [allStoresFeatureCollection, storesListMap]); + fetchData(); + }, [vehiclesListContext.data.selected]); - // - // C. Handle actions + useEffect(() => { + const vehicleFC = vehiclesContext.actions.getVehiclesByTripIdGeoJsonFC(vehiclesListContext.data.selected?.trip_id || ''); + if (!vehicleFC?.features.length) return; + }, [vehiclesListContext.data.selected, vehiclesListContext.data.raw, vehiclesListMap]); - // const handleMapClick = (event) => { - // if (event?.features[0]) { - // storesListContext.actions.updateSelectedStore(event.features[0].properties.store_id); - // } - // }; + // C. Handle actions + function handleLayerClick(event) { + if (event.features) { + vehiclesListContext.actions.updateSelectedVehicle(event.features[0].properties.id); + } + } - // // D. Render component - return ( - {allStoresFeatureCollection && ( - - - - + {vehiclesListContext.data.selected + && ( + <> + +
+ {vehiclesListContext.data.selected.bikes_allowed ? : } + {vehiclesListContext.data.selected.wheelchair_accessible ? : } +
+ {console.log(vehiclesContext.data.vehicles)} + + + + + + + + + +
+ )} + + + +
- // ); } diff --git a/frontend/components/vehicles/VehiclesListMap/styles.module.css b/frontend/components/vehicles/VehiclesListMap/styles.module.css new file mode 100644 index 00000000..f6e79780 --- /dev/null +++ b/frontend/components/vehicles/VehiclesListMap/styles.module.css @@ -0,0 +1,23 @@ +/* POPUP WRAPPER */ + +.popupWrapper div div { + display: flex; + flex-direction: column; + gap: 10px; + align-items: flex-start; + } + +.popupWrapper p{ + color: var(--color-system-text-200); + font-size: var(--font-size-text); +} +/* * */ +/* ICON LIST */ +.iconList{ + padding-left:8px; + line-height:17px; + vertical-align:middle; + width:100%; + display:inline-block; +} +/* * */ diff --git a/frontend/contexts/Vehicles.context.tsx b/frontend/contexts/Vehicles.context.tsx index 52231873..9503480b 100644 --- a/frontend/contexts/Vehicles.context.tsx +++ b/frontend/contexts/Vehicles.context.tsx @@ -14,9 +14,11 @@ import useSWR from 'swr'; interface VehiclesContextState { actions: { + getAllVehicles: () => undefined | Vehicle[] + getAllVehiclesGeoJsonFC: () => GeoJSON.FeatureCollection | undefined getVehicleById: (vehicleId: string) => undefined | Vehicle getVehicleByIdGeoJsonFC: (vehicleId: string) => GeoJSON.FeatureCollection | undefined - getVehicleBySearch: (searchData: string) => null | string + // getVehicleBySearch: (searchData: string) => null | string getVehiclesByLineId: (lineId: string) => Vehicle[] getVehiclesByLineIdGeoJsonFC: (lineId: string) => GeoJSON.FeatureCollection | undefined getVehiclesByPatternId: (patternId: string) => Vehicle[] @@ -75,6 +77,16 @@ export const VehiclesContextProvider = ({ children }) => { return collection; }; + const getAllVehicles = (): undefined | Vehicle[] => { + return allVehiclesData; + }; + + const getAllVehiclesGeoJsonFC = (): GeoJSON.FeatureCollection | undefined => { + const collection = getBaseGeoJsonFeatureCollection(); + allVehiclesData.forEach(vehicle => collection.features.push(transformVehicleDataIntoGeoJsonFeature(vehicle))); + return collection; + }; + const getVehiclesByLineId = (lineId: string): Vehicle[] => { return allVehiclesData?.filter(vehicle => vehicle.line_id === lineId) || []; }; @@ -111,25 +123,27 @@ export const VehiclesContextProvider = ({ children }) => { return collection; }; - const getVehicleBySearch = (searchData: string): Vehicle[] => { - return allVehiclesData?.filter(); - }; - const getVehicleBySearchFC = (seachData: string): void => { - const vehicle = getVehicleBySearch(seachData); - if (!vehicle) return; - const collection = getBaseGeoJsonFeatureCollection(); - collection.features.push(transformVehicleDataIntoGeoJsonFeature(vehicle)); - return collection; - }; + // const getVehicleBySearch = (searchData: string): Vehicle[] => { + // return allVehiclesData?.filter(); + // }; + // const getVehicleBySearchFC = (seachData: string): void => { + // const vehicle = getVehicleBySearch(seachData); + // if (!vehicle) return; + // const collection = getBaseGeoJsonFeatureCollection(); + // collection.features.push(transformVehicleDataIntoGeoJsonFeature(vehicle)); + // return collection; + // }; // // C. Define context value const contextValue: VehiclesContextState = { actions: { + getAllVehicles, + getAllVehiclesGeoJsonFC, getVehicleById, getVehicleByIdGeoJsonFC, - getVehicleBySearch, + // getVehicleBySearch, getVehiclesByLineId, getVehiclesByLineIdGeoJsonFC, getVehiclesByPatternId, @@ -163,14 +177,14 @@ export const VehiclesContextProvider = ({ children }) => { function transformVehicleDataIntoGeoJsonFeature(vehicleData: Vehicle): GeoJSON.Feature { return { geometry: { - coordinates: [vehicleData.lon, vehicleData.lat], + coordinates: [vehicleData.lon || 0, vehicleData.lat || 0], type: 'Point', }, properties: { bearing: vehicleData.bearing, block_id: vehicleData.block_id, current_status: vehicleData.current_status, - delay: Math.floor(Date.now() / 1000) - vehicleData.timestamp, + delay: Math.floor(Date.now() / 1000) - (vehicleData.timestamp || 0), id: vehicleData.id, line_id: vehicleData.line_id, pattern_id: vehicleData.id, @@ -180,7 +194,7 @@ function transformVehicleDataIntoGeoJsonFeature(vehicleData: Vehicle): GeoJSON.F speed: vehicleData.speed, stop_id: vehicleData.stop_id, timestamp: vehicleData.timestamp, - timeString: new Date(vehicleData.timestamp * 1000).toLocaleString(), + timeString: new Date((vehicleData.timestamp || 0) * 1000).toLocaleString(), trip_id: vehicleData.trip_id, }, type: 'Feature', diff --git a/frontend/contexts/VehiclesList.context.tsx b/frontend/contexts/VehiclesList.context.tsx new file mode 100644 index 00000000..e647adb3 --- /dev/null +++ b/frontend/contexts/VehiclesList.context.tsx @@ -0,0 +1,145 @@ +'use client'; + +/* * */ +import { Routes } from '@/utils/routes'; +import { Vehicle } from '@carrismetropolitana/api-types/vehicles'; +import { useQueryState } from 'nuqs'; +import { createContext, useContext, useEffect, useState } from 'react'; +import useSWR from 'swr'; + +/* * */ + +interface VehiclesListContextState { + actions: { + updateFilterBySearch: (search: string) => void + updateSelectedVehicle: (vehicleId: string) => void + } + data: { + filtered: Vehicle[] + raw: Vehicle[] + selected: null | Vehicle + } + filters: { + by_search: null | string + selected_vehicle: null | string + } + flags: { + is_loading: boolean + } +} + +/* * */ + +const VehiclesListContext = createContext(undefined); + +export function useVehiclesListContext() { + const context = useContext(VehiclesListContext); + if (!context) { + throw new Error('useVehiclesListContext must be used within a VehiclesListContext'); + } + return context; +} + +/* * */ + +export const VehiclesListContextProvider = ({ children }) => { + // + + // + // A. Setup variables + const [dataFilteredState, setDataFilteredState] = useState([]); + const [dataSelectedState, setDataSelectedState] = useState(null); + + const [filterBySearchState, setFilterBySearchState] = useQueryState('search'); + const [filteredSelectedVehicleState, setfilteredSelectedVehicleState] = useQueryState('search'); + + // + // B. Fetch data + + const { data: allVehicleData, isLoading: allVehiclesLoading } = useSWR(`${Routes.API}/vehicles`, { refreshInterval: 30000 }); + + // + // C. Transform data + + const applyFiltersToData = () => { + // + const filterResult: Vehicle[] = allVehicleData || []; + + // + // Filter By Search + switch (filterBySearchState) { + case 'accessible': + filterResult.filter(item => item.wheelchair_accessible.toString() === filterBySearchState); + break; + case 'make': + filterResult.filter(item => item.make === filterBySearchState); + break; + case 'plate': + filterResult.filter(item => item.license_plate === filterBySearchState); + break; + default: + console.error('Invalid filterBySearchState:', filterBySearchState); + break; + } + // + // Save filter result to state + return filterResult; + + // + }; + + // useEffect(() => { + // if (filterBySearchState) { + // const filteredVehicles = applyFiltersToData(); + // setDataFilteredState(filteredVehicles); + // } + // }, [allVehicleData, filterBySearchState]); + // + // D. Handle actions + + const updateFilterBySearch = (value: VehiclesListContextState['filters']['by_search']) => { + setFilterBySearchState(value); + }; + + const updateSelectedVehicle = (vehicleId: string) => { + if (!allVehicleData) return; + const foundVehicleData = allVehicleData.find(item => item.id === vehicleId) || null; + if (foundVehicleData) { + setDataSelectedState(foundVehicleData); + setfilteredSelectedVehicleState(vehicleId); + } + }; + + // + // E. Define context value + + const contextValue: VehiclesListContextState = { + actions: { + updateFilterBySearch, + updateSelectedVehicle, + }, + data: { + filtered: dataFilteredState, + raw: allVehicleData || [], + selected: dataSelectedState, + }, + filters: { + by_search: filterBySearchState, + selected_vehicle: filteredSelectedVehicleState, + }, + flags: { + is_loading: allVehiclesLoading, + }, + }; + + // + // F. Render components + + return ( + + {children} + + ); + + // +}; From 3d43b4896bdae97bc4da1dd0fc53e4898d6884f9 Mon Sep 17 00:00:00 2001 From: Skasix00 Date: Sat, 1 Feb 2025 10:57:59 +0000 Subject: [PATCH 05/22] Startedd adding the data filters, only need to return the filtered array --- frontend/components/layout/Grid/index.tsx | 7 +- .../vehicles/VehiclesListMap/index.tsx | 45 ++-- .../vehicles/VehiclesListToolbar/index.tsx | 129 ++++++++- frontend/contexts/VehiclesList.context.tsx | 253 ++++++++++++++++-- 4 files changed, 370 insertions(+), 64 deletions(-) diff --git a/frontend/components/layout/Grid/index.tsx b/frontend/components/layout/Grid/index.tsx index 9387b584..cd64be57 100644 --- a/frontend/components/layout/Grid/index.tsx +++ b/frontend/components/layout/Grid/index.tsx @@ -1,11 +1,10 @@ /* * */ - import styles from './styles.module.css'; - /* * */ interface Props { children?: React.ReactNode + classname?: string columns?: 'a' | 'aab' | 'ab' | 'abb' | 'abc' | 'abcd' hAlign?: 'center' | 'end' | 'start' vAlign?: 'center' | 'end' | 'start' @@ -14,9 +13,9 @@ interface Props { /* * */ -export function Grid({ children, columns = 'a', hAlign = 'start', vAlign = 'start', withGap }: Props) { +export function Grid({ children, classname, columns = 'a', hAlign = 'start', vAlign = 'start', withGap }: Props) { return ( -
+
{children}
); diff --git a/frontend/components/vehicles/VehiclesListMap/index.tsx b/frontend/components/vehicles/VehiclesListMap/index.tsx index 4a977c35..ef753372 100644 --- a/frontend/components/vehicles/VehiclesListMap/index.tsx +++ b/frontend/components/vehicles/VehiclesListMap/index.tsx @@ -5,10 +5,9 @@ import { CopyBadge } from '@/components/common/CopyBadge'; import { MapView } from '@/components/map/MapView'; import { MapViewStylePath } from '@/components/map/MapViewStylePath'; import { MapViewStyleVehicles, MapViewStyleVehiclesInteractiveLayerId, MapViewStyleVehiclesPrimaryLayerId } from '@/components/map/MapViewStyleVehicles'; -import { transformStopDataIntoGeoJsonFeature, useStopsContext } from '@/contexts/Stops.context'; +import { useStopsContext } from '@/contexts/Stops.context'; import { useVehiclesContext } from '@/contexts/Vehicles.context'; import { useVehiclesListContext } from '@/contexts/VehiclesList.context'; -import { getBaseGeoJsonFeatureCollection } from '@/utils/map.utils'; import { Routes } from '@/utils/routes'; import { IconBike, IconBikeOff, IconWheelchair, IconWheelchairOff } from '@tabler/icons-react'; import { Popup, useMap } from '@vis.gl/react-maplibre'; @@ -35,7 +34,7 @@ export default function Component() { const fetchPattern = async (patternId) => { const date = await findTodaysDate(); - console.log(date); + if (patternId) { const actualPattern = await fetch(`${Routes.API}/patterns/${patternId}`).then(res => res.json()); const isAvailable = actualPattern.filter(item => item.valid_on.includes(date)); @@ -54,25 +53,23 @@ export default function Component() { return vehiclesContext.actions.getAllVehiclesGeoJsonFC(); }, [vehiclesContext.data.vehicles]); - const activePathWaypointsGeoJson = useMemo(() => { - const patternId = vehiclesListContext.data.selected?.pattern_id; - const vehiclesByPatternId = vehiclesContext.actions.getVehiclesByPatternId(patternId || ''); - const collection = getBaseGeoJsonFeatureCollection(); + // const activePathWaypointsGeoJson = useMemo(() => { + // const patternId = vehiclesListContext.data.selected?.pattern_id; - if (!vehiclesListContext.data.selected?.pattern_id) return; + // if (!vehiclesListContext.data.selected?.pattern_id) return; - vehiclesByPatternId.forEach((pathStop) => { - const stopData = stopsContext.actions.getStopById(pathStop.stop_id || ''); - if (!stopData) return; - const result = transformStopDataIntoGeoJsonFeature(stopData); - result.properties = { - ...result.properties, - }; - collection.features.push(result); - }); + // // vehiclesByPatternId.forEach((pathStop) => { + // // const stopData = stopsContext.actions.getStopById(pathStop.stop_id || ''); + // // if (!stopData) return; + // // const result = transformStopDataIntoGeoJsonFeature(stopData); + // // result.properties = { + // // ...result.properties, + // // }; + // // collection.features.push(result); + // // }); - return collection; - }, [vehiclesListContext.data.selected, vehiclesContext.data.vehicles]); + // // return collection; + // }, [vehiclesListContext.data.selected, vehiclesContext.data.vehicles]); // B. Transform data @@ -93,14 +90,9 @@ export default function Component() { fetchData(); }, [vehiclesListContext.data.selected]); - useEffect(() => { - const vehicleFC = vehiclesContext.actions.getVehiclesByTripIdGeoJsonFC(vehiclesListContext.data.selected?.trip_id || ''); - if (!vehicleFC?.features.length) return; - }, [vehiclesListContext.data.selected, vehiclesListContext.data.raw, vehiclesListMap]); - // C. Handle actions function handleLayerClick(event) { - if (event.features) { + if (event.features.length !== 0) { vehiclesListContext.actions.updateSelectedVehicle(event.features[0].properties.id); } } @@ -120,7 +112,6 @@ export default function Component() { {vehiclesListContext.data.selected.bikes_allowed ? : } {vehiclesListContext.data.selected.wheelchair_accessible ? : }
- {console.log(vehiclesContext.data.vehicles)} @@ -141,7 +132,7 @@ export default function Component() { ); diff --git a/frontend/components/vehicles/VehiclesListToolbar/index.tsx b/frontend/components/vehicles/VehiclesListToolbar/index.tsx index 96707e85..468e8886 100644 --- a/frontend/components/vehicles/VehiclesListToolbar/index.tsx +++ b/frontend/components/vehicles/VehiclesListToolbar/index.tsx @@ -7,8 +7,11 @@ import { Grid } from '@/components/layout/Grid'; import { Section } from '@/components/layout/Section'; import { Surface } from '@/components/layout/Surface'; import { useVehiclesContext } from '@/contexts/Vehicles.context'; -import { TextInput } from '@mantine/core'; +import { useVehiclesListContext } from '@/contexts/VehiclesList.context'; +import { MultiSelect, Select, TextInput } from '@mantine/core'; +import { IconArrowLoopRight, IconBike, IconGasStation, IconTriangle, IconUser, IconWheelchair } from '@tabler/icons-react'; import { useTranslations } from 'next-intl'; +import { useEffect, useRef, useState } from 'react'; /* * */ @@ -20,22 +23,134 @@ export default function Component() { const t = useTranslations('vehicles.VehiclesListToolbar'); const vehiclesContext = useVehiclesContext(); + const vehiclesListContext = useVehiclesListContext(); + const typingTimeoutRef = useRef>(null); + const [textInput, setTextInput] = useState(null); + const [reducedMobility, setReducedMobility] = useState(''); + const [bikesAllowed, setBikesAllowed] = useState(''); + const [agencyId, setAgencyId] = useState([]); + const [make_model, setMakeModel] = useState([]); + const [propulsion, setPropulsion] = useState([]); // // B. Transform data - const filterByOptions = [ - { label: t('filter_by.search'), value: 'search' }, - ]; + useEffect(() => { + return () => { + if (typingTimeoutRef.current) { + clearTimeout(typingTimeoutRef.current); + } + }; + }, []); // - // C. Render components + + // C. Handle Actions + const handleTextInputChange = ({ currentTarget }) => { + setTextInput(currentTarget.value); + if (!vehiclesListContext.flags.is_loading && vehiclesListContext.data.raw) { + if (typingTimeoutRef.current) { + clearTimeout(typingTimeoutRef.current); + } + typingTimeoutRef.current = setTimeout(() => { + vehiclesListContext.actions.updateFilterBySearch(currentTarget.value); + }, 500); + } + }; + + const handleBikesAllowedInputChange = (option: string) => { + setBikesAllowed(option); + vehiclesListContext.actions.updateFilterByIsBikeAllowed(option); + }; + + const handleReducedMobilityChange = (option: string) => { + setReducedMobility(option); + vehiclesListContext.actions.updateFilterByWheelchair(option); + }; + + const handleAgencyIdChange = (option: string[]) => { + setAgencyId(option); + vehiclesListContext.actions.updateFilterByAgency(option); + }; + + const handleMakeAndModelChange = (option: string[]) => { + setMakeModel(option); + vehiclesListContext.actions.updateFilterByMakeAndModel(option); + }; + + const handlePropulsionChange = (option: string[]) => { + setPropulsion(option); + vehiclesListContext.actions.updateFilterByPropulsion(option); + }; + + // + // D. Render components return (
- - + + } onChange={handleTextInputChange} placeholder={t('filter_by.search')} type="search" value={textInput || ''} /> + } + onChange={handleReducedMobilityChange} + placeholder="Mobilidade Reduzida" + radius="sm" + value={reducedMobility} + data={[{ label: 'Não', value: 'false' }, + { label: 'Sim', value: 'true' }]} + clearable + searchable + /> + ({ label: a.name, value: a.agency_id.toString() })) || []} + leftSection={} + onChange={handleAgencyIdChange} + placeholder="Operador" + radius="sm" + value={agencyId} + searchable + /> + } + onChange={handleMakeAndModelChange} + radius="sm" + value={make_model || ''} + data={ + vehiclesListContext.data?.makes_and_models?.map(make => ({ + group: make.name, + items: make.models.map(model => ({ + label: model.name, + value: ` ${make.name}-${model.name}`, + })), + label: make.name, + value: make.name, + })) || [] + } + clearable + searchable + /> + ({ label: p.name, value: p.name })) || []} + leftSection={} + onChange={handlePropulsionChange} + placeholder="Combustível" + radius="sm" + value={propulsion} + clearable + searchable + />
diff --git a/frontend/contexts/VehiclesList.context.tsx b/frontend/contexts/VehiclesList.context.tsx index e647adb3..7c529230 100644 --- a/frontend/contexts/VehiclesList.context.tsx +++ b/frontend/contexts/VehiclesList.context.tsx @@ -11,15 +11,28 @@ import useSWR from 'swr'; interface VehiclesListContextState { actions: { + updateFilterByAgency: (agency: string []) => void + updateFilterByIsBikeAllowed: (isBikeAllowed: string) => void + updateFilterByMakeAndModel: (makeAndModel: string []) => void + updateFilterByPropulsion: (propulsion: string[]) => void updateFilterBySearch: (search: string) => void + updateFilterByWheelchair: (isWheelchairAcessible: string) => void updateSelectedVehicle: (vehicleId: string) => void } data: { + agencys: null | { agency_id: number, name: string }[] filtered: Vehicle[] + makes_and_models: null | { id: number, models: { id: number, name: string }[], name: string }[] + propulsions: null | { id: number, name: string }[] raw: Vehicle[] selected: null | Vehicle } filters: { + by_agency: null | string + by_isBicicleAllowed: null | string + by_isWheelchairAcessible: null | string + by_makeAndModel: null | string + by_propulsion: null | string by_search: null | string selected_vehicle: null | string } @@ -49,9 +62,19 @@ export const VehiclesListContextProvider = ({ children }) => { // A. Setup variables const [dataFilteredState, setDataFilteredState] = useState([]); const [dataSelectedState, setDataSelectedState] = useState(null); + const [allAgencys, setAllAgencys] = useState<{ agency_id: number, name: string }[]>([]); + const [allMakesAndModels, setAllMakesAndModels] = useState<{ id: number, models: { id: number, name: string }[], name: string }[]>([]); + const [allPropulsions, setAllPropulsions] = useState([]); + const [filterByWheelchairAccesibleState, setWheelchairAccesibleState] = useQueryState('isWheelchair'); + const [filterByAgencyState, setFilterByAgencyState] = useQueryState('agency'); + const [filterByBicycleAllowedState, setByBicycleAllowedState] = useQueryState('isBikeAllowed'); + const [filterByMakeAndModelState, setFilterByMakeAndModelState] = useQueryState('makeModel'); const [filterBySearchState, setFilterBySearchState] = useQueryState('search'); + const [filterByPropulsionState, setFilterByPropulsion] = useQueryState('propulsion'); const [filteredSelectedVehicleState, setfilteredSelectedVehicleState] = useQueryState('search'); + const [make, setMake] = useState(undefined); + const [model, setModel] = useState(undefined); // // B. Fetch data @@ -63,42 +86,205 @@ export const VehiclesListContextProvider = ({ children }) => { const applyFiltersToData = () => { // - const filterResult: Vehicle[] = allVehicleData || []; + let filterResult: Vehicle[] = allVehicleData || []; - // - // Filter By Search - switch (filterBySearchState) { - case 'accessible': - filterResult.filter(item => item.wheelchair_accessible.toString() === filterBySearchState); - break; - case 'make': - filterResult.filter(item => item.make === filterBySearchState); - break; - case 'plate': - filterResult.filter(item => item.license_plate === filterBySearchState); - break; - default: - console.error('Invalid filterBySearchState:', filterBySearchState); - break; + if (filterResult.length !== 0) { + if (filterByBicycleAllowedState) { + filterResult = (allVehicleData || []).filter(item => item.bikes_allowed?.toString() === filterByBicycleAllowedState || false); + } + + if (filterByWheelchairAccesibleState) { + filterResult = (allVehicleData || []).filter(item => item.wheelchair_accessible?.toString() === filterByBicycleAllowedState || false); + } + + if (filterByPropulsionState !== '') { + filterResult = (allVehicleData || []).filter(item => item.propulsion === filterByPropulsionState || ''); + } + + if (filterByAgencyState !== '') { + filterResult = (allVehicleData || []).filter(item => item.agency_id === filterByAgencyState || 0); + } } - // - // Save filter result to state + + if (filterBySearchState) { + filterResult = (allVehicleData || []).filter(item => + item.license_plate?.toLowerCase().includes(filterBySearchState.toLowerCase()), + ); + } + console.log(make); + if (make) { + filterResult = (allVehicleData || []).filter(item => item.make?.toLocaleLowerCase().includes(make.toLowerCase())); + } + + if (model) { + filterResult = (allVehicleData || []).filter(item => item.model?.toLocaleLowerCase().includes(model.toLowerCase())); + } + + if (filterByMakeAndModelState) { + filterResult = (allVehicleData || []).filter(item => + item.make?.toLowerCase().includes(filterByMakeAndModelState.toLowerCase()), + ); + } + console.log('fiter: ', filterBySearchState); + return filterResult; // }; - // useEffect(() => { - // if (filterBySearchState) { - // const filteredVehicles = applyFiltersToData(); - // setDataFilteredState(filteredVehicles); - // } - // }, [allVehicleData, filterBySearchState]); + const getAllMakesAndModels = () => { + if (!allVehicleData) return []; + + const makesMap = new Map(); + let makeIdCounter = 1; + let modelIdCounter = 1; + + allVehicleData.forEach((vehicle) => { + if (vehicle.make !== undefined && vehicle.model !== undefined) { + const makeName = vehicle.make; + const modelName = vehicle.model; + + if (!makesMap.has(makeName)) { + makesMap.set(makeName, { + id: makeIdCounter, + models: [], + name: makeName, + }); + makeIdCounter++; + } + + const makeObj = makesMap.get(makeName); + + if (!makeObj.models.some(model => model.name === modelName)) { + makeObj.models.push({ id: modelIdCounter, name: modelName }); + modelIdCounter++; + } + } + }); + + const makes_and_models = Array.from(makesMap.values()); + setAllMakesAndModels(makes_and_models); + return makes_and_models; + }; + + const getAllAgencys = () => { + const agencysMap = new Map(); + + allVehicleData?.forEach((vehicle) => { + if (vehicle.agency_id !== undefined) { + const agency_Id = vehicle.agency_id; + const agencyOverrides = { + 41: 'Viação Alvorada', + 42: 'Rodoviária de Lisboa (RL)', + 43: 'Transportes Sul do Tejo (TST)', + 44: 'Alsa Todi', + }; + + if (!agencysMap.has(agency_Id)) { + agencysMap.set(agency_Id, { agency_id: agency_Id, name: agencyOverrides[agency_Id] }); + } + } + }); + const agencys = Array.from(agencysMap.values()); + setAllAgencys(agencys); + return agencys; + }; + const getAllPropulsion = () => { + if (!allVehicleData) return []; + + const propulsionsMap = new Map(); + let idCounter = 1; + + allVehicleData.forEach((vehicle) => { + if (vehicle.propulsion !== undefined) { + const propulsionType = vehicle.propulsion; + + if (!propulsionsMap.has(propulsionType)) { + propulsionsMap.set(propulsionType, { id: idCounter, name: propulsionType }); + idCounter++; + } + } + }); + + const propulsions = Array.from(propulsionsMap.values()); + setAllPropulsions(propulsions); + return propulsions; + }; + + useEffect(() => { + if (filterBySearchState || filterByAgencyState || filterByBicycleAllowedState || filterByMakeAndModelState || filterByWheelchairAccesibleState) { + const filteredVehicles = applyFiltersToData(); + setDataFilteredState(filteredVehicles || []); + } + }, [filterBySearchState, filterByAgencyState, filterByBicycleAllowedState, filterByMakeAndModelState, filterByWheelchairAccesibleState]); + + useEffect(() => { + if (!allVehicleData && !allVehiclesLoading) return; + getAllAgencys(); + getAllMakesAndModels(); + getAllPropulsion(); + }, [allVehicleData]); // // D. Handle actions - const updateFilterBySearch = (value: VehiclesListContextState['filters']['by_search']) => { - setFilterBySearchState(value); + const updateFilterBySearch = (search: string | string[]) => { + if (Array.isArray(search)) { + setFilterBySearchState(search.join(' ')); + } + else { + setFilterBySearchState(search); + } + }; + + const updateFilterByAgency = (agency: string | string[]) => { + if (Array.isArray(agency)) { + setFilterByAgencyState(agency.join(' ')); + } + else { + setFilterByAgencyState(agency); + } + }; + + const updateFilterByIsBikeAllowed = (isBikeAllowed: string) => { + if (Array.isArray(isBikeAllowed)) { + setByBicycleAllowedState(isBikeAllowed.join(' ')); + } + else { + setByBicycleAllowedState(isBikeAllowed); + } + }; + + const updateFilterByWheelchair = (isWheelchair: string) => { + if (Array.isArray(isWheelchair)) { + setWheelchairAccesibleState(isWheelchair.join(' ')); + } + else { + setWheelchairAccesibleState(isWheelchair); + } + }; + + const updateFilterByMakeAndModel = (makeAndModel: string[]) => { + const extractedValues = makeAndModel.map((str) => { + const [make, model] = str.split('-'); + setMake(make); + setModel(model); + return { make, model }; + }); + if (Array.isArray(makeAndModel)) { + setFilterByMakeAndModelState(makeAndModel.join(' ')); + } + else { + setFilterByMakeAndModelState(makeAndModel); + } + }; + + const updateFilterByPropulsion = (propulsion: string | string[]) => { + if (Array.isArray(propulsion)) { + setFilterByPropulsion(propulsion.join(' ')); + } + else { + setFilterByPropulsion(propulsion); + } }; const updateSelectedVehicle = (vehicleId: string) => { @@ -115,17 +301,32 @@ export const VehiclesListContextProvider = ({ children }) => { const contextValue: VehiclesListContextState = { actions: { + updateFilterByAgency, + updateFilterByIsBikeAllowed, + updateFilterByMakeAndModel, + updateFilterByPropulsion, updateFilterBySearch, + updateFilterByWheelchair, updateSelectedVehicle, }, data: { + agencys: allAgencys || [], filtered: dataFilteredState, + makes_and_models: allMakesAndModels || [], + propulsions: allPropulsions || [], raw: allVehicleData || [], selected: dataSelectedState, + }, filters: { + by_agency: filterByAgencyState, + by_isBicicleAllowed: filterByBicycleAllowedState, + by_isWheelchairAcessible: filterByWheelchairAccesibleState, + by_makeAndModel: filterByMakeAndModelState, + by_propulsion: filterByPropulsionState, by_search: filterBySearchState, selected_vehicle: filteredSelectedVehicleState, + }, flags: { is_loading: allVehiclesLoading, From 75fd96d2f89f84ada01d9a324dcf9c6a47c18005 Mon Sep 17 00:00:00 2001 From: Skasix00 Date: Sat, 1 Feb 2025 11:37:55 +0000 Subject: [PATCH 06/22] Filter 100% working: Conditional Rendering fase --- .../vehicles/VehiclesListToolbar/index.tsx | 1 + frontend/contexts/VehiclesList.context.tsx | 99 ++++++++----------- 2 files changed, 40 insertions(+), 60 deletions(-) diff --git a/frontend/components/vehicles/VehiclesListToolbar/index.tsx b/frontend/components/vehicles/VehiclesListToolbar/index.tsx index 468e8886..e6e25703 100644 --- a/frontend/components/vehicles/VehiclesListToolbar/index.tsx +++ b/frontend/components/vehicles/VehiclesListToolbar/index.tsx @@ -125,6 +125,7 @@ export default function Component() { } onChange={handleMakeAndModelChange} + placeholder="Veículo" radius="sm" value={make_model || ''} data={ diff --git a/frontend/contexts/VehiclesList.context.tsx b/frontend/contexts/VehiclesList.context.tsx index 7c529230..8c47beb5 100644 --- a/frontend/contexts/VehiclesList.context.tsx +++ b/frontend/contexts/VehiclesList.context.tsx @@ -73,8 +73,6 @@ export const VehiclesListContextProvider = ({ children }) => { const [filterBySearchState, setFilterBySearchState] = useQueryState('search'); const [filterByPropulsionState, setFilterByPropulsion] = useQueryState('propulsion'); const [filteredSelectedVehicleState, setfilteredSelectedVehicleState] = useQueryState('search'); - const [make, setMake] = useState(undefined); - const [model, setModel] = useState(undefined); // // B. Fetch data @@ -85,51 +83,53 @@ export const VehiclesListContextProvider = ({ children }) => { // C. Transform data const applyFiltersToData = () => { - // - let filterResult: Vehicle[] = allVehicleData || []; + let filterResult = allVehicleData || []; - if (filterResult.length !== 0) { - if (filterByBicycleAllowedState) { - filterResult = (allVehicleData || []).filter(item => item.bikes_allowed?.toString() === filterByBicycleAllowedState || false); - } - - if (filterByWheelchairAccesibleState) { - filterResult = (allVehicleData || []).filter(item => item.wheelchair_accessible?.toString() === filterByBicycleAllowedState || false); - } - - if (filterByPropulsionState !== '') { - filterResult = (allVehicleData || []).filter(item => item.propulsion === filterByPropulsionState || ''); - } - - if (filterByAgencyState !== '') { - filterResult = (allVehicleData || []).filter(item => item.agency_id === filterByAgencyState || 0); - } + if (filterByBicycleAllowedState) { + filterResult = filterResult.filter( + item => item.bikes_allowed?.toString() === filterByBicycleAllowedState, + ); } - if (filterBySearchState) { - filterResult = (allVehicleData || []).filter(item => - item.license_plate?.toLowerCase().includes(filterBySearchState.toLowerCase()), + if (filterByWheelchairAccesibleState) { + filterResult = filterResult.filter( + item => item.wheelchair_accessible?.toString() === filterByWheelchairAccesibleState, ); } - console.log(make); - if (make) { - filterResult = (allVehicleData || []).filter(item => item.make?.toLocaleLowerCase().includes(make.toLowerCase())); + + if (filterByPropulsionState && filterByPropulsionState.trim() !== '') { + const propulsionValues = filterByPropulsionState.split(' ').filter(Boolean); + filterResult = filterResult.filter(item => + item.propulsion && propulsionValues.includes(item.propulsion), + ); } - if (model) { - filterResult = (allVehicleData || []).filter(item => item.model?.toLocaleLowerCase().includes(model.toLowerCase())); + if (filterByAgencyState && filterByAgencyState.trim() !== '') { + const agencyValues = filterByAgencyState.split(' ').filter(Boolean); + filterResult = filterResult.filter(item => + agencyValues.includes(item.agency_id.toString()), + ); } - if (filterByMakeAndModelState) { - filterResult = (allVehicleData || []).filter(item => - item.make?.toLowerCase().includes(filterByMakeAndModelState.toLowerCase()), + if (filterBySearchState && filterBySearchState.trim() !== '') { + filterResult = filterResult.filter(item => + item.license_plate?.toLowerCase().includes(filterBySearchState.toLowerCase()), ); + console.log(filterBySearchState); } - console.log('fiter: ', filterBySearchState); + if (filterByMakeAndModelState && filterByMakeAndModelState.trim() !== '') { + const makeModelValues = filterByMakeAndModelState.split(' ').filter(Boolean); + filterResult = filterResult.filter((item) => { + return makeModelValues.some((val) => { + const [makeFilter, modelFilter] = val.split('-').map(s => s.trim().toLowerCase()); + const itemMake = item.make?.toLowerCase() || ''; + const itemModel = item.model?.toLowerCase() || ''; + return itemMake.includes(makeFilter) && itemModel.includes(modelFilter); + }); + }); + } return filterResult; - - // }; const getAllMakesAndModels = () => { @@ -236,13 +236,8 @@ export const VehiclesListContextProvider = ({ children }) => { } }; - const updateFilterByAgency = (agency: string | string[]) => { - if (Array.isArray(agency)) { - setFilterByAgencyState(agency.join(' ')); - } - else { - setFilterByAgencyState(agency); - } + const updateFilterByAgency = (agency: string[]) => { + setFilterByAgencyState(agency.join(' ')); }; const updateFilterByIsBikeAllowed = (isBikeAllowed: string) => { @@ -264,27 +259,11 @@ export const VehiclesListContextProvider = ({ children }) => { }; const updateFilterByMakeAndModel = (makeAndModel: string[]) => { - const extractedValues = makeAndModel.map((str) => { - const [make, model] = str.split('-'); - setMake(make); - setModel(model); - return { make, model }; - }); - if (Array.isArray(makeAndModel)) { - setFilterByMakeAndModelState(makeAndModel.join(' ')); - } - else { - setFilterByMakeAndModelState(makeAndModel); - } + setFilterByMakeAndModelState(makeAndModel.join(' ')); }; - const updateFilterByPropulsion = (propulsion: string | string[]) => { - if (Array.isArray(propulsion)) { - setFilterByPropulsion(propulsion.join(' ')); - } - else { - setFilterByPropulsion(propulsion); - } + const updateFilterByPropulsion = (propulsion: string[]) => { + setFilterByPropulsion(propulsion.join(' ')); }; const updateSelectedVehicle = (vehicleId: string) => { From 2876da09ba3128fea788a32c1f882e872bfae242 Mon Sep 17 00:00:00 2001 From: Skasix00 Date: Sat, 1 Feb 2025 17:25:19 +0000 Subject: [PATCH 07/22] Stylyzed the multiselect separated the popup to it's own component --- .../vehicles/VehiclesListMap/index.tsx | 114 ++++++++---------- .../vehicles/VehiclesListMapPopup/index.tsx | 71 +++++++++++ .../styles.module.css | 0 .../vehicles/VehiclesListToolbar/index.tsx | 14 +-- frontend/contexts/VehiclesList.context.tsx | 55 +++++---- frontend/i18n/translations/en.json | 7 +- frontend/i18n/translations/pt.json | 8 +- frontend/themes/_default/default.theme.js | 17 ++- .../_default/overrides/MultiSelect.module.css | 84 +++++++++++++ 9 files changed, 270 insertions(+), 100 deletions(-) create mode 100644 frontend/components/vehicles/VehiclesListMapPopup/index.tsx rename frontend/components/vehicles/{VehiclesListMap => VehiclesListMapPopup}/styles.module.css (100%) create mode 100644 frontend/themes/_default/overrides/MultiSelect.module.css diff --git a/frontend/components/vehicles/VehiclesListMap/index.tsx b/frontend/components/vehicles/VehiclesListMap/index.tsx index ef753372..4a83ec5a 100644 --- a/frontend/components/vehicles/VehiclesListMap/index.tsx +++ b/frontend/components/vehicles/VehiclesListMap/index.tsx @@ -1,40 +1,37 @@ 'use client'; -/* * */ -import { CopyBadge } from '@/components/common/CopyBadge'; import { MapView } from '@/components/map/MapView'; import { MapViewStylePath } from '@/components/map/MapViewStylePath'; -import { MapViewStyleVehicles, MapViewStyleVehiclesInteractiveLayerId, MapViewStyleVehiclesPrimaryLayerId } from '@/components/map/MapViewStyleVehicles'; -import { useStopsContext } from '@/contexts/Stops.context'; +import { + MapViewStyleVehicles, + MapViewStyleVehiclesInteractiveLayerId, + MapViewStyleVehiclesPrimaryLayerId, +} from '@/components/map/MapViewStyleVehicles'; +import { transformStopDataIntoGeoJsonFeature, useStopsContext } from '@/contexts/Stops.context'; import { useVehiclesContext } from '@/contexts/Vehicles.context'; import { useVehiclesListContext } from '@/contexts/VehiclesList.context'; +import { getBaseGeoJsonFeatureCollection } from '@/utils/map.utils'; import { Routes } from '@/utils/routes'; -import { IconBike, IconBikeOff, IconWheelchair, IconWheelchairOff } from '@tabler/icons-react'; -import { Popup, useMap } from '@vis.gl/react-maplibre'; import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson'; import { DateTime } from 'luxon'; import { useEffect, useMemo, useState } from 'react'; -import styles from './styles.module.css'; - -/* * */ +import { VehicleListMapPopup } from '../VehiclesListMapPopup'; export default function Component() { // A. Setup variables - const { vehiclesListMap } = useMap(); const vehiclesListContext = useVehiclesListContext(); const vehiclesContext = useVehiclesContext(); const stopsContext = useStopsContext(); const [activePathShapeGeoJson, setActivePathShapeGeoJson] = useState | FeatureCollection | undefined>(undefined); + const selectedVehicleFromList = vehiclesListContext.data.selected; + const selectedVehicle = selectedVehicleFromList && vehiclesContext.data.vehicles.find(vehicle => vehicle.id === selectedVehicleFromList.id); // B. Fetch Data - const findTodaysDate = () => { - return DateTime.now().toFormat('yyyyLLdd'); - }; + const findTodaysDate = () => DateTime.now().toFormat('yyyyLLdd'); const fetchPattern = async (patternId) => { const date = await findTodaysDate(); - if (patternId) { const actualPattern = await fetch(`${Routes.API}/patterns/${patternId}`).then(res => res.json()); const isAvailable = actualPattern.filter(item => item.valid_on.includes(date)); @@ -44,46 +41,53 @@ export default function Component() { const fetchShape = async (id) => { if (id) { + console.log(id); const shape = await fetch(`${Routes.API}/shapes/${id[0].shape_id}`).then(res => res.json()); return shape.geojson; } }; const activeVehiclesGeoJson = useMemo(() => { - return vehiclesContext.actions.getAllVehiclesGeoJsonFC(); - }, [vehiclesContext.data.vehicles]); - - // const activePathWaypointsGeoJson = useMemo(() => { - // const patternId = vehiclesListContext.data.selected?.pattern_id; - - // if (!vehiclesListContext.data.selected?.pattern_id) return; - - // // vehiclesByPatternId.forEach((pathStop) => { - // // const stopData = stopsContext.actions.getStopById(pathStop.stop_id || ''); - // // if (!stopData) return; - // // const result = transformStopDataIntoGeoJsonFeature(stopData); - // // result.properties = { - // // ...result.properties, - // // }; - // // collection.features.push(result); - // // }); - - // // return collection; - // }, [vehiclesListContext.data.selected, vehiclesContext.data.vehicles]); - - // B. Transform data + if (vehiclesListContext.data.filtered && vehiclesListContext.data.filtered.length > 0) { + const features: Feature[] = []; + vehiclesListContext.data.filtered.forEach((vehicle) => { + const fc = vehiclesContext.actions.getVehicleByIdGeoJsonFC(vehicle.id); + if (fc && fc.features && fc.features.length > 0) { + features.push(fc.features[0]); + } + }); + return { features, type: 'FeatureCollection' as const }; + } + else { + return vehiclesContext.actions.getAllVehiclesGeoJsonFC(); + } + }, [vehiclesListContext.data.filtered, vehiclesContext.data.vehicles]); + + // const activePathFeatureCollection = useMemo(() => { + // if (!linesDetailContext.data.active_pattern?.path) return; + // const collection = getBaseGeoJsonFeatureCollection(); + // linesDetailContext.data.active_pattern.path.forEach((pathStop) => { + // const stopData = stopsContext.actions.getStopById(pathStop.stop_id); + // if (!stopData) return; + // const result = transformStopDataIntoGeoJsonFeature(stopData); + // result.properties = { + // ...result.properties, + // color: vehiclesContext.data.vehicles.map(item => ), + // sequence: pathStop.stop_sequence, + // text_color: linesDetailContext.data.active_pattern?.text_color, + // }; + // collection.features.push(result); + // }); + // return collection; + // }, [linesDetailContext.data.active_pattern, vehiclesContext.data.vehicles]); useEffect(() => { const fetchData = async () => { const patternId = vehiclesListContext.data.selected?.pattern_id; - if (!patternId) return; - const shapeId = await fetchPattern(patternId); if (!shapeId) return; - const shapeGeoJson = await fetchShape(shapeId); - setActivePathShapeGeoJson(shapeGeoJson); }; @@ -104,36 +108,16 @@ export default function Component() { interactiveLayerIds={[MapViewStyleVehiclesInteractiveLayerId]} onClick={handleLayerClick} > - {vehiclesListContext.data.selected - && ( - <> - -
- {vehiclesListContext.data.selected.bikes_allowed ? : } - {vehiclesListContext.data.selected.wheelchair_accessible ? : } -
- - - - - - - - - -
- - )} - - + + {selectedVehicle && ( + + )} ); } diff --git a/frontend/components/vehicles/VehiclesListMapPopup/index.tsx b/frontend/components/vehicles/VehiclesListMapPopup/index.tsx new file mode 100644 index 00000000..c334ac8a --- /dev/null +++ b/frontend/components/vehicles/VehiclesListMapPopup/index.tsx @@ -0,0 +1,71 @@ +import { CopyBadge } from '@/components/common/CopyBadge'; +import { + IconBike, + IconBikeOff, + IconWheelchair, + IconWheelchairOff, +} from '@tabler/icons-react'; +import { Popup } from '@vis.gl/react-maplibre'; + +import styles from './styles.module.css'; + +export function VehicleListMapPopup({ selectedVehicle }) { + return ( + <> + + +
+ {selectedVehicle.bikes_allowed ? : } + {selectedVehicle.wheelchair_accessible ? : } +
+ + + + + + + + + +
+ + + ); +} diff --git a/frontend/components/vehicles/VehiclesListMap/styles.module.css b/frontend/components/vehicles/VehiclesListMapPopup/styles.module.css similarity index 100% rename from frontend/components/vehicles/VehiclesListMap/styles.module.css rename to frontend/components/vehicles/VehiclesListMapPopup/styles.module.css diff --git a/frontend/components/vehicles/VehiclesListToolbar/index.tsx b/frontend/components/vehicles/VehiclesListToolbar/index.tsx index e6e25703..5dd39deb 100644 --- a/frontend/components/vehicles/VehiclesListToolbar/index.tsx +++ b/frontend/components/vehicles/VehiclesListToolbar/index.tsx @@ -94,9 +94,9 @@ export default function Component() { } onChange={handleReducedMobilityChange} - placeholder="Mobilidade Reduzida" + placeholder={t('filter_by.wheel_chair')} radius="sm" - value={reducedMobility} + value={vehiclesListContext.filters.by_isWheelchairAcessible} data={[{ label: 'Não', value: 'false' }, { label: 'Sim', value: 'true' }]} clearable @@ -117,7 +117,7 @@ export default function Component() { data={vehiclesListContext.data?.agencys?.map(a => ({ label: a.name, value: a.agency_id.toString() })) || []} leftSection={} onChange={handleAgencyIdChange} - placeholder="Operador" + placeholder={t('filter_by.operator')} radius="sm" value={agencyId} searchable @@ -125,7 +125,7 @@ export default function Component() { } onChange={handleMakeAndModelChange} - placeholder="Veículo" + placeholder={t('filter_by.make_model')} radius="sm" value={make_model || ''} data={ @@ -146,7 +146,7 @@ export default function Component() { data={vehiclesListContext.data?.propulsions?.map(p => ({ label: p.name, value: p.name })) || []} leftSection={} onChange={handlePropulsionChange} - placeholder="Combustível" + placeholder={t('filter_by.propulsion')} radius="sm" value={propulsion} clearable diff --git a/frontend/contexts/VehiclesList.context.tsx b/frontend/contexts/VehiclesList.context.tsx index 8c47beb5..a8683c09 100644 --- a/frontend/contexts/VehiclesList.context.tsx +++ b/frontend/contexts/VehiclesList.context.tsx @@ -1,19 +1,16 @@ 'use client'; -/* * */ import { Routes } from '@/utils/routes'; import { Vehicle } from '@carrismetropolitana/api-types/vehicles'; import { useQueryState } from 'nuqs'; import { createContext, useContext, useEffect, useState } from 'react'; import useSWR from 'swr'; -/* * */ - interface VehiclesListContextState { actions: { - updateFilterByAgency: (agency: string []) => void + updateFilterByAgency: (agency: string[]) => void updateFilterByIsBikeAllowed: (isBikeAllowed: string) => void - updateFilterByMakeAndModel: (makeAndModel: string []) => void + updateFilterByMakeAndModel: (makeAndModel: string[]) => void updateFilterByPropulsion: (propulsion: string[]) => void updateFilterBySearch: (search: string) => void updateFilterByWheelchair: (isWheelchairAcessible: string) => void @@ -41,8 +38,6 @@ interface VehiclesListContextState { } } -/* * */ - const VehiclesListContext = createContext(undefined); export function useVehiclesListContext() { @@ -53,17 +48,16 @@ export function useVehiclesListContext() { return context; } -/* * */ - export const VehiclesListContextProvider = ({ children }) => { - // - // // A. Setup variables const [dataFilteredState, setDataFilteredState] = useState([]); + const [dataFilteredVehicleState, setDataFilteredVehicleState] = useState([]); const [dataSelectedState, setDataSelectedState] = useState(null); const [allAgencys, setAllAgencys] = useState<{ agency_id: number, name: string }[]>([]); - const [allMakesAndModels, setAllMakesAndModels] = useState<{ id: number, models: { id: number, name: string }[], name: string }[]>([]); + const [allMakesAndModels, setAllMakesAndModels] = useState< + { id: number, models: { id: number, name: string }[], name: string }[] + >([]); const [allPropulsions, setAllPropulsions] = useState([]); const [filterByWheelchairAccesibleState, setWheelchairAccesibleState] = useQueryState('isWheelchair'); @@ -72,16 +66,13 @@ export const VehiclesListContextProvider = ({ children }) => { const [filterByMakeAndModelState, setFilterByMakeAndModelState] = useQueryState('makeModel'); const [filterBySearchState, setFilterBySearchState] = useQueryState('search'); const [filterByPropulsionState, setFilterByPropulsion] = useQueryState('propulsion'); - const [filteredSelectedVehicleState, setfilteredSelectedVehicleState] = useQueryState('search'); // // B. Fetch data - const { data: allVehicleData, isLoading: allVehiclesLoading } = useSWR(`${Routes.API}/vehicles`, { refreshInterval: 30000 }); // // C. Transform data - const applyFiltersToData = () => { let filterResult = allVehicleData || []; @@ -115,7 +106,6 @@ export const VehiclesListContextProvider = ({ children }) => { filterResult = filterResult.filter(item => item.license_plate?.toLowerCase().includes(filterBySearchState.toLowerCase()), ); - console.log(filterBySearchState); } if (filterByMakeAndModelState && filterByMakeAndModelState.trim() !== '') { @@ -189,6 +179,7 @@ export const VehiclesListContextProvider = ({ children }) => { setAllAgencys(agencys); return agencys; }; + const getAllPropulsion = () => { if (!allVehicleData) return []; @@ -212,11 +203,27 @@ export const VehiclesListContextProvider = ({ children }) => { }; useEffect(() => { - if (filterBySearchState || filterByAgencyState || filterByBicycleAllowedState || filterByMakeAndModelState || filterByWheelchairAccesibleState) { + if ( + filterBySearchState + || filterByAgencyState + || filterByBicycleAllowedState + || filterByMakeAndModelState + || filterByWheelchairAccesibleState + ) { const filteredVehicles = applyFiltersToData(); setDataFilteredState(filteredVehicles || []); } - }, [filterBySearchState, filterByAgencyState, filterByBicycleAllowedState, filterByMakeAndModelState, filterByWheelchairAccesibleState]); + else { + setDataFilteredState(allVehicleData || []); + } + }, [ + filterBySearchState, + filterByAgencyState, + filterByBicycleAllowedState, + filterByMakeAndModelState, + filterByWheelchairAccesibleState, + allVehicleData, + ]); useEffect(() => { if (!allVehicleData && !allVehiclesLoading) return; @@ -224,6 +231,7 @@ export const VehiclesListContextProvider = ({ children }) => { getAllMakesAndModels(); getAllPropulsion(); }, [allVehicleData]); + // // D. Handle actions @@ -268,10 +276,11 @@ export const VehiclesListContextProvider = ({ children }) => { const updateSelectedVehicle = (vehicleId: string) => { if (!allVehicleData) return; - const foundVehicleData = allVehicleData.find(item => item.id === vehicleId) || null; + const foundVehicleData = allVehicleData.filter(item => item.id === vehicleId) || null; + if (foundVehicleData) { - setDataSelectedState(foundVehicleData); - setfilteredSelectedVehicleState(vehicleId); + setDataSelectedState(foundVehicleData[0] || null); + setDataFilteredVehicleState([foundVehicleData[0] || null]); } }; @@ -295,7 +304,6 @@ export const VehiclesListContextProvider = ({ children }) => { propulsions: allPropulsions || [], raw: allVehicleData || [], selected: dataSelectedState, - }, filters: { by_agency: filterByAgencyState, @@ -304,8 +312,7 @@ export const VehiclesListContextProvider = ({ children }) => { by_makeAndModel: filterByMakeAndModelState, by_propulsion: filterByPropulsionState, by_search: filterBySearchState, - selected_vehicle: filteredSelectedVehicleState, - + selected_vehicle: dataSelectedState?.id || null, }, flags: { is_loading: allVehiclesLoading, diff --git a/frontend/i18n/translations/en.json b/frontend/i18n/translations/en.json index 28321da0..78ac0fa4 100644 --- a/frontend/i18n/translations/en.json +++ b/frontend/i18n/translations/en.json @@ -1516,7 +1516,12 @@ "vehicles": { "VehiclesListToolbar": { "filter_by": { - "search": "Search by plates, number os passengers, vehicle number." + "bicycle": "Permite Bicicletas", + "make_model": "Make and Model", + "operator": "Agency", + "propulsion": "Fuel Type", + "search": "Search by license plate", + "wheel_chair": "Reduced Mobility" }, "found_items_counter": "{count, plural, =0 {No vehicle found.} one {Found # vehicle} other {Found # vehicles}}", "heading": "Veículos", diff --git a/frontend/i18n/translations/pt.json b/frontend/i18n/translations/pt.json index e4ddad8a..0989d733 100644 --- a/frontend/i18n/translations/pt.json +++ b/frontend/i18n/translations/pt.json @@ -1516,7 +1516,13 @@ "vehicles": { "VehiclesListToolbar": { "filter_by": { - "search": "Pesquise por marticula, numero de passsageiros, por numero." + "bicycle": "Permite Bicicletas", + "make_model": "Marca e Modelo", + "operator": "Operador", + "propulsion": "Combustível", + "search": "Pesquise por matrícula", + "wheel_chair": "Mobilidade Reduzida" + }, "found_items_counter": "{count, plural, =0 {Nenhum Veículo encontrado.} one {Encontrado # veículo} other {Encontrados # veículos}}", "heading": "Veículos", diff --git a/frontend/themes/_default/default.theme.js b/frontend/themes/_default/default.theme.js index a2b7606e..ef595659 100644 --- a/frontend/themes/_default/default.theme.js +++ b/frontend/themes/_default/default.theme.js @@ -18,12 +18,13 @@ import '@/themes/_default/styles/wordpress.css'; import AccordionOverride from '@/themes/_default/overrides/Accordion.module.css'; import ButtonOverride from '@/themes/_default/overrides/Button.module.css'; +import MultiSelectOverride from '@/themes/_default/overrides/MultiSelect.module.css'; import SegmentedControlOverride from '@/themes/_default/overrides/SegmentedControl.module.css'; import SelectOverride from '@/themes/_default/overrides/Select.module.css'; import SkeletonOverride from '@/themes/_default/overrides/Skeleton.module.css'; import TextInputOverride from '@/themes/_default/overrides/TextInput.module.css'; import combineClasses from '@/utils/combineClasses'; -import { Accordion, Button, createTheme, SegmentedControl, Select, Skeleton, TextInput } from '@mantine/core'; +import { Accordion, Button, createTheme, MultiSelect, SegmentedControl, Select, Skeleton, TextInput } from '@mantine/core'; import { IconCaretLeftFilled } from '@tabler/icons-react'; /* * */ @@ -75,6 +76,19 @@ export default createTheme({ }, }), + MultiSelect: MultiSelect.extend({ + classNames: () => { + let defaultClasses = { + dropdown: MultiSelectOverride.dropdown, + input: MultiSelectOverride.input, + option: MultiSelectOverride.option, + section: MultiSelectOverride.section, + wrapper: MultiSelectOverride.wrapper, + }; + return defaultClasses; + }, + }), + SegmentedControl: SegmentedControl.extend({ classNames: (_, props) => { let defaultClasses = { @@ -113,7 +127,6 @@ export default createTheme({ return defaultClasses; }, }), - TextInput: TextInput.extend({ classNames: (_, props) => { let defaultClasses = { diff --git a/frontend/themes/_default/overrides/MultiSelect.module.css b/frontend/themes/_default/overrides/MultiSelect.module.css new file mode 100644 index 00000000..ad5f50a5 --- /dev/null +++ b/frontend/themes/_default/overrides/MultiSelect.module.css @@ -0,0 +1,84 @@ +/* * */ +/* INPUT */ + +.input { + height: auto; + max-height: none; + padding: var(--size-spacing-15); + font-size: 18px; + font-weight: var(--font-weight-medium); + line-height: 1; + cursor: pointer; + background-color: var(--color-system-background-100); + border: 1px solid var(--color-system-border-200); + border-radius: 5px; +} + +.input::placeholder { + color: var(--color-system-text-300); +} + +.wrapper[data-with-left-section="true"] .input { + padding-left: calc(var(--size-spacing-15) + 33px); +} + +.wrapper[data-with-right-section="true"] .input { + padding-right: calc(var(--size-spacing-15) + 30px); +} + +/* * */ +/* SECTION */ + +.section { + display: flex; + width: auto; + padding: var(--size-spacing-15); + color: var(--color-system-text-300); +} + +.section[data-position="left"] { + pointer-events: none; +} + +.section.variantWhite { + color: var(--color-system-text-300); +} + +/* * */ +/* DROPDOWN */ + +.dropdown { + padding: 6px var(--size-spacing-5); + overflow: hidden; + color: var(--color-system-text-300); + background-color: var(--color-system-background-100); + border: 1px solid var(--color-system-border-200); + border-radius: 5px; + box-shadow: 0 0 20px 0 rgb(0 0 0 / 5%); +} + +/* * */ +/* OPTION */ + +.option { + padding: var(--size-spacing-10); + font-size: 16px; + font-weight: var(--font-weight-medium); + color: var(--color-system-text-200); + border-radius: 3px; +} + +.option:hover { + color: var(--color-system-text-100); + background-color: var(--color-system-background-200); +} + +.option[data-combobox-selected="true"], +.option[data-combobox-active="true"] { + color: var(--color-system-text-100); + background-color: var(--color-system-border-100); +} + +.option[data-checked="true"] { + color: var(--color-system-text-100); +} \ No newline at end of file From b43064076a03945c59e2179f9bfb8951fd4d75ea Mon Sep 17 00:00:00 2001 From: Skasix00 Date: Mon, 3 Feb 2025 14:32:07 +0000 Subject: [PATCH 08/22] Refactored some code on vehicle map and vehiclelistcontext --- .../app/(views)/(website)/vehicles/layout.tsx | 12 +- .../VehicleListMapPopupBadge/index.tsx | 39 +++++++ .../styles.module.css | 38 +++++++ .../vehicles/VehiclesListMap/index.tsx | 64 +++++------ .../vehicles/VehiclesListMapPopup/index.tsx | 103 ++++++++++-------- .../VehiclesListMapPopup/styles.module.css | 16 ++- frontend/contexts/VehiclesList.context.tsx | 18 +-- 7 files changed, 186 insertions(+), 104 deletions(-) create mode 100644 frontend/components/vehicles/VehicleListMapPopupBadge/index.tsx create mode 100644 frontend/components/vehicles/VehicleListMapPopupBadge/styles.module.css diff --git a/frontend/app/(views)/(website)/vehicles/layout.tsx b/frontend/app/(views)/(website)/vehicles/layout.tsx index dd89aa1a..b44b0744 100644 --- a/frontend/app/(views)/(website)/vehicles/layout.tsx +++ b/frontend/app/(views)/(website)/vehicles/layout.tsx @@ -1,19 +1,13 @@ /* * */ -import { StopsContextProvider } from '@/contexts/Stops.context'; -import { VehiclesContextProvider } from '@/contexts/Vehicles.context'; import { VehiclesListContextProvider } from '@/contexts/VehiclesList.context'; /* * */ export default function Layout({ children }) { return ( - - - - {children} - - - + + {children} + ); } diff --git a/frontend/components/vehicles/VehicleListMapPopupBadge/index.tsx b/frontend/components/vehicles/VehicleListMapPopupBadge/index.tsx new file mode 100644 index 00000000..32849948 --- /dev/null +++ b/frontend/components/vehicles/VehicleListMapPopupBadge/index.tsx @@ -0,0 +1,39 @@ +'use client'; + +/* * */ + +import type { Line } from '@carrismetropolitana/api-types/network'; + +import classNames from 'classnames/bind'; + +import styles from './styles.module.css'; + +/* * */ + +interface Props { + color?: string + lineData?: Line + shortName?: string + textColor?: string +} + +/* * */ + +const cx = classNames.bind(styles); + +/* * */ + +export function VehicleListMapPopupBadge({ lineData }: Props) { + // A. Render components + return ( + <> +
+ {lineData?.short_name || '• • •'} +
+
+

{lineData?.long_name}

+
+ + ); + // +} diff --git a/frontend/components/vehicles/VehicleListMapPopupBadge/styles.module.css b/frontend/components/vehicles/VehicleListMapPopupBadge/styles.module.css new file mode 100644 index 00000000..0c6b2b0b --- /dev/null +++ b/frontend/components/vehicles/VehicleListMapPopupBadge/styles.module.css @@ -0,0 +1,38 @@ +/* * */ +/* BADGE */ + +.badge { + display: flex; + align-items: center; + justify-content: center; + border-radius: 999px; + color: var(--color-system-background-100); + background-color: var(--color-system-text-200); + font-weight: var(--font-weight-extrabold); + letter-spacing: 1px; + line-height: 1; + position: relative; + +} + +.badge.md { + font-size: 16px; + min-width: 65px; + max-width: 65px; + min-height: 26px; + max-height: 26px; + text-align: center; + vertical-align: middle; + display: flex; + +} + +/* * */ + +/* LINE HEADER */ +.line_name{ + color: var(--color-system-text-100); + font-size: var(--font-size-subtitle); + font-weight: var(--font-weight-extrabold); +} +/* * */ \ No newline at end of file diff --git a/frontend/components/vehicles/VehiclesListMap/index.tsx b/frontend/components/vehicles/VehiclesListMap/index.tsx index 4a83ec5a..d5a01875 100644 --- a/frontend/components/vehicles/VehiclesListMap/index.tsx +++ b/frontend/components/vehicles/VehiclesListMap/index.tsx @@ -7,11 +7,13 @@ import { MapViewStyleVehiclesInteractiveLayerId, MapViewStyleVehiclesPrimaryLayerId, } from '@/components/map/MapViewStyleVehicles'; +import { useLinesContext } from '@/contexts/Lines.context'; import { transformStopDataIntoGeoJsonFeature, useStopsContext } from '@/contexts/Stops.context'; import { useVehiclesContext } from '@/contexts/Vehicles.context'; import { useVehiclesListContext } from '@/contexts/VehiclesList.context'; import { getBaseGeoJsonFeatureCollection } from '@/utils/map.utils'; import { Routes } from '@/utils/routes'; +import { Shape } from '@carrismetropolitana/api-types/network'; import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson'; import { DateTime } from 'luxon'; import { useEffect, useMemo, useState } from 'react'; @@ -22,30 +24,39 @@ export default function Component() { // A. Setup variables const vehiclesListContext = useVehiclesListContext(); const vehiclesContext = useVehiclesContext(); + const LinesContext = useLinesContext(); const stopsContext = useStopsContext(); + const [shapeId, setShapeId] = useState(undefined); + const [patternId, setPattern] = useState(); const [activePathShapeGeoJson, setActivePathShapeGeoJson] = useState | FeatureCollection | undefined>(undefined); const selectedVehicleFromList = vehiclesListContext.data.selected; const selectedVehicle = selectedVehicleFromList && vehiclesContext.data.vehicles.find(vehicle => vehicle.id === selectedVehicleFromList.id); // B. Fetch Data + + const lineData = LinesContext.actions.getLineDataById(selectedVehicle?.line_id || ''); + const findTodaysDate = () => DateTime.now().toFormat('yyyyLLdd'); - const fetchPattern = async (patternId) => { + const fetchPattern = useMemo(async () => { + if (!selectedVehicle) return []; const date = await findTodaysDate(); + const patternId = vehiclesListContext.data.selected?.pattern_id; + if (patternId) { const actualPattern = await fetch(`${Routes.API}/patterns/${patternId}`).then(res => res.json()); const isAvailable = actualPattern.filter(item => item.valid_on.includes(date)); + setShapeId(isAvailable); return isAvailable; } - }; + }, [vehiclesListContext.data.selected]); - const fetchShape = async (id) => { - if (id) { - console.log(id); - const shape = await fetch(`${Routes.API}/shapes/${id[0].shape_id}`).then(res => res.json()); - return shape.geojson; - } - }; + const fetchShape = useMemo(async () => { + if (!shapeId) return []; + const shape = await fetch(`${Routes.API}/shapes/${shapeId[0].shape_id}`).then(res => res.json()); + setActivePathShapeGeoJson(shape.geojson); + return shape.geojson; + }, [shapeId]); const activeVehiclesGeoJson = useMemo(() => { if (vehiclesListContext.data.filtered && vehiclesListContext.data.filtered.length > 0) { @@ -63,44 +74,33 @@ export default function Component() { } }, [vehiclesListContext.data.filtered, vehiclesContext.data.vehicles]); - // const activePathFeatureCollection = useMemo(() => { - // if (!linesDetailContext.data.active_pattern?.path) return; + // const activePathWaypointsGeoJson = useMemo(() => { + // if (!stopsDetailContext.data.active_pattern_group?.path) return; // const collection = getBaseGeoJsonFeatureCollection(); - // linesDetailContext.data.active_pattern.path.forEach((pathStop) => { + // stopsDetailContext.data.active_pattern_group.path.forEach((pathStop) => { // const stopData = stopsContext.actions.getStopById(pathStop.stop_id); // if (!stopData) return; // const result = transformStopDataIntoGeoJsonFeature(stopData); // result.properties = { // ...result.properties, - // color: vehiclesContext.data.vehicles.map(item => ), - // sequence: pathStop.stop_sequence, - // text_color: linesDetailContext.data.active_pattern?.text_color, + // color: stopsDetailContext.data.active_pattern_group?.color, + // text_color: stopsDetailContext.data.active_pattern_group?.text_color, // }; // collection.features.push(result); // }); // return collection; - // }, [linesDetailContext.data.active_pattern, vehiclesContext.data.vehicles]); - - useEffect(() => { - const fetchData = async () => { - const patternId = vehiclesListContext.data.selected?.pattern_id; - if (!patternId) return; - const shapeId = await fetchPattern(patternId); - if (!shapeId) return; - const shapeGeoJson = await fetchShape(shapeId); - setActivePathShapeGeoJson(shapeGeoJson); - }; - - fetchData(); - }, [vehiclesListContext.data.selected]); + // }, [stopsDetailContext.data.active_trip_id, vehiclesContext.data.vehicles]); // C. Handle actions function handleLayerClick(event) { - if (event.features.length !== 0) { + console.log('event:', event); + if (event.features.length === 0) { + setActivePathShapeGeoJson(undefined); + } + if (event.features.length !== 0 && event.features[0].source === 'default-source-vehicles') { vehiclesListContext.actions.updateSelectedVehicle(event.features[0].properties.id); } } - // D. Render component return ( {selectedVehicle && ( - + )} ); diff --git a/frontend/components/vehicles/VehiclesListMapPopup/index.tsx b/frontend/components/vehicles/VehiclesListMapPopup/index.tsx index c334ac8a..0db3c7be 100644 --- a/frontend/components/vehicles/VehiclesListMapPopup/index.tsx +++ b/frontend/components/vehicles/VehiclesListMapPopup/index.tsx @@ -1,7 +1,9 @@ -import { CopyBadge } from '@/components/common/CopyBadge'; +import { VehicleListMapPopupBadge } from '@/components/vehicles/VehicleListMapPopupBadge'; +import { Table } from '@mantine/core'; import { IconBike, IconBikeOff, + IconCodeVariable, IconWheelchair, IconWheelchairOff, } from '@tabler/icons-react'; @@ -9,63 +11,76 @@ import { Popup } from '@vis.gl/react-maplibre'; import styles from './styles.module.css'; -export function VehicleListMapPopup({ selectedVehicle }) { +export function VehicleListMapPopup({ lineData, selectedVehicle }) { + // A. Setup Variables + + const id = selectedVehicle.id; + const license_plate = selectedVehicle.license_plate; + const capacity_seated = selectedVehicle.capacity_seated; + const capacity_standing = selectedVehicle.capacity_standing; + const capacity_total = selectedVehicle.capacity_total; + const make = selectedVehicle.make; + const model = selectedVehicle.model; + const propulsion = selectedVehicle.propulsion; + const emission_class = selectedVehicle.emission_class; + const current_status = selectedVehicle.current_status; + + // + + // B. Render Components return ( <> - + +
+ +
+
{selectedVehicle.bikes_allowed ? : } {selectedVehicle.wheelchair_accessible ? : } +

{license_plate ? license_plate : 'Não definido'}

- - - - - - - - - -
+
+ + + + + Campo + Valor + + + + {[ + { label: 'ID', value: id ? id : 'Não Definido.' }, + { label: 'Lugares Sentados', value: capacity_seated ? capacity_seated : 'Não Definido.' }, + { label: 'Lugares em pé', value: capacity_standing ? capacity_standing : 'Não Definido.' }, + { label: 'Capacidade Total', value: capacity_total ? capacity_total : 'Não Definido.' }, + { label: 'Marca', value: make ? make : 'Não Definido.' }, + { label: 'Modelo', value: model ? model : 'Não Definido.' }, + { label: 'Propulsão', value: propulsion ? propulsion : 'Não Definido.' }, + { label: 'Emission Class', value: emission_class ? emission_class : 'Não Definido.' }, + { label: 'Estado Atual', value: current_status ? current_status : 'Não Definido.' }, + ].map(row => ( + + {row.label} + {row.value} + + ))} + +
+
+
+ ); } diff --git a/frontend/components/vehicles/VehiclesListMapPopup/styles.module.css b/frontend/components/vehicles/VehiclesListMapPopup/styles.module.css index f6e79780..43629021 100644 --- a/frontend/components/vehicles/VehiclesListMapPopup/styles.module.css +++ b/frontend/components/vehicles/VehiclesListMapPopup/styles.module.css @@ -4,7 +4,7 @@ display: flex; flex-direction: column; gap: 10px; - align-items: flex-start; + align-items: center; } .popupWrapper p{ @@ -14,10 +14,14 @@ /* * */ /* ICON LIST */ .iconList{ - padding-left:8px; - line-height:17px; - vertical-align:middle; - width:100%; - display:inline-block; + flex-direction: row !important; +} +/* * */ +/* LICENSE PLATE */ +.license_plate{ + border: 2px solid var(--color-system-text-200); + font-size: var(--font-size-text); + padding: 2px; + border-radius: 10px; } /* * */ diff --git a/frontend/contexts/VehiclesList.context.tsx b/frontend/contexts/VehiclesList.context.tsx index a8683c09..2108e9d1 100644 --- a/frontend/contexts/VehiclesList.context.tsx +++ b/frontend/contexts/VehiclesList.context.tsx @@ -3,6 +3,7 @@ import { Routes } from '@/utils/routes'; import { Vehicle } from '@carrismetropolitana/api-types/vehicles'; import { useQueryState } from 'nuqs'; +import { parseAsArrayOf, parseAsString } from 'nuqs'; import { createContext, useContext, useEffect, useState } from 'react'; import useSWR from 'swr'; @@ -89,6 +90,7 @@ export const VehiclesListContextProvider = ({ children }) => { } if (filterByPropulsionState && filterByPropulsionState.trim() !== '') { + console.log('filterByPropulsionState', filterByPropulsionState); const propulsionValues = filterByPropulsionState.split(' ').filter(Boolean); filterResult = filterResult.filter(item => item.propulsion && propulsionValues.includes(item.propulsion), @@ -203,24 +205,14 @@ export const VehiclesListContextProvider = ({ children }) => { }; useEffect(() => { - if ( - filterBySearchState - || filterByAgencyState - || filterByBicycleAllowedState - || filterByMakeAndModelState - || filterByWheelchairAccesibleState - ) { - const filteredVehicles = applyFiltersToData(); - setDataFilteredState(filteredVehicles || []); - } - else { - setDataFilteredState(allVehicleData || []); - } + const filteredVehicles = applyFiltersToData(); + setDataFilteredState(filteredVehicles || []); }, [ filterBySearchState, filterByAgencyState, filterByBicycleAllowedState, filterByMakeAndModelState, + filterByPropulsionState, filterByWheelchairAccesibleState, allVehicleData, ]); From 58ac1d5359ab8f7179a90add7266d783564a5856 Mon Sep 17 00:00:00 2001 From: Skasix00 Date: Mon, 3 Feb 2025 15:22:23 +0000 Subject: [PATCH 09/22] Added coloes to the lines and added stops waypoints --- .../vehicles/VehiclesListMap/index.tsx | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/frontend/components/vehicles/VehiclesListMap/index.tsx b/frontend/components/vehicles/VehiclesListMap/index.tsx index d5a01875..d5217c4b 100644 --- a/frontend/components/vehicles/VehiclesListMap/index.tsx +++ b/frontend/components/vehicles/VehiclesListMap/index.tsx @@ -13,10 +13,10 @@ import { useVehiclesContext } from '@/contexts/Vehicles.context'; import { useVehiclesListContext } from '@/contexts/VehiclesList.context'; import { getBaseGeoJsonFeatureCollection } from '@/utils/map.utils'; import { Routes } from '@/utils/routes'; -import { Shape } from '@carrismetropolitana/api-types/network'; +import { Pattern, Shape } from '@carrismetropolitana/api-types/network'; import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson'; import { DateTime } from 'luxon'; -import { useEffect, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import { VehicleListMapPopup } from '../VehiclesListMapPopup'; @@ -26,8 +26,7 @@ export default function Component() { const vehiclesContext = useVehiclesContext(); const LinesContext = useLinesContext(); const stopsContext = useStopsContext(); - const [shapeId, setShapeId] = useState(undefined); - const [patternId, setPattern] = useState(); + const [pattern, setPattern] = useState(undefined); const [activePathShapeGeoJson, setActivePathShapeGeoJson] = useState | FeatureCollection | undefined>(undefined); const selectedVehicleFromList = vehiclesListContext.data.selected; const selectedVehicle = selectedVehicleFromList && vehiclesContext.data.vehicles.find(vehicle => vehicle.id === selectedVehicleFromList.id); @@ -46,17 +45,20 @@ export default function Component() { if (patternId) { const actualPattern = await fetch(`${Routes.API}/patterns/${patternId}`).then(res => res.json()); const isAvailable = actualPattern.filter(item => item.valid_on.includes(date)); - setShapeId(isAvailable); + setPattern(isAvailable); return isAvailable; } }, [vehiclesListContext.data.selected]); const fetchShape = useMemo(async () => { - if (!shapeId) return []; - const shape = await fetch(`${Routes.API}/shapes/${shapeId[0].shape_id}`).then(res => res.json()); + if (!pattern) return []; + const color = pattern.map(item => item.color.toString()); + console.log(color); + const shape = await fetch(`${Routes.API}/shapes/${pattern[0].shape_id}`).then(res => res.json()); + shape.geojson.properties = { ...shape.geojson.properties, color: color[0] }; setActivePathShapeGeoJson(shape.geojson); return shape.geojson; - }, [shapeId]); + }, [pattern]); const activeVehiclesGeoJson = useMemo(() => { if (vehiclesListContext.data.filtered && vehiclesListContext.data.filtered.length > 0) { @@ -74,28 +76,28 @@ export default function Component() { } }, [vehiclesListContext.data.filtered, vehiclesContext.data.vehicles]); - // const activePathWaypointsGeoJson = useMemo(() => { - // if (!stopsDetailContext.data.active_pattern_group?.path) return; - // const collection = getBaseGeoJsonFeatureCollection(); - // stopsDetailContext.data.active_pattern_group.path.forEach((pathStop) => { - // const stopData = stopsContext.actions.getStopById(pathStop.stop_id); - // if (!stopData) return; - // const result = transformStopDataIntoGeoJsonFeature(stopData); - // result.properties = { - // ...result.properties, - // color: stopsDetailContext.data.active_pattern_group?.color, - // text_color: stopsDetailContext.data.active_pattern_group?.text_color, - // }; - // collection.features.push(result); - // }); - // return collection; - // }, [stopsDetailContext.data.active_trip_id, vehiclesContext.data.vehicles]); + const activePathWaypointsGeoJson = useMemo(() => { + if (!pattern) return; + const collection = getBaseGeoJsonFeatureCollection(); + pattern.map(pattern => pattern.path.forEach((pathStop) => { + const stopData = stopsContext.actions.getStopById(pathStop.stop_id); + if (!stopData) return; + const result = transformStopDataIntoGeoJsonFeature(stopData); + result.properties = { + ...result.properties, + color: pattern.color, + text_color: pattern.text_color, + }; + collection.features.push(result); + })); + return collection; + }, [pattern, vehiclesContext.data.vehicles]); // C. Handle actions function handleLayerClick(event) { - console.log('event:', event); if (event.features.length === 0) { setActivePathShapeGeoJson(undefined); + setPattern(undefined); } if (event.features.length !== 0 && event.features[0].source === 'default-source-vehicles') { vehiclesListContext.actions.updateSelectedVehicle(event.features[0].properties.id); @@ -113,7 +115,7 @@ export default function Component() { {selectedVehicle && ( From 12416be8a54d7812de00ba50777d376190176f37 Mon Sep 17 00:00:00 2001 From: Skasix00 Date: Mon, 3 Feb 2025 17:04:27 +0000 Subject: [PATCH 10/22] Finished the popup --- .../vehicles/VehiclesListMap/index.tsx | 6 +- .../vehicles/VehiclesListMapPopup/index.tsx | 60 +++++++++---------- .../VehiclesListMapPopup/styles.module.css | 30 +++++----- frontend/contexts/VehiclesList.context.tsx | 1 - 4 files changed, 46 insertions(+), 51 deletions(-) diff --git a/frontend/components/vehicles/VehiclesListMap/index.tsx b/frontend/components/vehicles/VehiclesListMap/index.tsx index d5217c4b..a404de0a 100644 --- a/frontend/components/vehicles/VehiclesListMap/index.tsx +++ b/frontend/components/vehicles/VehiclesListMap/index.tsx @@ -45,7 +45,9 @@ export default function Component() { if (patternId) { const actualPattern = await fetch(`${Routes.API}/patterns/${patternId}`).then(res => res.json()); const isAvailable = actualPattern.filter(item => item.valid_on.includes(date)); + setPattern(isAvailable); + return isAvailable; } }, [vehiclesListContext.data.selected]); @@ -53,10 +55,12 @@ export default function Component() { const fetchShape = useMemo(async () => { if (!pattern) return []; const color = pattern.map(item => item.color.toString()); - console.log(color); const shape = await fetch(`${Routes.API}/shapes/${pattern[0].shape_id}`).then(res => res.json()); + shape.geojson.properties = { ...shape.geojson.properties, color: color[0] }; + setActivePathShapeGeoJson(shape.geojson); + return shape.geojson; }, [pattern]); diff --git a/frontend/components/vehicles/VehiclesListMapPopup/index.tsx b/frontend/components/vehicles/VehiclesListMapPopup/index.tsx index 0db3c7be..8466a366 100644 --- a/frontend/components/vehicles/VehiclesListMapPopup/index.tsx +++ b/frontend/components/vehicles/VehiclesListMapPopup/index.tsx @@ -3,7 +3,6 @@ import { Table } from '@mantine/core'; import { IconBike, IconBikeOff, - IconCodeVariable, IconWheelchair, IconWheelchairOff, } from '@tabler/icons-react'; @@ -39,46 +38,41 @@ export function VehicleListMapPopup({ lineData, selectedVehicle }) { latitude={selectedVehicle.lat} longitude={selectedVehicle.lon} > - -
- -
+
{selectedVehicle.bikes_allowed ? : } {selectedVehicle.wheelchair_accessible ? : } -

{license_plate ? license_plate : 'Não definido'}

+

{license_plate ? license_plate : 'Não Definido'}

- - - - - Campo - Valor +
+ + + Campo + Valor + + + + {[ + { label: 'ID', value: id ? id : 'Não Definido.' }, + { label: 'Lugares Sentados', value: capacity_seated ? capacity_seated : 'Não Definido' }, + { label: 'Lugares em pé', value: capacity_standing ? capacity_standing : 'Não Definido' }, + { label: 'Capacidade Total', value: capacity_total ? capacity_total : 'Não Definido' }, + { label: 'Marca', value: make ? make : 'Não Definido' }, + { label: 'Modelo', value: model ? model : 'Não Definido.' }, + { label: 'Propulsão', value: propulsion ? propulsion : 'Não Definido' }, + { label: 'Emission Class', value: emission_class ? emission_class : 'Não Definido' }, + { label: 'Estado Atual', value: current_status ? current_status : 'Não Definido' }, + ].map(row => ( + + {row.label} + {row.value} - - - {[ - { label: 'ID', value: id ? id : 'Não Definido.' }, - { label: 'Lugares Sentados', value: capacity_seated ? capacity_seated : 'Não Definido.' }, - { label: 'Lugares em pé', value: capacity_standing ? capacity_standing : 'Não Definido.' }, - { label: 'Capacidade Total', value: capacity_total ? capacity_total : 'Não Definido.' }, - { label: 'Marca', value: make ? make : 'Não Definido.' }, - { label: 'Modelo', value: model ? model : 'Não Definido.' }, - { label: 'Propulsão', value: propulsion ? propulsion : 'Não Definido.' }, - { label: 'Emission Class', value: emission_class ? emission_class : 'Não Definido.' }, - { label: 'Estado Atual', value: current_status ? current_status : 'Não Definido.' }, - ].map(row => ( - - {row.label} - {row.value} - - ))} - -
-
+ ))} + +
diff --git a/frontend/components/vehicles/VehiclesListMapPopup/styles.module.css b/frontend/components/vehicles/VehiclesListMapPopup/styles.module.css index 43629021..3518fc39 100644 --- a/frontend/components/vehicles/VehiclesListMapPopup/styles.module.css +++ b/frontend/components/vehicles/VehiclesListMapPopup/styles.module.css @@ -1,27 +1,25 @@ /* POPUP WRAPPER */ - -.popupWrapper div div { - display: flex; - flex-direction: column; - gap: 10px; - align-items: center; - } - -.popupWrapper p{ - color: var(--color-system-text-200); - font-size: var(--font-size-text); +.popupWrapper div div{ + display: flex; + flex-direction: column; + gap: 5px; + align-items: center; + padding: 5px; + border-radius: 10px; + height: 200px; + overflow: scroll; } -/* * */ + /* ICON LIST */ -.iconList{ +.iconList { flex-direction: row !important; + justify-content: center; } -/* * */ + /* LICENSE PLATE */ -.license_plate{ +.license_plate { border: 2px solid var(--color-system-text-200); font-size: var(--font-size-text); padding: 2px; border-radius: 10px; } -/* * */ diff --git a/frontend/contexts/VehiclesList.context.tsx b/frontend/contexts/VehiclesList.context.tsx index 2108e9d1..87a92309 100644 --- a/frontend/contexts/VehiclesList.context.tsx +++ b/frontend/contexts/VehiclesList.context.tsx @@ -90,7 +90,6 @@ export const VehiclesListContextProvider = ({ children }) => { } if (filterByPropulsionState && filterByPropulsionState.trim() !== '') { - console.log('filterByPropulsionState', filterByPropulsionState); const propulsionValues = filterByPropulsionState.split(' ').filter(Boolean); filterResult = filterResult.filter(item => item.propulsion && propulsionValues.includes(item.propulsion), From 2cbafd00411c9cc8ac5f8ec0a68cf2548698f88b Mon Sep 17 00:00:00 2001 From: Skasix00 Date: Tue, 4 Feb 2025 17:41:06 +0000 Subject: [PATCH 11/22] Ruff layout added, all the functionallity converted to this layout --- .../vehicles/VehiclesList/index.tsx | 19 +++++++++---- .../vehicles/VehiclesList/styles.module.css | 28 +++++++++++-------- .../vehicles/VehiclesListMap/index.tsx | 13 ++------- .../vehicles/VehiclesListMapPopup/index.tsx | 17 ++++------- .../vehicles/VehiclesListToolbar/index.tsx | 22 +++++++++++---- 5 files changed, 55 insertions(+), 44 deletions(-) diff --git a/frontend/components/vehicles/VehiclesList/index.tsx b/frontend/components/vehicles/VehiclesList/index.tsx index a61fc5da..3d9fbd34 100644 --- a/frontend/components/vehicles/VehiclesList/index.tsx +++ b/frontend/components/vehicles/VehiclesList/index.tsx @@ -5,26 +5,33 @@ import { Section } from '@/components/layout/Section'; import { Surface } from '@/components/layout/Surface'; import VehiclesListGroup from '@/components/vehicles/VehiclesListGroup'; -import VehiclesListMap from '@/components/vehicles/VehiclesListMap'; import VehiclesListToolbar from '@/components/vehicles/VehiclesListToolbar'; +import { useTranslations } from 'next-intl'; +import VehiclesListMap from '../VehiclesListMap'; import styles from './styles.module.css'; /* * */ export default function Component() { + const t = useTranslations('vehicles.VehiclesListToolbar'); + return ( <> - + +
+ -
-
- +
+ +
+
-
+ +
); diff --git a/frontend/components/vehicles/VehiclesList/styles.module.css b/frontend/components/vehicles/VehiclesList/styles.module.css index e3af5a0b..41e1d28c 100644 --- a/frontend/components/vehicles/VehiclesList/styles.module.css +++ b/frontend/components/vehicles/VehiclesList/styles.module.css @@ -1,31 +1,37 @@ -/* * */ /* CONTAINER */ -.contentWrapper { +.container { display: grid; grid-template: - "a b" minmax(500px, auto) / 1fr; + "a b" minmax(100vh, auto) / 1fr 1fr; align-items: flex-start; justify-content: flex-start; width: 100%; } @media (width < 1000px) { - .contentWrapper { + .container { grid-template: - "b" 300px [b_] + "b" 50vh [b_] "a" auto [a_] / 1fr; - gap: var(--size-spacing-30); } } /* * */ -/* GROUPS WRAPPER */ +/* MAP WRAPPER */ -.groupsWrapper { - grid-area: a; +.sidebarWrapper { + position: sticky; + top: var(--size-height-header); + grid-area: b; height: 100%; - overflow: hidden; + max-height: calc(100vh - var(--size-height-header));; + border-left: 1px solid var(--color-system-border-100); } -/* * */ \ No newline at end of file +@media (width < 1000px) { + .sidebarWrapper { + position: static; + border: none; + } +} \ No newline at end of file diff --git a/frontend/components/vehicles/VehiclesListMap/index.tsx b/frontend/components/vehicles/VehiclesListMap/index.tsx index a404de0a..3aea570d 100644 --- a/frontend/components/vehicles/VehiclesListMap/index.tsx +++ b/frontend/components/vehicles/VehiclesListMap/index.tsx @@ -7,24 +7,20 @@ import { MapViewStyleVehiclesInteractiveLayerId, MapViewStyleVehiclesPrimaryLayerId, } from '@/components/map/MapViewStyleVehicles'; -import { useLinesContext } from '@/contexts/Lines.context'; import { transformStopDataIntoGeoJsonFeature, useStopsContext } from '@/contexts/Stops.context'; import { useVehiclesContext } from '@/contexts/Vehicles.context'; import { useVehiclesListContext } from '@/contexts/VehiclesList.context'; import { getBaseGeoJsonFeatureCollection } from '@/utils/map.utils'; import { Routes } from '@/utils/routes'; -import { Pattern, Shape } from '@carrismetropolitana/api-types/network'; +import { Pattern } from '@carrismetropolitana/api-types/network'; import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson'; import { DateTime } from 'luxon'; import { useMemo, useState } from 'react'; -import { VehicleListMapPopup } from '../VehiclesListMapPopup'; - export default function Component() { // A. Setup variables const vehiclesListContext = useVehiclesListContext(); const vehiclesContext = useVehiclesContext(); - const LinesContext = useLinesContext(); const stopsContext = useStopsContext(); const [pattern, setPattern] = useState(undefined); const [activePathShapeGeoJson, setActivePathShapeGeoJson] = useState | FeatureCollection | undefined>(undefined); @@ -33,8 +29,6 @@ export default function Component() { // B. Fetch Data - const lineData = LinesContext.actions.getLineDataById(selectedVehicle?.line_id || ''); - const findTodaysDate = () => DateTime.now().toFormat('yyyyLLdd'); const fetchPattern = useMemo(async () => { @@ -102,6 +96,7 @@ export default function Component() { if (event.features.length === 0) { setActivePathShapeGeoJson(undefined); setPattern(undefined); + vehiclesListContext.actions.updateSelectedVehicle(''); } if (event.features.length !== 0 && event.features[0].source === 'default-source-vehicles') { vehiclesListContext.actions.updateSelectedVehicle(event.features[0].properties.id); @@ -121,9 +116,7 @@ export default function Component() { shapeData={activePathShapeGeoJson} waypointsData={activePathWaypointsGeoJson} /> - {selectedVehicle && ( - - )} + ); } diff --git a/frontend/components/vehicles/VehiclesListMapPopup/index.tsx b/frontend/components/vehicles/VehiclesListMapPopup/index.tsx index 8466a366..73a98812 100644 --- a/frontend/components/vehicles/VehiclesListMapPopup/index.tsx +++ b/frontend/components/vehicles/VehiclesListMapPopup/index.tsx @@ -1,3 +1,4 @@ +import { Section } from '@/components/layout/Section'; import { VehicleListMapPopupBadge } from '@/components/vehicles/VehicleListMapPopupBadge'; import { Table } from '@mantine/core'; import { @@ -28,16 +29,8 @@ export function VehicleListMapPopup({ lineData, selectedVehicle }) { // B. Render Components return ( - <> - +
+
@@ -74,7 +67,7 @@ export function VehicleListMapPopup({ lineData, selectedVehicle }) {
- - +
+
); } diff --git a/frontend/components/vehicles/VehiclesListToolbar/index.tsx b/frontend/components/vehicles/VehiclesListToolbar/index.tsx index 5dd39deb..97aa36fa 100644 --- a/frontend/components/vehicles/VehiclesListToolbar/index.tsx +++ b/frontend/components/vehicles/VehiclesListToolbar/index.tsx @@ -4,8 +4,9 @@ import { FoundItemsCounter } from '@/components/common/FoundItemsCounter'; import { Grid } from '@/components/layout/Grid'; +import { NoDataLabel } from '@/components/layout/NoDataLabel'; import { Section } from '@/components/layout/Section'; -import { Surface } from '@/components/layout/Surface'; +import { useLinesContext } from '@/contexts/Lines.context'; import { useVehiclesContext } from '@/contexts/Vehicles.context'; import { useVehiclesListContext } from '@/contexts/VehiclesList.context'; import { MultiSelect, Select, TextInput } from '@mantine/core'; @@ -13,6 +14,8 @@ import { IconArrowLoopRight, IconBike, IconGasStation, IconTriangle, IconUser, I import { useTranslations } from 'next-intl'; import { useEffect, useRef, useState } from 'react'; +import { VehicleListMapPopup } from '../VehiclesListMapPopup'; + /* * */ export default function Component() { @@ -23,6 +26,7 @@ export default function Component() { const t = useTranslations('vehicles.VehiclesListToolbar'); const vehiclesContext = useVehiclesContext(); + const LinesContext = useLinesContext(); const vehiclesListContext = useVehiclesListContext(); const typingTimeoutRef = useRef>(null); const [textInput, setTextInput] = useState(null); @@ -31,6 +35,9 @@ export default function Component() { const [agencyId, setAgencyId] = useState([]); const [make_model, setMakeModel] = useState([]); const [propulsion, setPropulsion] = useState([]); + const selectedVehicleFromList = vehiclesListContext.data.selected; + const selectedVehicle = selectedVehicleFromList && vehiclesContext.data.vehicles.find(vehicle => vehicle.id === selectedVehicleFromList.id); + const lineData = LinesContext.actions.getLineDataById(selectedVehicle?.line_id || ''); // // B. Transform data @@ -85,10 +92,9 @@ export default function Component() { // // D. Render components - return ( - -
+ <> +
} onChange={handleTextInputChange} placeholder={t('filter_by.search')} type="search" value={textInput || ''} /> } @@ -161,11 +161,9 @@ export default function Component() {
-
+
{!selectedVehicle && ()} - {selectedVehicle && ( - - )} + {selectedVehicle && ()}
); From d6d0ae62dd3555be048d1a7126792baf6a86a599 Mon Sep 17 00:00:00 2001 From: Skasix00 Date: Wed, 5 Feb 2025 16:29:02 +0000 Subject: [PATCH 13/22] Tweaked some names and fixed some bugs --- .../index.tsx | 2 +- .../styles.module.css | 0 .../vehicles/VehiclesList/styles.module.css | 2 +- .../index.tsx | 6 +- .../styles.module.css | 0 .../vehicles/VehiclesListToolbar/index.tsx | 166 ++++++++---------- frontend/contexts/VehiclesList.context.tsx | 57 +++--- frontend/i18n/translations/en.json | 1 + frontend/i18n/translations/pt.json | 1 + 9 files changed, 110 insertions(+), 125 deletions(-) rename frontend/components/vehicles/{VehicleListMapPopupBadge => VehicleListMapBadge}/index.tsx (91%) rename frontend/components/vehicles/{VehicleListMapPopupBadge => VehicleListMapBadge}/styles.module.css (100%) rename frontend/components/vehicles/{VehiclesListMapPopup => VehiclesListMapDetails}/index.tsx (92%) rename frontend/components/vehicles/{VehiclesListMapPopup => VehiclesListMapDetails}/styles.module.css (100%) diff --git a/frontend/components/vehicles/VehicleListMapPopupBadge/index.tsx b/frontend/components/vehicles/VehicleListMapBadge/index.tsx similarity index 91% rename from frontend/components/vehicles/VehicleListMapPopupBadge/index.tsx rename to frontend/components/vehicles/VehicleListMapBadge/index.tsx index 32849948..10d1d007 100644 --- a/frontend/components/vehicles/VehicleListMapPopupBadge/index.tsx +++ b/frontend/components/vehicles/VehicleListMapBadge/index.tsx @@ -23,7 +23,7 @@ const cx = classNames.bind(styles); /* * */ -export function VehicleListMapPopupBadge({ lineData }: Props) { +export function VehicleListMapBadge({ lineData }: Props) { // A. Render components return ( <> diff --git a/frontend/components/vehicles/VehicleListMapPopupBadge/styles.module.css b/frontend/components/vehicles/VehicleListMapBadge/styles.module.css similarity index 100% rename from frontend/components/vehicles/VehicleListMapPopupBadge/styles.module.css rename to frontend/components/vehicles/VehicleListMapBadge/styles.module.css diff --git a/frontend/components/vehicles/VehiclesList/styles.module.css b/frontend/components/vehicles/VehiclesList/styles.module.css index 733f2f15..b59e1cf8 100644 --- a/frontend/components/vehicles/VehiclesList/styles.module.css +++ b/frontend/components/vehicles/VehiclesList/styles.module.css @@ -3,7 +3,7 @@ .container { display: grid; grid-template: - "a b" minmax(100vh, auto) / 1fr 1fr; + "a b" minmax(65vh, auto) / 1fr 1fr; align-items: flex-start; justify-content: flex-start; width: 100%; diff --git a/frontend/components/vehicles/VehiclesListMapPopup/index.tsx b/frontend/components/vehicles/VehiclesListMapDetails/index.tsx similarity index 92% rename from frontend/components/vehicles/VehiclesListMapPopup/index.tsx rename to frontend/components/vehicles/VehiclesListMapDetails/index.tsx index 79e31462..75e6862b 100644 --- a/frontend/components/vehicles/VehiclesListMapPopup/index.tsx +++ b/frontend/components/vehicles/VehiclesListMapDetails/index.tsx @@ -1,5 +1,5 @@ import { Section } from '@/components/layout/Section'; -import { VehicleListMapPopupBadge } from '@/components/vehicles/VehicleListMapPopupBadge'; +import { VehicleListMapBadge } from '@/components/vehicles/VehicleListMapBadge'; import { Table } from '@mantine/core'; import { IconBike, @@ -10,7 +10,7 @@ import { import styles from './styles.module.css'; -export function VehicleListMapPopup({ lineData, selectedVehicle }) { +export function VehicleListMapDetails({ lineData, selectedVehicle }) { // A. Setup Variables const id = selectedVehicle.id; @@ -30,7 +30,7 @@ export function VehicleListMapPopup({ lineData, selectedVehicle }) { return (
- +
{selectedVehicle.bikes_allowed ? : } diff --git a/frontend/components/vehicles/VehiclesListMapPopup/styles.module.css b/frontend/components/vehicles/VehiclesListMapDetails/styles.module.css similarity index 100% rename from frontend/components/vehicles/VehiclesListMapPopup/styles.module.css rename to frontend/components/vehicles/VehiclesListMapDetails/styles.module.css diff --git a/frontend/components/vehicles/VehiclesListToolbar/index.tsx b/frontend/components/vehicles/VehiclesListToolbar/index.tsx index 87605df3..8fcb8951 100644 --- a/frontend/components/vehicles/VehiclesListToolbar/index.tsx +++ b/frontend/components/vehicles/VehiclesListToolbar/index.tsx @@ -2,6 +2,7 @@ /* * */ +import { ExpandToggle } from '@/components/common/ExpandToggle'; import { FoundItemsCounter } from '@/components/common/FoundItemsCounter'; import { Grid } from '@/components/layout/Grid'; import { NoDataLabel } from '@/components/layout/NoDataLabel'; @@ -12,158 +13,133 @@ import { useVehiclesListContext } from '@/contexts/VehiclesList.context'; import { MultiSelect, Select, TextInput } from '@mantine/core'; import { IconArrowLoopRight, IconBike, IconGasStation, IconTriangle, IconUser, IconWheelchair } from '@tabler/icons-react'; import { useTranslations } from 'next-intl'; -import { useEffect, useRef, useState } from 'react'; -import { VehicleListMapPopup } from '../VehiclesListMapPopup'; +import { VehicleListMapDetails } from '../VehiclesListMapDetails'; +import styles from './styles.module.css'; /* * */ export default function Component() { - // - - // // A. Setup variables const t = useTranslations('vehicles.VehiclesListToolbar'); + const vehiclesContext = useVehiclesContext(); const LinesContext = useLinesContext(); const vehiclesListContext = useVehiclesListContext(); - const typingTimeoutRef = useRef>(null); - const [textInput, setTextInput] = useState(null); - const [reducedMobility, setReducedMobility] = useState(''); - const [bikesAllowed, setBikesAllowed] = useState(''); - const [agencyId, setAgencyId] = useState([]); - const [make_model, setMakeModel] = useState([]); - const [propulsion, setPropulsion] = useState([]); + const selectedVehicleFromList = vehiclesListContext.data.selected; const selectedVehicle = selectedVehicleFromList && vehiclesContext.data.vehicles.find(vehicle => vehicle.id === selectedVehicleFromList.id); - const lineData = LinesContext.actions.getLineDataById(selectedVehicle?.line_id || ''); - - // - // B. Transform data - useEffect(() => { - return () => { - if (typingTimeoutRef.current) { - clearTimeout(typingTimeoutRef.current); - } - }; - }, []); + const lineData = LinesContext.actions.getLineDataById(selectedVehicle?.line_id || ''); // - // C. Handle Actions + // B. Handle Actions const handleTextInputChange = ({ currentTarget }) => { - setTextInput(currentTarget.value); - if (!vehiclesListContext.flags.is_loading && vehiclesListContext.data.raw) { - if (typingTimeoutRef.current) { - clearTimeout(typingTimeoutRef.current); - } - typingTimeoutRef.current = setTimeout(() => { - vehiclesListContext.actions.updateFilterBySearch(currentTarget.value); - }, 500); - } + vehiclesListContext.actions.updateFilterBySearch(currentTarget.value); }; const handleBikesAllowedInputChange = (option: string) => { - setBikesAllowed(option); vehiclesListContext.actions.updateFilterByIsBikeAllowed(option); }; const handleReducedMobilityChange = (option: string) => { - setReducedMobility(option); vehiclesListContext.actions.updateFilterByWheelchair(option); }; const handleAgencyIdChange = (option: string[]) => { - setAgencyId(option); vehiclesListContext.actions.updateFilterByAgency(option); }; const handleMakeAndModelChange = (option: string[]) => { - setMakeModel(option); vehiclesListContext.actions.updateFilterByMakeAndModel(option); }; const handlePropulsionChange = (option: string[]) => { - setPropulsion(option); vehiclesListContext.actions.updateFilterByPropulsion(option); }; // - // D. Render components + // C. Render components return ( <>
- - } onChange={handleTextInputChange} placeholder={t('filter_by.search')} type="search" value={textInput || ''} /> - } - onChange={handleReducedMobilityChange} - placeholder={t('filter_by.wheel_chair')} - radius="sm" - value={vehiclesListContext.filters.by_isWheelchairAcessible} - data={[{ label: 'Não', value: 'false' }, - { label: 'Sim', value: 'true' }]} - clearable - searchable - /> - ({ label: a.name, value: a.agency_id.toString() })) || []} - leftSection={} - onChange={handleAgencyIdChange} - placeholder={t('filter_by.operator')} - radius="sm" - value={agencyId} - searchable - /> - } - onChange={handleMakeAndModelChange} - placeholder={t('filter_by.make_model')} - radius="sm" - value={make_model || ''} - data={ - vehiclesListContext.data?.makes_and_models?.map(make => ({ - group: make.name, - items: make.models.map(model => ({ - label: model.name, - value: ` ${make.name}-${model.name}`, - })), - label: make.name, - value: make.name, - })) || [] - } - clearable - searchable - /> + + } onChange={handleTextInputChange} placeholder={t('filter_by.search')} type="search" value={vehiclesListContext.filters.by_search} /> ({ label: p.name, value: p.name })) || []} leftSection={} onChange={handlePropulsionChange} placeholder={t('filter_by.propulsion')} radius="sm" - value={propulsion} + value={vehiclesListContext.filters.by_propulsion?.split(' ') || []} clearable searchable /> + + + + } + onChange={handleBikesAllowedInputChange} + placeholder={t('filter_by.bicycle')} + radius="sm" + value={vehiclesListContext.filters.by_isBicicleAllowed} + data={[{ label: 'Não', value: 'false' }, + { label: 'Sim', value: 'true' }]} + clearable + searchable + /> + ({ label: a.name, value: a.agency_id.toString() })) || []} + leftSection={} + onChange={handleAgencyIdChange} + placeholder={t('filter_by.operator')} + radius="sm" + value={vehiclesListContext.filters.by_agency?.split(' ') || []} + clearable + searchable + /> + } + onChange={handleMakeAndModelChange} + placeholder={t('filter_by.make_model')} + radius="sm" + value={vehiclesListContext.filters.by_makeAndModel?.split(',') || []} + data={ + vehiclesListContext.data?.makes_and_models?.map(make => ({ + group: make.name, + items: make.models.map(model => ({ + label: model.name, + value: `${make.name}-${model.name}`, + })), + })) || [] + } + clearable + searchable + /> + + + +
- {!selectedVehicle && ()} - {selectedVehicle && ()} + {!selectedVehicle && ()} + {selectedVehicle && ()}
); diff --git a/frontend/contexts/VehiclesList.context.tsx b/frontend/contexts/VehiclesList.context.tsx index 87a92309..320b4745 100644 --- a/frontend/contexts/VehiclesList.context.tsx +++ b/frontend/contexts/VehiclesList.context.tsx @@ -3,7 +3,6 @@ import { Routes } from '@/utils/routes'; import { Vehicle } from '@carrismetropolitana/api-types/vehicles'; import { useQueryState } from 'nuqs'; -import { parseAsArrayOf, parseAsString } from 'nuqs'; import { createContext, useContext, useEffect, useState } from 'react'; import useSWR from 'swr'; @@ -31,7 +30,7 @@ interface VehiclesListContextState { by_isWheelchairAcessible: null | string by_makeAndModel: null | string by_propulsion: null | string - by_search: null | string + by_search: string selected_vehicle: null | string } flags: { @@ -53,7 +52,6 @@ export const VehiclesListContextProvider = ({ children }) => { // // A. Setup variables const [dataFilteredState, setDataFilteredState] = useState([]); - const [dataFilteredVehicleState, setDataFilteredVehicleState] = useState([]); const [dataSelectedState, setDataSelectedState] = useState(null); const [allAgencys, setAllAgencys] = useState<{ agency_id: number, name: string }[]>([]); const [allMakesAndModels, setAllMakesAndModels] = useState< @@ -61,12 +59,12 @@ export const VehiclesListContextProvider = ({ children }) => { >([]); const [allPropulsions, setAllPropulsions] = useState([]); - const [filterByWheelchairAccesibleState, setWheelchairAccesibleState] = useQueryState('isWheelchair'); - const [filterByAgencyState, setFilterByAgencyState] = useQueryState('agency'); - const [filterByBicycleAllowedState, setByBicycleAllowedState] = useQueryState('isBikeAllowed'); - const [filterByMakeAndModelState, setFilterByMakeAndModelState] = useQueryState('makeModel'); - const [filterBySearchState, setFilterBySearchState] = useQueryState('search'); - const [filterByPropulsionState, setFilterByPropulsion] = useQueryState('propulsion'); + const [filterByWheelchairAccesibleState, setWheelchairAccesibleState] = useQueryState('isWheelchair', { clearOnDefault: true }); + const [filterByAgencyState, setFilterByAgencyState] = useQueryState('agency', { clearOnDefault: true }); + const [filterByBicycleAllowedState, setByBicycleAllowedState] = useQueryState('isBikeAllowed', { clearOnDefault: true }); + const [filterByMakeAndModelState, setFilterByMakeAndModelState] = useQueryState('makeModel', { clearOnDefault: true }); + const [filterBySearchState, setFilterBySearchState] = useQueryState('search', { clearOnDefault: true, defaultValue: '' }); + const [filterByPropulsionState, setFilterByPropulsion] = useQueryState('propulsion', { clearOnDefault: true }); // // B. Fetch data @@ -89,28 +87,28 @@ export const VehiclesListContextProvider = ({ children }) => { ); } - if (filterByPropulsionState && filterByPropulsionState.trim() !== '') { + if (filterByPropulsionState) { const propulsionValues = filterByPropulsionState.split(' ').filter(Boolean); filterResult = filterResult.filter(item => item.propulsion && propulsionValues.includes(item.propulsion), ); } - if (filterByAgencyState && filterByAgencyState.trim() !== '') { + if (filterByAgencyState) { const agencyValues = filterByAgencyState.split(' ').filter(Boolean); filterResult = filterResult.filter(item => agencyValues.includes(item.agency_id.toString()), ); } - if (filterBySearchState && filterBySearchState.trim() !== '') { + if (filterBySearchState) { filterResult = filterResult.filter(item => item.license_plate?.toLowerCase().includes(filterBySearchState.toLowerCase()), ); } if (filterByMakeAndModelState && filterByMakeAndModelState.trim() !== '') { - const makeModelValues = filterByMakeAndModelState.split(' ').filter(Boolean); + const makeModelValues = filterByMakeAndModelState.split(',').filter(Boolean); filterResult = filterResult.filter((item) => { return makeModelValues.some((val) => { const [makeFilter, modelFilter] = val.split('-').map(s => s.trim().toLowerCase()); @@ -226,17 +224,17 @@ export const VehiclesListContextProvider = ({ children }) => { // // D. Handle actions - const updateFilterBySearch = (search: string | string[]) => { - if (Array.isArray(search)) { - setFilterBySearchState(search.join(' ')); - } - else { - setFilterBySearchState(search); - } + const updateFilterBySearch = (search: string) => { + setFilterBySearchState(search); }; const updateFilterByAgency = (agency: string[]) => { - setFilterByAgencyState(agency.join(' ')); + if (agency.length !== 0) { + setFilterByAgencyState(agency.join(' ')); + } + else { + setFilterByAgencyState(null); + } }; const updateFilterByIsBikeAllowed = (isBikeAllowed: string) => { @@ -258,11 +256,21 @@ export const VehiclesListContextProvider = ({ children }) => { }; const updateFilterByMakeAndModel = (makeAndModel: string[]) => { - setFilterByMakeAndModelState(makeAndModel.join(' ')); + if (makeAndModel.length === 0) { + setFilterByMakeAndModelState(null); + } + else { + setFilterByMakeAndModelState(makeAndModel.join(',')); + } }; - const updateFilterByPropulsion = (propulsion: string[]) => { - setFilterByPropulsion(propulsion.join(' ')); + const updateFilterByPropulsion = (propulsionOptions: string[]) => { + if (propulsionOptions.length === 0) { + setFilterByPropulsion(null); + } + else { + setFilterByPropulsion(propulsionOptions.join(' ').trim()); + } }; const updateSelectedVehicle = (vehicleId: string) => { @@ -271,7 +279,6 @@ export const VehiclesListContextProvider = ({ children }) => { if (foundVehicleData) { setDataSelectedState(foundVehicleData[0] || null); - setDataFilteredVehicleState([foundVehicleData[0] || null]); } }; diff --git a/frontend/i18n/translations/en.json b/frontend/i18n/translations/en.json index 78ac0fa4..bee253fc 100644 --- a/frontend/i18n/translations/en.json +++ b/frontend/i18n/translations/en.json @@ -1525,6 +1525,7 @@ }, "found_items_counter": "{count, plural, =0 {No vehicle found.} one {Found # vehicle} other {Found # vehicles}}", "heading": "Veículos", + "select_vehicle": "Select a vehicle", "subheading": "The pass navegante municipal Familia allows that each family pays at most the value of 2 municipal navegante pass indenpendently of the number of elemnts of the family. It is valid in all the companies of the public means of transport, in all of the 18 municipalities of the Lisbon metropolitan area." } } diff --git a/frontend/i18n/translations/pt.json b/frontend/i18n/translations/pt.json index 0989d733..c24abaa1 100644 --- a/frontend/i18n/translations/pt.json +++ b/frontend/i18n/translations/pt.json @@ -1526,6 +1526,7 @@ }, "found_items_counter": "{count, plural, =0 {Nenhum Veículo encontrado.} one {Encontrado # veículo} other {Encontrados # veículos}}", "heading": "Veículos", + "select_vehicle": "Selecione um veículo", "subheading": "O passe navegante municipal Família permite que cada agregado familiar pague no máximo o valor de 2 passes navegante municipal, independentemente do número de elementos do agregado familiar. É válido em todas as empresas de serviço público de transporte regular de passageiros, em todos os 18 municípios da Área Metropolitana de Lisboa." } } From 1e45afb70f300a6e46dbebd0d2c0da582653fd14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20de=20Vasconcelos?= Date: Thu, 6 Feb 2025 23:32:44 +0000 Subject: [PATCH 14/22] Removed unused JSON formatter settings from VSCode configuration --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b1cde8b4..62ed94ce 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -53,9 +53,6 @@ "**/components/**/*.css": "${dirname} (${extname})", "**/components/**/*.js": "${dirname} (${extname})", "**/components/**/*.tsx": "${dirname} (${extname})" - }, - "[json]": { - "editor.defaultFormatter": "dbaeumer.vscode-eslint" } } From 644af1f6c635eaa5249a594b199c54a322157c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20de=20Vasconcelos?= Date: Thu, 6 Feb 2025 23:41:24 +0000 Subject: [PATCH 15/22] Use named exports --- frontend/app/(views)/(website)/vehicles/page.tsx | 6 ++---- frontend/components/vehicles/VehiclesList/index.tsx | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/app/(views)/(website)/vehicles/page.tsx b/frontend/app/(views)/(website)/vehicles/page.tsx index d9819f13..640a2633 100644 --- a/frontend/app/(views)/(website)/vehicles/page.tsx +++ b/frontend/app/(views)/(website)/vehicles/page.tsx @@ -1,11 +1,9 @@ /* * */ -import VehiclesList from '@/components/vehicles/VehiclesList'; +import { VehiclesList } from '@/components/vehicles/VehiclesList'; /* * */ export default function Page() { - return ( - - ); + return ; } diff --git a/frontend/components/vehicles/VehiclesList/index.tsx b/frontend/components/vehicles/VehiclesList/index.tsx index 993fe1ed..40d9f0ed 100644 --- a/frontend/components/vehicles/VehiclesList/index.tsx +++ b/frontend/components/vehicles/VehiclesList/index.tsx @@ -13,7 +13,7 @@ import styles from './styles.module.css'; /* * */ -export default function Component() { +export function VehiclesList() { const t = useTranslations('vehicles.VehiclesListToolbar'); return ( From ff284c2c0a43f719f4a0e230ea7622c3bce1f700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20de=20Vasconcelos?= Date: Thu, 6 Feb 2025 23:43:38 +0000 Subject: [PATCH 16/22] Remove unused VehiclesListGroup --- .../components/vehicles/VehiclesList/index.tsx | 15 ++++++++++++--- .../vehicles/VehiclesListGroup/index.tsx | 11 ----------- 2 files changed, 12 insertions(+), 14 deletions(-) delete mode 100644 frontend/components/vehicles/VehiclesListGroup/index.tsx diff --git a/frontend/components/vehicles/VehiclesList/index.tsx b/frontend/components/vehicles/VehiclesList/index.tsx index 40d9f0ed..949d1630 100644 --- a/frontend/components/vehicles/VehiclesList/index.tsx +++ b/frontend/components/vehicles/VehiclesList/index.tsx @@ -4,18 +4,25 @@ import { Section } from '@/components/layout/Section'; import { Surface } from '@/components/layout/Surface'; -import VehiclesListGroup from '@/components/vehicles/VehiclesListGroup'; +import VehiclesListMap from '@/components/vehicles/VehiclesListMap'; import VehiclesListToolbar from '@/components/vehicles/VehiclesListToolbar'; import { useTranslations } from 'next-intl'; -import VehiclesListMap from '../VehiclesListMap'; import styles from './styles.module.css'; /* * */ export function VehiclesList() { + // + + // + // A. Setup variables + const t = useTranslations('vehicles.VehiclesListToolbar'); + // + // B. Render components + return ( <> @@ -23,7 +30,7 @@ export function VehiclesList() {
- +

Informação

@@ -37,4 +44,6 @@ export function VehiclesList() { ); + + // } diff --git a/frontend/components/vehicles/VehiclesListGroup/index.tsx b/frontend/components/vehicles/VehiclesListGroup/index.tsx deleted file mode 100644 index 1e2d6486..00000000 --- a/frontend/components/vehicles/VehiclesListGroup/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -'use client'; - -export default function Component() { - // A. Render components - - return ( -

Informação

- ); - - // -} From 7a76f24a6eb92b451ec5c5241677e7630a070864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20de=20Vasconcelos?= Date: Thu, 6 Feb 2025 23:46:18 +0000 Subject: [PATCH 17/22] Update translation keys for VehiclesList component --- frontend/components/vehicles/VehiclesList/index.tsx | 3 +-- frontend/i18n/translations/en.json | 8 +++++--- frontend/i18n/translations/pt.json | 8 +++++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/frontend/components/vehicles/VehiclesList/index.tsx b/frontend/components/vehicles/VehiclesList/index.tsx index 949d1630..cb1c81d5 100644 --- a/frontend/components/vehicles/VehiclesList/index.tsx +++ b/frontend/components/vehicles/VehiclesList/index.tsx @@ -18,7 +18,7 @@ export function VehiclesList() { // // A. Setup variables - const t = useTranslations('vehicles.VehiclesListToolbar'); + const t = useTranslations('vehicles.VehiclesList'); // // B. Render components @@ -40,7 +40,6 @@ export function VehiclesList() {
- ); diff --git a/frontend/i18n/translations/en.json b/frontend/i18n/translations/en.json index bee253fc..0dc10ccc 100644 --- a/frontend/i18n/translations/en.json +++ b/frontend/i18n/translations/en.json @@ -1514,6 +1514,10 @@ "number": "{value, number}" }, "vehicles": { + "VehiclesList": { + "heading": "Veículos", + "subheading": "The pass navegante municipal Familia allows that each family pays at most the value of 2 municipal navegante pass indenpendently of the number of elemnts of the family. It is valid in all the companies of the public means of transport, in all of the 18 municipalities of the Lisbon metropolitan area." + }, "VehiclesListToolbar": { "filter_by": { "bicycle": "Permite Bicicletas", @@ -1524,9 +1528,7 @@ "wheel_chair": "Reduced Mobility" }, "found_items_counter": "{count, plural, =0 {No vehicle found.} one {Found # vehicle} other {Found # vehicles}}", - "heading": "Veículos", - "select_vehicle": "Select a vehicle", - "subheading": "The pass navegante municipal Familia allows that each family pays at most the value of 2 municipal navegante pass indenpendently of the number of elemnts of the family. It is valid in all the companies of the public means of transport, in all of the 18 municipalities of the Lisbon metropolitan area." + "select_vehicle": "Select a vehicle" } } } diff --git a/frontend/i18n/translations/pt.json b/frontend/i18n/translations/pt.json index c24abaa1..72bb61ed 100644 --- a/frontend/i18n/translations/pt.json +++ b/frontend/i18n/translations/pt.json @@ -1514,6 +1514,10 @@ "number": "{value, number}" }, "vehicles": { + "VehiclesList": { + "heading": "Veículos", + "subheading": "O passe navegante municipal Família permite que cada agregado familiar pague no máximo o valor de 2 passes navegante municipal, independentemente do número de elementos do agregado familiar. É válido em todas as empresas de serviço público de transporte regular de passageiros, em todos os 18 municípios da Área Metropolitana de Lisboa." + }, "VehiclesListToolbar": { "filter_by": { "bicycle": "Permite Bicicletas", @@ -1525,9 +1529,7 @@ }, "found_items_counter": "{count, plural, =0 {Nenhum Veículo encontrado.} one {Encontrado # veículo} other {Encontrados # veículos}}", - "heading": "Veículos", - "select_vehicle": "Selecione um veículo", - "subheading": "O passe navegante municipal Família permite que cada agregado familiar pague no máximo o valor de 2 passes navegante municipal, independentemente do número de elementos do agregado familiar. É válido em todas as empresas de serviço público de transporte regular de passageiros, em todos os 18 municípios da Área Metropolitana de Lisboa." + "select_vehicle": "Selecione um veículo" } } } From e72ae1f50690448056295bf0446d524f809ea71c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20de=20Vasconcelos?= Date: Thu, 6 Feb 2025 23:59:45 +0000 Subject: [PATCH 18/22] Whitespace --- frontend/contexts/NewsList.context.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/contexts/NewsList.context.tsx b/frontend/contexts/NewsList.context.tsx index 17526cea..e45c1a4d 100644 --- a/frontend/contexts/NewsList.context.tsx +++ b/frontend/contexts/NewsList.context.tsx @@ -66,7 +66,7 @@ export const NewsListContextProvider = ({ children }) => { // // Filter by news date - + if (filterBySearch) { filterResult = filterResult.filter((newsItem) => { const titleLowerCase = newsItem.title.toLowerCase(); From 96ff24a3fe0bdb191e204d0d0b1d036c6222f4be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20de=20Vasconcelos?= Date: Fri, 7 Feb 2025 00:04:13 +0000 Subject: [PATCH 19/22] Include previous commit changes (before merge with production) --- frontend/themes/_default/default.theme.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frontend/themes/_default/default.theme.js b/frontend/themes/_default/default.theme.js index 31f68cd8..3b1dcd79 100644 --- a/frontend/themes/_default/default.theme.js +++ b/frontend/themes/_default/default.theme.js @@ -19,12 +19,14 @@ import '@/themes/_default/styles/wordpress.css'; import AccordionOverride from '@/themes/_default/overrides/Accordion.module.css'; import ButtonOverride from '@/themes/_default/overrides/Button.module.css'; import DatePickerInputOverride from '@/themes/_default/overrides/DatePickerInput.module.css'; +import MultiSelectOverride from '@/themes/_default/overrides/MultiSelect.module.css'; import SegmentedControlOverride from '@/themes/_default/overrides/SegmentedControl.module.css'; import SelectOverride from '@/themes/_default/overrides/Select.module.css'; import SkeletonOverride from '@/themes/_default/overrides/Skeleton.module.css'; import TextInputOverride from '@/themes/_default/overrides/TextInput.module.css'; import combineClasses from '@/utils/combineClasses'; import { Accordion, Button, createTheme, MultiSelect, SegmentedControl, Select, Skeleton, TextInput } from '@mantine/core'; +import { DatePickerInput } from '@mantine/dates'; import { IconCaretLeftFilled } from '@tabler/icons-react'; /* * */ @@ -87,6 +89,18 @@ export default createTheme({ }, }), + MultiSelect: MultiSelect.extend({ + classNames: () => { + let defaultClasses = { + dropdown: MultiSelectOverride.dropdown, + input: MultiSelectOverride.input, + option: MultiSelectOverride.option, + section: MultiSelectOverride.section, + wrapper: MultiSelectOverride.wrapper, + }; + return defaultClasses; + } }), + SegmentedControl: SegmentedControl.extend({ classNames: (_, props) => { let defaultClasses = { @@ -125,6 +139,7 @@ export default createTheme({ return defaultClasses; }, }), + TextInput: TextInput.extend({ classNames: (_, props) => { let defaultClasses = { From 1c12a3979ff2b6ed59911ef14104cb6a740f7abf Mon Sep 17 00:00:00 2001 From: Bruno Castelo Date: Fri, 7 Feb 2025 15:09:24 +0000 Subject: [PATCH 20/22] Added vehicle list box and starting code refactoring --- .../vehicles/VehicleListInfoBlock/index.tsx | 19 +++++++++++++++++++ .../vehicles/VehiclesList/index.tsx | 3 ++- frontend/i18n/translations/en.json | 3 +++ frontend/i18n/translations/pt.json | 3 +++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 frontend/components/vehicles/VehicleListInfoBlock/index.tsx diff --git a/frontend/components/vehicles/VehicleListInfoBlock/index.tsx b/frontend/components/vehicles/VehicleListInfoBlock/index.tsx new file mode 100644 index 00000000..b48bb544 --- /dev/null +++ b/frontend/components/vehicles/VehicleListInfoBlock/index.tsx @@ -0,0 +1,19 @@ +/* * */ + +import { useTranslations } from 'next-intl'; + +/* * */ +export function VehiclesListInfoBlock() { + // + // A. Setup variables + + const t = useTranslations('vehicles.VehiclesListInfoBlock'); + + // + // B. Render components + return ( + <> +

{t('heading')}

+ + ); +} diff --git a/frontend/components/vehicles/VehiclesList/index.tsx b/frontend/components/vehicles/VehiclesList/index.tsx index cb1c81d5..428439de 100644 --- a/frontend/components/vehicles/VehiclesList/index.tsx +++ b/frontend/components/vehicles/VehiclesList/index.tsx @@ -8,6 +8,7 @@ import VehiclesListMap from '@/components/vehicles/VehiclesListMap'; import VehiclesListToolbar from '@/components/vehicles/VehiclesListToolbar'; import { useTranslations } from 'next-intl'; +import { VehiclesListInfoBlock } from '../VehicleListInfoBlock'; import styles from './styles.module.css'; /* * */ @@ -30,7 +31,7 @@ export function VehiclesList() {
-

Informação

+
diff --git a/frontend/i18n/translations/en.json b/frontend/i18n/translations/en.json index 12d3691e..bed1e3d6 100644 --- a/frontend/i18n/translations/en.json +++ b/frontend/i18n/translations/en.json @@ -1540,6 +1540,9 @@ "heading": "Veículos", "subheading": "The pass navegante municipal Familia allows that each family pays at most the value of 2 municipal navegante pass indenpendently of the number of elemnts of the family. It is valid in all the companies of the public means of transport, in all of the 18 municipalities of the Lisbon metropolitan area." }, + "VehiclesListInfoBox": { + "heading": "Vehicle Information" + }, "VehiclesListToolbar": { "filter_by": { "bicycle": "Permite Bicicletas", diff --git a/frontend/i18n/translations/pt.json b/frontend/i18n/translations/pt.json index 0f99f92b..41aac508 100644 --- a/frontend/i18n/translations/pt.json +++ b/frontend/i18n/translations/pt.json @@ -1540,6 +1540,9 @@ "heading": "Veículos", "subheading": "O passe navegante municipal Família permite que cada agregado familiar pague no máximo o valor de 2 passes navegante municipal, independentemente do número de elementos do agregado familiar. É válido em todas as empresas de serviço público de transporte regular de passageiros, em todos os 18 municípios da Área Metropolitana de Lisboa." }, + "VehiclesListInfoBlock": { + "heading": "Informação do Veículo" + }, "VehiclesListToolbar": { "filter_by": { "bicycle": "Permite Bicicletas", From c20a5d7c1f41ecf7fd90e745b7f33efe2dd73c07 Mon Sep 17 00:00:00 2001 From: Bruno Castelo Date: Fri, 7 Feb 2025 17:21:55 +0000 Subject: [PATCH 21/22] Add styles for no data container in VehiclesListToolbar --- .../vehicles/VehiclesListToolbar/styles.module.css | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 frontend/components/vehicles/VehiclesListToolbar/styles.module.css diff --git a/frontend/components/vehicles/VehiclesListToolbar/styles.module.css b/frontend/components/vehicles/VehiclesListToolbar/styles.module.css new file mode 100644 index 00000000..1314f635 --- /dev/null +++ b/frontend/components/vehicles/VehiclesListToolbar/styles.module.css @@ -0,0 +1,9 @@ +/* NO DATA CONTAINER*/ +.noDataContainer{ + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + align-self: center; + height: 100%; +} \ No newline at end of file From 908bd45aa0d00ec31d80b68bde95b9a96dcc1899 Mon Sep 17 00:00:00 2001 From: Bruno Castelo Date: Fri, 7 Feb 2025 17:22:02 +0000 Subject: [PATCH 22/22] Modified VehhicleList Table Data --- .../vehicles/VehicleListMapBadge/index.tsx | 2 +- .../vehicles/VehiclesList/index.tsx | 16 +- .../vehicles/VehiclesList/styles.module.css | 19 ++- .../vehicles/VehiclesListMap/index.tsx | 16 +- .../vehicles/VehiclesListMapDetails/index.tsx | 67 ++++----- .../VehiclesListMapDetails/styles.module.css | 23 ++- .../vehicles/VehiclesListToolbar/index.tsx | 139 +++++++++--------- frontend/contexts/VehiclesList.context.tsx | 55 ++----- 8 files changed, 162 insertions(+), 175 deletions(-) diff --git a/frontend/components/vehicles/VehicleListMapBadge/index.tsx b/frontend/components/vehicles/VehicleListMapBadge/index.tsx index 10d1d007..a2d74a9a 100644 --- a/frontend/components/vehicles/VehicleListMapBadge/index.tsx +++ b/frontend/components/vehicles/VehicleListMapBadge/index.tsx @@ -27,7 +27,7 @@ export function VehicleListMapBadge({ lineData }: Props) { // A. Render components return ( <> -
+
{lineData?.short_name || '• • •'}
diff --git a/frontend/components/vehicles/VehiclesList/index.tsx b/frontend/components/vehicles/VehiclesList/index.tsx index 428439de..972933d0 100644 --- a/frontend/components/vehicles/VehiclesList/index.tsx +++ b/frontend/components/vehicles/VehiclesList/index.tsx @@ -29,19 +29,23 @@ export function VehiclesList() {
- + {/*
-
+
*/} + -
- -
+
+
+
+ +
-
+
+ ); diff --git a/frontend/components/vehicles/VehiclesList/styles.module.css b/frontend/components/vehicles/VehiclesList/styles.module.css index b59e1cf8..e03b0734 100644 --- a/frontend/components/vehicles/VehiclesList/styles.module.css +++ b/frontend/components/vehicles/VehiclesList/styles.module.css @@ -1,9 +1,10 @@ +/* * */ /* CONTAINER */ .container { display: grid; grid-template: - "a b" minmax(65vh, auto) / 1fr 1fr; + "a b" minmax(100vh, auto) / 1fr 1fr; align-items: flex-start; justify-content: flex-start; width: 100%; @@ -12,24 +13,26 @@ @media (width < 1000px) { .container { grid-template: - "a" 50vh [a_] - "b" auto [b_] / 1fr; + "b" 50vh [b_] + "a" auto [a_] / 1fr; } } /* * */ /* MAP WRAPPER */ -.sidebarWrapper { - grid-area: b; +.mapWrapper { + position: sticky; + top: var(--size-height-header); + grid-area: a; height: 100%; - border-left: 1px solid var(--color-system-border-100); + max-height: calc(100vh - var(--size-height-header)); + border-right: 1px solid var(--color-system-border-100); } @media (width < 1000px) { - .sidebarWrapper { + .mapWrapper { position: static; border: none; } - } \ No newline at end of file diff --git a/frontend/components/vehicles/VehiclesListMap/index.tsx b/frontend/components/vehicles/VehiclesListMap/index.tsx index 130a1a3c..fbc65134 100644 --- a/frontend/components/vehicles/VehiclesListMap/index.tsx +++ b/frontend/components/vehicles/VehiclesListMap/index.tsx @@ -22,8 +22,10 @@ export default function Component() { const vehiclesListContext = useVehiclesListContext(); const vehiclesContext = useVehiclesContext(); const stopsContext = useStopsContext(); + const [pattern, setPattern] = useState(undefined); const [activePathShapeGeoJson, setActivePathShapeGeoJson] = useState | FeatureCollection | undefined>(undefined); + const selectedVehicleFromList = vehiclesListContext.data.selected; const selectedVehicle = selectedVehicleFromList && vehiclesContext.data.vehicles.find(vehicle => vehicle.id === selectedVehicleFromList.id); @@ -104,18 +106,10 @@ export default function Component() { } // D. Render component return ( - + - - + ); + // } diff --git a/frontend/components/vehicles/VehiclesListMapDetails/index.tsx b/frontend/components/vehicles/VehiclesListMapDetails/index.tsx index 75e6862b..9d07e1f9 100644 --- a/frontend/components/vehicles/VehiclesListMapDetails/index.tsx +++ b/frontend/components/vehicles/VehiclesListMapDetails/index.tsx @@ -11,56 +11,57 @@ import { import styles from './styles.module.css'; export function VehicleListMapDetails({ lineData, selectedVehicle }) { - // A. Setup Variables + const { + bikes_allowed, + capacity_seated = 'Não Definido', + capacity_standing = 'Não Definido', + capacity_total = 'Não Definido', + current_status = 'Não Definido', + emission_class = 'Não Definido', + id = 'Não Definido', + license_plate = 'Não Definido', + make = 'Não Definido', + model = 'Não Definido', + propulsion = 'Não Definido', + wheelchair_accessible, + } = selectedVehicle; - const id = selectedVehicle.id; - const license_plate = selectedVehicle.license_plate; - const capacity_seated = selectedVehicle.capacity_seated; - const capacity_standing = selectedVehicle.capacity_standing; - const capacity_total = selectedVehicle.capacity_total; - const make = selectedVehicle.make; - const model = selectedVehicle.model; - const propulsion = selectedVehicle.propulsion; - const emission_class = selectedVehicle.emission_class; - const current_status = selectedVehicle.current_status; + const rows = [ + { label: 'ID', value: id }, + { label: 'Lugares Sentados', value: capacity_seated }, + { label: 'Lugares em pé', value: capacity_standing }, + { label: 'Capacidade Total', value: capacity_total }, + { label: 'Marca', value: make }, + { label: 'Modelo', value: model }, + { label: 'Propulsão', value: propulsion }, + { label: 'Emission Class', value: emission_class }, + { label: 'Estado Atual', value: current_status }, + ]; - // - - // B. Render Components return (
- {selectedVehicle.bikes_allowed ? : } - {selectedVehicle.wheelchair_accessible ? : } -

{license_plate ? license_plate : 'Não Definido'}

+ {bikes_allowed ? : } + {wheelchair_accessible ? : } +

{license_plate}

- +
- Campo - Valor + Campo + Valor - {[ - { label: 'ID', value: id ? id : 'Não Definido.' }, - { label: 'Lugares Sentados', value: capacity_seated ? capacity_seated : 'Não Definido' }, - { label: 'Lugares em pé', value: capacity_standing ? capacity_standing : 'Não Definido' }, - { label: 'Capacidade Total', value: capacity_total ? capacity_total : 'Não Definido' }, - { label: 'Marca', value: make ? make : 'Não Definido' }, - { label: 'Modelo', value: model ? model : 'Não Definido.' }, - { label: 'Propulsão', value: propulsion ? propulsion : 'Não Definido' }, - { label: 'Emission Class', value: emission_class ? emission_class : 'Não Definido' }, - { label: 'Estado Atual', value: current_status ? current_status : 'Não Definido' }, - ].map(row => ( + {rows.map(row => ( - {row.label} - {row.value} + {row.label} + {row.value} ))} diff --git a/frontend/components/vehicles/VehiclesListMapDetails/styles.module.css b/frontend/components/vehicles/VehiclesListMapDetails/styles.module.css index 617c2b46..485648ee 100644 --- a/frontend/components/vehicles/VehiclesListMapDetails/styles.module.css +++ b/frontend/components/vehicles/VehiclesListMapDetails/styles.module.css @@ -1,8 +1,10 @@ /* ICON LIST */ .iconList { + display: flex; flex-direction: row !important; justify-content: center; padding: 10px; + gap: 10px; } /* LICENSE PLATE */ @@ -12,17 +14,34 @@ padding: 2px; border-radius: 10px; } - +/* * */ /* DATA WRAPPER */ .dataWrapper{ margin: 0 auto; display: flex; flex-direction: column; align-items: center; + width: 100%; } /* TABLE WRAPPER */ .tableWrapper{ padding: 10px; width: 100%; -} \ No newline at end of file +} +/* * */ +/* TABLE DATA */ +.tableHeaders{ + font-size: var(--font-size-title); + font-weight: var(--font-weight-bold); +} + +.rowLabel{ + font-size: var(--font-size-subtitle); + font-weight: var(--font-weight-semibold); +} +.rowValue{ + font-size: var(--font-size-text); + font-weight: var(--font-weight-text); +} +/* * */ \ No newline at end of file diff --git a/frontend/components/vehicles/VehiclesListToolbar/index.tsx b/frontend/components/vehicles/VehiclesListToolbar/index.tsx index 8fcb8951..98f7a630 100644 --- a/frontend/components/vehicles/VehiclesListToolbar/index.tsx +++ b/frontend/components/vehicles/VehiclesListToolbar/index.tsx @@ -63,85 +63,80 @@ export default function Component() { // // C. Render components return ( - <> -
+
+ + } onChange={handleTextInputChange} placeholder={t('filter_by.search')} type="search" value={vehiclesListContext.filters.by_search} /> + ({ label: p.name, value: p.name })) || []} + leftSection={} + onChange={handlePropulsionChange} + placeholder={t('filter_by.propulsion')} + radius="sm" + value={vehiclesListContext.filters.by_propulsion?.split(' ') || []} + clearable + searchable + /> + + + - } onChange={handleTextInputChange} placeholder={t('filter_by.search')} type="search" value={vehiclesListContext.filters.by_search} /> + } + onChange={handleBikesAllowedInputChange} + placeholder={t('filter_by.bicycle')} + radius="sm" + value={vehiclesListContext.filters.by_isBicicleAllowed} + data={[{ label: 'Não', value: 'false' }, + { label: 'Sim', value: 'true' }]} + clearable + searchable + /> ({ label: p.name, value: p.name })) || []} - leftSection={} - onChange={handlePropulsionChange} - placeholder={t('filter_by.propulsion')} + data={vehiclesListContext.data?.agencys?.map(a => ({ label: a.name, value: a.agency_id.toString() })) || []} + leftSection={} + onChange={handleAgencyIdChange} + placeholder={t('filter_by.operator')} radius="sm" - value={vehiclesListContext.filters.by_propulsion?.split(' ') || []} + value={vehiclesListContext.filters.by_agency?.split(' ') || []} + clearable + searchable + /> + } + onChange={handleMakeAndModelChange} + placeholder={t('filter_by.make_model')} + radius="sm" + value={vehiclesListContext.filters.by_makeAndModel?.split(',') || []} + data={ + vehiclesListContext.data?.makes_and_models?.map(make => ({ + group: make.name, + items: make.models.map(model => ({ + label: model.name, + value: `${make.name}-${model.name}`, + })), + })) || [] + } clearable searchable /> - - - - } - onChange={handleBikesAllowedInputChange} - placeholder={t('filter_by.bicycle')} - radius="sm" - value={vehiclesListContext.filters.by_isBicicleAllowed} - data={[{ label: 'Não', value: 'false' }, - { label: 'Sim', value: 'true' }]} - clearable - searchable - /> - ({ label: a.name, value: a.agency_id.toString() })) || []} - leftSection={} - onChange={handleAgencyIdChange} - placeholder={t('filter_by.operator')} - radius="sm" - value={vehiclesListContext.filters.by_agency?.split(' ') || []} - clearable - searchable - /> - } - onChange={handleMakeAndModelChange} - placeholder={t('filter_by.make_model')} - radius="sm" - value={vehiclesListContext.filters.by_makeAndModel?.split(',') || []} - data={ - vehiclesListContext.data?.makes_and_models?.map(make => ({ - group: make.name, - items: make.models.map(model => ({ - label: model.name, - value: `${make.name}-${model.name}`, - })), - })) || [] - } - clearable - searchable - /> - - - - - -
-
- {!selectedVehicle && ()} - {selectedVehicle && ()} -
- + + + + {!selectedVehicle && (
)} + {selectedVehicle && ()} +
); // diff --git a/frontend/contexts/VehiclesList.context.tsx b/frontend/contexts/VehiclesList.context.tsx index 320b4745..b6df8719 100644 --- a/frontend/contexts/VehiclesList.context.tsx +++ b/frontend/contexts/VehiclesList.context.tsx @@ -1,10 +1,11 @@ 'use client'; - +/* * */ import { Routes } from '@/utils/routes'; import { Vehicle } from '@carrismetropolitana/api-types/vehicles'; import { useQueryState } from 'nuqs'; import { createContext, useContext, useEffect, useState } from 'react'; import useSWR from 'swr'; +/* * */ interface VehiclesListContextState { actions: { @@ -49,14 +50,15 @@ export function useVehiclesListContext() { } export const VehiclesListContextProvider = ({ children }) => { + // + // // A. Setup variables const [dataFilteredState, setDataFilteredState] = useState([]); const [dataSelectedState, setDataSelectedState] = useState(null); + const [allAgencys, setAllAgencys] = useState<{ agency_id: number, name: string }[]>([]); - const [allMakesAndModels, setAllMakesAndModels] = useState< - { id: number, models: { id: number, name: string }[], name: string }[] - >([]); + const [allMakesAndModels, setAllMakesAndModels] = useState<{ id: number, models: { id: number, name: string }[], name: string }[]>([]); const [allPropulsions, setAllPropulsions] = useState([]); const [filterByWheelchairAccesibleState, setWheelchairAccesibleState] = useQueryState('isWheelchair', { clearOnDefault: true }); @@ -76,35 +78,25 @@ export const VehiclesListContextProvider = ({ children }) => { let filterResult = allVehicleData || []; if (filterByBicycleAllowedState) { - filterResult = filterResult.filter( - item => item.bikes_allowed?.toString() === filterByBicycleAllowedState, - ); + filterResult = filterResult.filter(item => item.bikes_allowed?.toString() === filterByBicycleAllowedState); } if (filterByWheelchairAccesibleState) { - filterResult = filterResult.filter( - item => item.wheelchair_accessible?.toString() === filterByWheelchairAccesibleState, - ); + filterResult = filterResult.filter(item => item.wheelchair_accessible?.toString() === filterByWheelchairAccesibleState); } if (filterByPropulsionState) { const propulsionValues = filterByPropulsionState.split(' ').filter(Boolean); - filterResult = filterResult.filter(item => - item.propulsion && propulsionValues.includes(item.propulsion), - ); + filterResult = filterResult.filter(item => item.propulsion && propulsionValues.includes(item.propulsion)); } if (filterByAgencyState) { const agencyValues = filterByAgencyState.split(' ').filter(Boolean); - filterResult = filterResult.filter(item => - agencyValues.includes(item.agency_id.toString()), - ); + filterResult = filterResult.filter(item => agencyValues.includes(item.agency_id.toString())); } if (filterBySearchState) { - filterResult = filterResult.filter(item => - item.license_plate?.toLowerCase().includes(filterBySearchState.toLowerCase()), - ); + filterResult = filterResult.filter(item => item.license_plate?.toLowerCase().includes(filterBySearchState.toLowerCase())); } if (filterByMakeAndModelState && filterByMakeAndModelState.trim() !== '') { @@ -123,16 +115,13 @@ export const VehiclesListContextProvider = ({ children }) => { const getAllMakesAndModels = () => { if (!allVehicleData) return []; - const makesMap = new Map(); let makeIdCounter = 1; let modelIdCounter = 1; - allVehicleData.forEach((vehicle) => { if (vehicle.make !== undefined && vehicle.model !== undefined) { const makeName = vehicle.make; const modelName = vehicle.model; - if (!makesMap.has(makeName)) { makesMap.set(makeName, { id: makeIdCounter, @@ -141,16 +130,13 @@ export const VehiclesListContextProvider = ({ children }) => { }); makeIdCounter++; } - const makeObj = makesMap.get(makeName); - if (!makeObj.models.some(model => model.name === modelName)) { makeObj.models.push({ id: modelIdCounter, name: modelName }); modelIdCounter++; } } }); - const makes_and_models = Array.from(makesMap.values()); setAllMakesAndModels(makes_and_models); return makes_and_models; @@ -158,7 +144,6 @@ export const VehiclesListContextProvider = ({ children }) => { const getAllAgencys = () => { const agencysMap = new Map(); - allVehicleData?.forEach((vehicle) => { if (vehicle.agency_id !== undefined) { const agency_Id = vehicle.agency_id; @@ -181,21 +166,17 @@ export const VehiclesListContextProvider = ({ children }) => { const getAllPropulsion = () => { if (!allVehicleData) return []; - const propulsionsMap = new Map(); let idCounter = 1; - allVehicleData.forEach((vehicle) => { if (vehicle.propulsion !== undefined) { const propulsionType = vehicle.propulsion; - if (!propulsionsMap.has(propulsionType)) { propulsionsMap.set(propulsionType, { id: idCounter, name: propulsionType }); idCounter++; } } }); - const propulsions = Array.from(propulsionsMap.values()); setAllPropulsions(propulsions); return propulsions; @@ -238,21 +219,11 @@ export const VehiclesListContextProvider = ({ children }) => { }; const updateFilterByIsBikeAllowed = (isBikeAllowed: string) => { - if (Array.isArray(isBikeAllowed)) { - setByBicycleAllowedState(isBikeAllowed.join(' ')); - } - else { - setByBicycleAllowedState(isBikeAllowed); - } + setByBicycleAllowedState(isBikeAllowed); }; const updateFilterByWheelchair = (isWheelchair: string) => { - if (Array.isArray(isWheelchair)) { - setWheelchairAccesibleState(isWheelchair.join(' ')); - } - else { - setWheelchairAccesibleState(isWheelchair); - } + setWheelchairAccesibleState(isWheelchair); }; const updateFilterByMakeAndModel = (makeAndModel: string[]) => {