diff --git a/frontend/src/components/general/buttons/FilterButton.tsx b/frontend/src/components/general/buttons/FilterButton.tsx index cbe3990b..6d7daf91 100644 --- a/frontend/src/components/general/buttons/FilterButton.tsx +++ b/frontend/src/components/general/buttons/FilterButton.tsx @@ -1,24 +1,32 @@ -import React from 'react'; +import React from 'react' interface FilterButtonProps { - ariaLabel: string; - activeCount: number; - onClick: () => void; + ariaLabel: string + activeCount: number + isOnMap: boolean + onClick: () => void } -const FilterButton: React.FC = ({ ariaLabel, activeCount, onClick }) => { - +const FilterButton: React.FC = ({ + ariaLabel, + activeCount, + onClick, + isOnMap, +}) => { return ( - - ); -}; + ) +} -export default FilterButton; +export default FilterButton diff --git a/frontend/src/components/general/cards/DashboardCard.tsx b/frontend/src/components/general/cards/DashboardCard.tsx index d518a46b..432f4385 100644 --- a/frontend/src/components/general/cards/DashboardCard.tsx +++ b/frontend/src/components/general/cards/DashboardCard.tsx @@ -1,27 +1,33 @@ -import { Link } from '@tanstack/react-router'; -import { MoveRight } from 'lucide-react'; -import React from 'react'; +import { Link } from '@tanstack/react-router' +import { MoveRight } from 'lucide-react' +import React from 'react' interface DashboardCard { - headline: string; - description: string; - linkLabel: string; - url: string; - theme: "dark" | "light" | "white"; + headline: string + description: string + linkLabel: string + url: string + theme: 'dark' | 'light' | 'white' } -const DashboardCard: React.FC = ({ headline, description, linkLabel, url, theme }) => { +const DashboardCard: React.FC = ({ + headline, + description, + linkLabel, + url, + theme, +}) => { const themeClasses = { dark: 'border-green-dark bg-green-dark-50 hover:bg-green-dark-100', light: 'border-green-light bg-green-light-50 hover:bg-green-light-100', - white: 'border-dark-50 bg-white hover:bg-dark-100' - }; + white: 'border-dark-50 bg-white hover:bg-dark-100', + } return ( - + className={`shadow-cards border h-full p-6 rounded-xl group flex flex-col gap-4 transition-all ease-in-out duration-300 ${themeClasses[theme]}`} + >

{headline}

{description}

@@ -29,10 +35,7 @@ const DashboardCard: React.FC = ({ headline, description, linkLab

- ); + ) } - - - -export default DashboardCard; +export default DashboardCard diff --git a/frontend/src/components/general/filter/Dialog.tsx b/frontend/src/components/general/filter/Dialog.tsx index 61c60b52..fbb14a48 100644 --- a/frontend/src/components/general/filter/Dialog.tsx +++ b/frontend/src/components/general/filter/Dialog.tsx @@ -1,146 +1,173 @@ -import useTreeclusterFilter from '@/hooks/useTreeclusterFilter' -import React, { useState } from 'react'; -import FilterButton from '../buttons/FilterButton'; -import PrimaryButton from '../buttons/PrimaryButton'; -import SecondaryButton from '../buttons/SecondaryButton'; -import { X } from 'lucide-react'; -import useOutsideClick from '@/hooks/useOutsideClick'; -import Option from './Option'; -import { useNavigate } from '@tanstack/react-router'; -import { useSuspenseQuery } from '@tanstack/react-query'; -import { EntitiesWateringStatus } from '@/api/backendApi'; -import { getWateringStatusDetails } from '@/hooks/useDetailsForWateringStatus'; -import { regionsQuery } from '@/api/queries'; +import { + ForwardedRef, + forwardRef, + useEffect, + useImperativeHandle, + useState, +} from 'react' +import FilterButton from '../buttons/FilterButton' +import PrimaryButton from '../buttons/PrimaryButton' +import SecondaryButton from '../buttons/SecondaryButton' +import { X } from 'lucide-react' +import useOutsideClick from '@/hooks/useOutsideClick' +import { useNavigate } from '@tanstack/react-router' +import useFilter from '@/hooks/useFilter' +import { Filters } from '@/context/FilterContext' +import useStore from '@/store/store' interface DialogProps { - initStatusTags: string[] - initRegionTags: string[] headline: string fullUrlPath: string - applyFilter: (statusTags: string[], regionTags: string[]) => void + children?: React.ReactNode + isOnMap?: boolean + onApplyFilters: () => void + onResetFilters: () => void } -const Dialog: React.FC = ({ initStatusTags, initRegionTags, headline, fullUrlPath, applyFilter }) => { - const [isOpen, setIsOpen] = useState(false); - const navigate = useNavigate({ from: fullUrlPath }) - const dialogRef = useOutsideClick(() => close()) - const { data: regionRes } = useSuspenseQuery(regionsQuery()); - - const { - filters, - setFilters, - appliedFilters, - handleFilterChange, - resetFilters, - applyFilters, - } = useTreeclusterFilter(initStatusTags, initRegionTags) - - const resetAndClose = () => { - resetFilters(applyFilter) - setIsOpen(false) - navigate({ search: () => ({}) }) - } +const Dialog = forwardRef( + ( + { + headline, + fullUrlPath, + onApplyFilters, + onResetFilters, + children, + isOnMap = false, + }: DialogProps, + ref: ForwardedRef + ) => { + const [isOpen, setIsOpen] = useState(false) + const [oldValues, setOldValues] = useState({ + statusTags: [], + regionTags: [], + hasCluster: undefined, + plantingYears: [], + }) + const [count, setCount] = useState(0) + const navigate = useNavigate({ from: fullUrlPath }) + const mapPosition = useStore((state) => ({ + lat: state.map.center[0], + lng: state.map.center[1], + zoom: state.map.zoom, + })) + useImperativeHandle(ref, () => dialogRef.current) - const close = () => { - setIsOpen(false) - setFilters({ - statusTags: appliedFilters.statusTags, - regionTags: appliedFilters.regionTags, + const dialogRef = useOutsideClick(() => { + if (!isOpen) return + handleClose() }) - } - const handleApplyFilters = () => { - applyFilters(applyFilter) - setIsOpen(false) + const { filters, resetFilters, applyOldStateToTags } = useFilter() - navigate({ - search: () => ({ - status: filters.statusTags.length > 0 ? filters.statusTags : undefined, - region: filters.regionTags.length > 0 ? filters.regionTags : undefined, - }), - }) - } + const handleSubmit = () => { + onApplyFilters() + setIsOpen(false) + setIsOpen(false) + + navigate({ + search: () => ({ + lat: isOnMap ? mapPosition.lat : undefined, + lng: isOnMap ? mapPosition.lng : undefined, + zoom: isOnMap ? mapPosition.zoom : undefined, + status: + filters.statusTags.length > 0 ? filters.statusTags : undefined, + region: + filters.regionTags.length > 0 ? filters.regionTags : undefined, + hasCluster: filters.hasCluster ?? undefined, + plantingYears: + filters.plantingYears.length > 0 + ? filters.plantingYears + : undefined, + }), + }) + } + + const handleReset = () => { + onResetFilters() + applyOldStateToTags({ + statusTags: [], + regionTags: [], + hasCluster: undefined, + plantingYears: [], + }) + resetFilters() + setIsOpen(false) + isOnMap + ? navigate({ + search: { + lat: mapPosition.lat, + lng: mapPosition.lng, + zoom: mapPosition.zoom, + }, + }) + : navigate({ search: () => ({}) }) + } + + const handleClose = () => { + setIsOpen(false) + applyOldStateToTags(oldValues) + } - return ( -
-
- - { - isOpen ? setIsOpen(false) : setIsOpen(true) - }} - /> - -
-
-

{headline}

- -
- -
- - Zustand der Bewässerung: - - {Object.entries(EntitiesWateringStatus).map( - ([statusKey, statusValue]) => ( -
- -
- Stadtteil in Flensburg: - {regionRes.regions.map((region) => ( -
- -
- - -
-
-
- ) -} + + + + + ) + } +) export default Dialog diff --git a/frontend/src/components/general/filter/Option.tsx b/frontend/src/components/general/filter/Option.tsx index bfe621d6..59b73248 100644 --- a/frontend/src/components/general/filter/Option.tsx +++ b/frontend/src/components/general/filter/Option.tsx @@ -3,6 +3,7 @@ import React from 'react' interface OptionProps { name: string label: string + value?: string children?: React.ReactNode checked: boolean onChange: (event: React.ChangeEvent) => void @@ -11,6 +12,7 @@ interface OptionProps { const Option: React.FC = ({ name, label, + value, children, checked, onChange, @@ -24,7 +26,7 @@ const Option: React.FC = ({ type="checkbox" name={name} checked={checked} - value={label} + value={value ?? label} onChange={onChange} className="opacity-0 w-0 h-0" /> diff --git a/frontend/src/components/general/filter/fieldsets/ClusterFieldset.tsx b/frontend/src/components/general/filter/fieldsets/ClusterFieldset.tsx new file mode 100644 index 00000000..e4e4f35c --- /dev/null +++ b/frontend/src/components/general/filter/fieldsets/ClusterFieldset.tsx @@ -0,0 +1,36 @@ +import Option from '../Option' +import useFilter from '@/hooks/useFilter' + +const ClusterFieldset = () => { + const { filters, handleClusterChange } = useFilter() + const treeClusterOptions = [ + { + label: 'Gruppe zugehörig', + value: true, + }, + { + label: 'Keiner Gruppe zugehörig', + value: false, + } + ] + return ( +
+ + Zugehörigkeit einer Bewässerungsgruppe: + + {treeClusterOptions.map((type, key) => ( +
+ ) +} + +export default ClusterFieldset diff --git a/frontend/src/components/general/filter/fieldsets/PlantingYearFieldset.tsx b/frontend/src/components/general/filter/fieldsets/PlantingYearFieldset.tsx new file mode 100644 index 00000000..664e2b08 --- /dev/null +++ b/frontend/src/components/general/filter/fieldsets/PlantingYearFieldset.tsx @@ -0,0 +1,27 @@ +import useFilter from '@/hooks/useFilter' +import Option from '../Option' + +const PlantingYearFieldset = () => { + const { filters, handlePlantingYearChange } = useFilter() + const currentYear = new Date().getFullYear() + const years = Array.from({ length: 5 }, (_, i) => currentYear - i) + + return ( +
+ + Pflanzjahr: + + {years.map((year, key) => ( +
+ ) +} + +export default PlantingYearFieldset \ No newline at end of file diff --git a/frontend/src/components/general/filter/fieldsets/RegionFieldset.tsx b/frontend/src/components/general/filter/fieldsets/RegionFieldset.tsx new file mode 100644 index 00000000..80a74cb1 --- /dev/null +++ b/frontend/src/components/general/filter/fieldsets/RegionFieldset.tsx @@ -0,0 +1,28 @@ +import Option from '../Option' +import useFilter from '@/hooks/useFilter' +import { useSuspenseQuery } from '@tanstack/react-query' +import { regionsQuery } from '@/api/queries' + +const RegionFieldset = () => { + const { filters, handleRegionChange } = useFilter() + const { data: regionRes } = useSuspenseQuery(regionsQuery()); + + return ( +
+ + Stadtteil in Flensburg: + + {regionRes?.regions.map((region) => ( +
+ ) +} + +export default RegionFieldset diff --git a/frontend/src/components/general/filter/fieldsets/StatusFieldset.tsx b/frontend/src/components/general/filter/fieldsets/StatusFieldset.tsx new file mode 100644 index 00000000..9a544ccf --- /dev/null +++ b/frontend/src/components/general/filter/fieldsets/StatusFieldset.tsx @@ -0,0 +1,34 @@ +import Option from '../Option' +import { getWateringStatusDetails } from '@/hooks/useDetailsForWateringStatus' +import useFilter from '@/hooks/useFilter' +import { EntitiesWateringStatus } from '@green-ecolution/backend-client' + +const StatusFieldset = () => { + const { filters, handleStatusChange } = useFilter() + return ( +
+ + Zustand der Bewässerung: + + {Object.entries(EntitiesWateringStatus).map( + ([statusKey, statusValue]) => ( +
+ ) +} + +export default StatusFieldset diff --git a/frontend/src/components/map/Map.tsx b/frontend/src/components/map/Map.tsx index 321a3aaa..0b861655 100644 --- a/frontend/src/components/map/Map.tsx +++ b/frontend/src/components/map/Map.tsx @@ -9,7 +9,7 @@ export interface MapProps extends React.PropsWithChildren { const Map = ({ width = '100%', - height = 'calc(100dvh - 4.563rem)', // calculate screen height minus header height + height = 'calc(100dvh - 4.563rem)', children, }: MapProps) => { const { zoom, center, maxZoom, minZoom } = useMapStore((state) => ({ diff --git a/frontend/src/components/map/MapButtons.tsx b/frontend/src/components/map/MapButtons.tsx index 2b88573a..b64012d0 100644 --- a/frontend/src/components/map/MapButtons.tsx +++ b/frontend/src/components/map/MapButtons.tsx @@ -5,7 +5,7 @@ export interface MapButtonsProps extends React.PropsWithChildren {} const MapButtons = ({ children }: MapButtonsProps) => { return ( -
+
{children !== undefined ? children : }
@@ -16,8 +16,7 @@ const MapButtons = ({ children }: MapButtonsProps) => { const DefaultMapButtons = () => { return ( <> - {/* {}} /> */} - + {/* */} ); diff --git a/frontend/src/components/map/TreeMarker.tsx b/frontend/src/components/map/TreeMarker.tsx index 3a868b0e..3c7f5735 100644 --- a/frontend/src/components/map/TreeMarker.tsx +++ b/frontend/src/components/map/TreeMarker.tsx @@ -84,6 +84,7 @@ interface WithTreesAndClustersProps { trees: Tree[] clusters: TreeCluster[] zoomThreshold?: number + activeFilter?: boolean, } export const WithTreesAndClusters = ({ @@ -94,6 +95,7 @@ export const WithTreesAndClusters = ({ trees, clusters, zoomThreshold = 17, + activeFilter = false, }: WithTreesAndClustersProps) => { const { zoom } = useStore((state) => ({ zoom: state.map.zoom, @@ -101,7 +103,7 @@ export const WithTreesAndClusters = ({ return ( <> - {zoom >= zoomThreshold + {zoom >= zoomThreshold || activeFilter ? : } diff --git a/frontend/src/components/treecluster/TreeClusterList.tsx b/frontend/src/components/treecluster/TreeClusterList.tsx index 39170258..ecbdfde5 100644 --- a/frontend/src/components/treecluster/TreeClusterList.tsx +++ b/frontend/src/components/treecluster/TreeClusterList.tsx @@ -1,31 +1,14 @@ -import { treeClusterQuery } from '@/api/queries' -import { getWateringStatusDetails } from '@/hooks/useDetailsForWateringStatus' -import { useSuspenseQuery } from '@tanstack/react-query' import TreeclusterCard from '../general/cards/TreeclusterCard' +import { TreeCluster } from '@/api/backendApi' interface TreeClusterListProps { - filter: { - status: string[] - region: string[] - } + filteredData: TreeCluster[]; } -const TreeClusterList = ({ filter }: TreeClusterListProps) => { - const { data: clustersRes } = useSuspenseQuery(treeClusterQuery()) - - const filteredClusters = clustersRes?.data.filter( - (cluster) => - (filter.status.length === 0 || - filter.status.includes( - getWateringStatusDetails(cluster.wateringStatus).label - )) && - (filter.region.length === 0 || - filter.region.includes(cluster.region?.name || '')) - ) - +const TreeClusterList = ({ filteredData }: TreeClusterListProps) => { return (
    - {filteredClusters?.length === 0 ? ( + {filteredData?.length === 0 ? (
  • Es wurden keine Bewässerungsgruppen gefunden, die den @@ -33,7 +16,7 @@ const TreeClusterList = ({ filter }: TreeClusterListProps) => {

  • ) : ( - filteredClusters?.map((cluster, key) => ( + filteredData?.map((cluster, key) => (
  • diff --git a/frontend/src/context/FilterContext.tsx b/frontend/src/context/FilterContext.tsx new file mode 100644 index 00000000..da2e20d2 --- /dev/null +++ b/frontend/src/context/FilterContext.tsx @@ -0,0 +1,101 @@ +import React, { createContext, useState, ReactNode } from 'react' + +export interface Filters { + statusTags: string[] + regionTags: string[] + hasCluster: boolean | undefined + plantingYears: number[] +} + +interface FilterContextType { + filters: Filters + handleStatusChange: (event: React.ChangeEvent) => void + handleRegionChange: (event: React.ChangeEvent) => void + handleClusterChange: (event: React.ChangeEvent) => void + handlePlantingYearChange: (event: React.ChangeEvent) => void + resetFilters: () => void + applyOldStateToTags: (oldValues: Filters) => void +} + +export const FilterContext = createContext( + undefined +) + +interface FilterProviderProps { + initialStatus?: string[] + initialRegions?: string[] + initialHasCluster?: boolean | undefined + initialPlantingYears?: number[] + children: ReactNode +} + +const FilterProvider: React.FC = ({ + initialStatus = [], + initialRegions = [], + initialHasCluster = undefined, + initialPlantingYears = [], + children, +}) => { + const [statusTags, setStatusTags] = useState(initialStatus) + const [regionTags, setRegionTags] = useState(initialRegions) + const [plantingYears, setPlantingYears] = useState(initialPlantingYears) + const [hasCluster, setHasCluster] = useState(initialHasCluster) + + const handleStatusChange = (event: React.ChangeEvent) => { + const { checked, value } = event.target + setStatusTags((prev) => + checked ? [...prev, value] : prev.filter((tag) => tag !== value) + ) + } + + const handleRegionChange = (event: React.ChangeEvent) => { + const { checked, value } = event.target + setRegionTags((prev) => + checked ? [...prev, value] : prev.filter((tag) => tag !== value) + ) + } + + const handleClusterChange = (event: React.ChangeEvent) => { + const { value } = event.target + setHasCluster(value === "true"); + } + + const handlePlantingYearChange = (event: React.ChangeEvent) => { + const { checked, value } = event.target; + setPlantingYears((prev) => + checked ? [...prev, Number(value)] : prev.filter((year) => year !== Number(value)) + ); + } + + const applyOldStateToTags = (oldValues: Filters) => { + setStatusTags(oldValues.statusTags) + setRegionTags(oldValues.regionTags) + setHasCluster(oldValues.hasCluster) + setPlantingYears(oldValues.plantingYears) + } + + const resetFilters = () => { + setStatusTags([]) + setRegionTags([]) + setHasCluster(undefined) + setPlantingYears([]) + } + + return ( + + {children} + + ) +} + +export default FilterProvider diff --git a/frontend/src/hooks/form/useFormSync.ts b/frontend/src/hooks/form/useFormSync.ts index 852fe35d..e640c0c8 100644 --- a/frontend/src/hooks/form/useFormSync.ts +++ b/frontend/src/hooks/form/useFormSync.ts @@ -17,13 +17,11 @@ export const useFormSync = ( }); useEffect(() => { - console.log("defaultValues", defaultValues); defaultValues && form.reset(defaultValues); }, [defaultValues, form]); useEffect(() => { const { unsubscribe } = form.watch((value) => { - console.log(value); commit(value); }); diff --git a/frontend/src/hooks/useBreadcrumb.ts b/frontend/src/hooks/useBreadcrumb.ts index 5797205f..3c9a6dac 100644 --- a/frontend/src/hooks/useBreadcrumb.ts +++ b/frontend/src/hooks/useBreadcrumb.ts @@ -3,7 +3,6 @@ import { useRouterState } from '@tanstack/react-router' export function useBreadcrumbs() { const breadcrumbs = useRouterState({ select: (state) => { - console.log(state) return state.matches .map((match) => ({ title: match.meta?.find((tag) => tag.title)!.title as string, @@ -12,8 +11,5 @@ export function useBreadcrumbs() { .filter((crumb) => Boolean(crumb.title)) }, }) - - console.log(breadcrumbs) - return breadcrumbs } diff --git a/frontend/src/hooks/useFilter.tsx b/frontend/src/hooks/useFilter.tsx new file mode 100644 index 00000000..d1948371 --- /dev/null +++ b/frontend/src/hooks/useFilter.tsx @@ -0,0 +1,12 @@ +import { FilterContext } from '@/context/FilterContext' +import { useContext } from 'react' + +const useFilter = () => { + const context = useContext(FilterContext) + if (context === undefined) { + throw new Error('useFilter must be used within a FilterProvider') + } + return context +} + +export default useFilter diff --git a/frontend/src/routes/_protected/map/index.tsx b/frontend/src/routes/_protected/map/index.tsx index bc649b5c..e8f4b44b 100644 --- a/frontend/src/routes/_protected/map/index.tsx +++ b/frontend/src/routes/_protected/map/index.tsx @@ -1,18 +1,39 @@ -import { createFileRoute, useNavigate } from '@tanstack/react-router' +import { + createFileRoute, + useLoaderData, + useNavigate, +} from '@tanstack/react-router' import MapButtons from '@/components/map/MapButtons' import { Tree, TreeCluster } from '@green-ecolution/backend-client' import { useSuspenseQuery } from '@tanstack/react-query' import { WithTreesAndClusters } from '@/components/map/TreeMarker' import { treeClusterQuery, treeQuery } from '@/api/queries' +import { useRef, useState } from 'react' +import Dialog from '@/components/general/filter/Dialog' +import { useMapMouseSelect } from '@/hooks/useMapMouseSelect' +import { useMap } from 'react-leaflet' +import StatusFieldset from '@/components/general/filter/fieldsets/StatusFieldset' +import { getWateringStatusDetails } from '@/hooks/useDetailsForWateringStatus' +import useFilter from '@/hooks/useFilter' +import FilterProvider from '@/context/FilterContext' +import { z } from 'zod' +import ClusterFieldset from '@/components/general/filter/fieldsets/ClusterFieldset' +import PlantingYearFieldset from '@/components/general/filter/fieldsets/PlantingYearFieldset' -export const Route = createFileRoute('/_protected/map/')({ - component: MapView, +const mapFilterSchema = z.object({ + status: z.array(z.string()).optional(), + hasCluster: z.boolean().optional(), + plantingYears: z.array(z.number()).optional(), }) function MapView() { const navigate = useNavigate({ from: '/map' }) + const map = useMap() + const dialogRef = useRef(null) + const [activeFilter, setActiveFilter] = useState(true) const { data: cluster } = useSuspenseQuery(treeClusterQuery()) const { data: trees } = useSuspenseQuery(treeQuery()) + const { filters } = useFilter() const handleTreeClick = (tree: Tree) => { navigate({ to: `/tree/$treeId`, params: { treeId: tree.id.toString() } }) @@ -25,15 +46,103 @@ function MapView() { }) } + useMapMouseSelect((_, e) => { + const target = e.originalEvent.target as HTMLElement + if (dialogRef.current?.contains(target)) { + map.dragging.disable() + map.scrollWheelZoom.disable() + } else { + map.dragging.enable() + map.scrollWheelZoom.enable() + } + }) + + const filterData = () => { + return trees.data.filter((tree) => { + const statusFilter = + filters.statusTags.length === 0 || + filters.statusTags.includes( + getWateringStatusDetails(tree.wateringStatus).label + ) + + const hasCluster = + filters.hasCluster === undefined || + (filters.hasCluster && tree.treeClusterId) || + (!filters.hasCluster && !tree.treeClusterId) + + const plantingYearsFilter = + filters.plantingYears.length === 0 || + filters.plantingYears.includes(tree.plantingYear); + + return statusFilter && hasCluster && plantingYearsFilter + }) + } + + const [filteredData, setFilteredData] = useState(filterData()) + + const handleFilter = () => { + const data = filterData() + setActiveFilter(true) + setFilteredData(data) + } + + const handleReset = () => { + setActiveFilter(false) + setFilteredData(trees.data) + } + return ( <> +
    + + + + + +
    ) } + +const MapViewWithProvider = () => { + const search = useLoaderData({ from: '/_protected/map/' }) + return ( + + + + ) +} + +export const Route = createFileRoute('/_protected/map/')({ + component: MapViewWithProvider, + validateSearch: mapFilterSchema, + + loaderDeps: ({ search: { status, hasCluster, plantingYears } }) => ({ + status: status || [], + hasCluster: hasCluster || undefined, + plantingYears: plantingYears || [], + }), + + loader: ({ deps: { status, hasCluster, plantingYears } }) => { + return { status, hasCluster, plantingYears } + }, +}) diff --git a/frontend/src/routes/_protected/treecluster/index.tsx b/frontend/src/routes/_protected/treecluster/index.tsx index b510e2e5..95ca8ee8 100644 --- a/frontend/src/routes/_protected/treecluster/index.tsx +++ b/frontend/src/routes/_protected/treecluster/index.tsx @@ -1,32 +1,52 @@ -import { useState, useEffect, Suspense } from 'react' import Dialog from '@/components/general/filter/Dialog' -import { createFileRoute } from '@tanstack/react-router' +import { createFileRoute, useLoaderData } from '@tanstack/react-router' import { z } from 'zod' import ButtonLink from '@/components/general/links/ButtonLink' import { Plus } from 'lucide-react' +import { getWateringStatusDetails } from '@/hooks/useDetailsForWateringStatus' +import { TreeCluster } from '@/api/backendApi' +import { useSuspenseQuery } from '@tanstack/react-query' import LoadingInfo from '@/components/general/error/LoadingInfo' +import FilterProvider from '@/context/FilterContext' +import useFilter from '@/hooks/useFilter' +import { Suspense, useState } from 'react' +import StatusFieldset from '@/components/general/filter/fieldsets/StatusFieldset' +import RegionFieldset from '@/components/general/filter/fieldsets/RegionFieldset' import { ErrorBoundary } from 'react-error-boundary' import TreeClusterList from '@/components/treecluster/TreeClusterList' +import { treeClusterQuery } from '@/api/queries' const treeclusterFilterSchema = z.object({ status: z.array(z.string()).optional().default([]), region: z.array(z.string()).optional().default([]), }) -export const Route = createFileRoute('/_protected/treecluster/')({ - component: Treecluster, - validateSearch: treeclusterFilterSchema, -}) - function Treecluster() { - const search = Route.useSearch() - const [statusTags, setStatusTags] = useState(search.status) - const [regionTags, setRegionTags] = useState(search.region) + const { data: clustersRes } = useSuspenseQuery(treeClusterQuery()) + const { filters } = useFilter() + + const filterData = () => { + return clustersRes.data.filter((cluster) => { + const statusFilter = + filters.statusTags.length === 0 || + filters.statusTags.includes( + getWateringStatusDetails(cluster.wateringStatus).label + ) - useEffect(() => { - if (search.status) setStatusTags(search.status) - if (search.region) setRegionTags(search.region) - }, [search.status, search.region]) + const regionFilter = + filters.regionTags.length === 0 || + filters.regionTags.includes(cluster.region.name) + + return statusFilter && regionFilter + }) + } + + const [filteredData, setFilteredData] = useState(filterData()) + + const handleFilter = () => { + const data = filterData(); + setFilteredData(data); + } return (
    @@ -51,15 +71,14 @@ function Treecluster() {
    { - setStatusTags(statusTags) - setRegionTags(regionTags) - }} - /> + onApplyFilters={() => handleFilter()} + onResetFilters={() => setFilteredData(clustersRes.data)} + > + + +
    @@ -78,12 +97,38 @@ function Treecluster() {

    } > - +
    ) } + +const TreeclusterWithProvider = () => { + const search = useLoaderData({ from: '/_protected/treecluster/' }) + return ( + + + + ) +} + +export const Route = createFileRoute('/_protected/treecluster/')({ + component: TreeclusterWithProvider, + validateSearch: treeclusterFilterSchema, + + loaderDeps: ({ search: { status, region } }) => ({ + status: status || [], + region: region || [], + }), + + loader: ({ deps: { status, region } }) => { + return { status, region } + }, +}) + +export default TreeclusterWithProvider diff --git a/frontend/src/routes/logout.tsx b/frontend/src/routes/logout.tsx index d9c6a094..3af2f421 100644 --- a/frontend/src/routes/logout.tsx +++ b/frontend/src/routes/logout.tsx @@ -17,7 +17,6 @@ export const Route = createFileRoute('/logout')({ }, }) .then(() => { - console.log('Logged out') useStore.setState((state) => { state.auth.isAuthenticated = false state.auth.token = null diff --git a/frontend/src/store/form/useFormStore.ts b/frontend/src/store/form/useFormStore.ts index db965fd7..1c094b03 100644 --- a/frontend/src/store/form/useFormStore.ts +++ b/frontend/src/store/form/useFormStore.ts @@ -23,7 +23,6 @@ const useFormStore = create>()( form: undefined, type: "new", commit: (form) => { - console.log("commit", form); set((state) => { state.form = form; });