From 0bae3bf212d76ad11c8b57b87393fbb5897ccab0 Mon Sep 17 00:00:00 2001 From: JaPatryk Date: Mon, 2 Jun 2025 23:20:36 +0200 Subject: [PATCH] init filter --- ArtisanConnect/app/(tabs)/notices.jsx | 295 +++++++++++++----- ArtisanConnect/components/FilterSection.jsx | 0 ArtisanConnect/components/ui/slider/index.tsx | 264 ++++++++++++++++ ArtisanConnect/package-lock.json | 3 + ArtisanConnect/package.json | 1 + 5 files changed, 482 insertions(+), 81 deletions(-) create mode 100644 ArtisanConnect/components/FilterSection.jsx create mode 100644 ArtisanConnect/components/ui/slider/index.tsx diff --git a/ArtisanConnect/app/(tabs)/notices.jsx b/ArtisanConnect/app/(tabs)/notices.jsx index 957fdf9..f1f08ab 100644 --- a/ArtisanConnect/app/(tabs)/notices.jsx +++ b/ArtisanConnect/app/(tabs)/notices.jsx @@ -1,38 +1,105 @@ -// import React from "react" -import {FlatList, Text, ActivityIndicator, RefreshControl} from "react-native"; -import {useState, useEffect} from "react"; +import { FlatList, Text, ActivityIndicator, RefreshControl, Dimensions } from "react-native"; +import { useState, useEffect } from "react"; import { Ionicons } from "@expo/vector-icons"; -import {useNoticesStore} from "@/store/noticesStore"; -import {NoticeCard} from "@/components/NoticeCard"; -import { useLocalSearchParams } from "expo-router"; -import { HStack } from "@/components/ui/hstack" -import { Box } from "@/components/ui/box" -import { Button, ButtonText, ButtonIcon } from "@/components/ui/button"; +import { useNoticesStore } from "@/store/noticesStore"; +import { NoticeCard } from "@/components/NoticeCard"; +import { useLocalSearchParams, useRouter } from "expo-router"; +import { Box } from "@/components/ui/box"; +import { Button, ButtonText } from "@/components/ui/button"; +import { ChevronDownIcon } from "@/components/ui/icon"; +import { listCategories } from "@/api/categories"; +import { FormControl, FormControlLabel } from "@/components/ui/form-control"; +import { Input, InputField } from "@/components/ui/input"; +import { HStack } from "@/components/ui/hstack"; + import { - Actionsheet, - ActionsheetContent, - ActionsheetItem, - ActionsheetItemText, - ActionsheetDragIndicator, - ActionsheetDragIndicatorWrapper, - ActionsheetBackdrop, -} from "@/components/ui/actionsheet" -import { useRouter } from "expo-router"; + Actionsheet, + ActionsheetContent, + ActionsheetDragIndicator, + ActionsheetDragIndicatorWrapper, + ActionsheetBackdrop, +} from "@/components/ui/actionsheet"; +import { + Select, + SelectTrigger, + SelectInput, + SelectIcon, + SelectPortal, + SelectBackdrop, + SelectContent, + SelectDragIndicator, + SelectDragIndicatorWrapper, + SelectItem, +} from "@/components/ui/select"; export default function Notices() { - const {notices, fetchNotices} = useNoticesStore(); + // Hooks + const { notices, fetchNotices } = useNoticesStore(); const [refreshing, setRefreshing] = useState(false); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); - const [showActionsheet, setShowActionsheet] = useState(false) - const handleClose = () => setShowActionsheet(false) - + const [showActionsheet, setShowActionsheet] = useState(false); + const [categories, setCategories] = useState([]); + const [filteredNotices, setFilteredNotices] = useState([]); const params = useLocalSearchParams(); - console.log("GET params:", params); + const router = useRouter(); + + useEffect(() => { + const fetchSelectItems = async () => { + try { + const data = await listCategories(); + if (Array.isArray(data)) { + setCategories(data); + } else { + console.error('listCategories did not return an array:', data); + setError(new Error('Invalid categories data')); + } + } catch (error) { + console.error('Error fetching select items:', error); + setError(error); + } + }; + fetchSelectItems(); + }, []); + useEffect(() => { loadData(); }, []); + useEffect(() => { + let result = notices; + + if (params.category) { + result = result.filter(notice => notice.category === params.category); + } + + if (params.sort === "latest") { + result = [...result].sort( + (a, b) => new Date(b.publishDate) - new Date(a.publishDate) + ); + } + + if(params.priceFrom) { + result = result.filter(notice => { + const price = parseFloat(notice.price); + const priceFrom = parseFloat(params.priceFrom); + return !isNaN(price) && price >= priceFrom; + }); + } + + if (params.priceTo) { + result = result.filter(notice => { + const price = parseFloat(notice.price); + const priceTo = parseFloat(params.priceTo); + return !isNaN(price) && price <= priceTo; + }); + } + + setFilteredNotices(result); + }, [notices, params.category, params.sort, params.priceFrom, params.priceTo]); + + + let filterActive = !!params.category || params.sort === "latest"; const loadData = async () => { @@ -47,29 +114,29 @@ export default function Notices() { } }; - const router = useRouter(); + const handleCategorySelect = (value) => { + router.replace({ + pathname: "/notices", + params: { ...params, category: value } + }); + }; - let filteredNotices = notices; - let filterActive = false; - if (params.sort) { - if( params.sort === "latest") { - filteredNotices = [...filteredNotices].sort( - (a, b) => new Date(b.publishDate) - new Date(a.publishDate) - ); - } + const handlePriceFrom = (value) => { + router.replace({ + pathname: "/notices", + params: { ...params, priceFrom: value } + }); } - if(params.category) { - filteredNotices = filteredNotices.filter( - (notice) => notice.category === params.category - ); - filterActive = true; + const handlePriceTo = (value) => { + router.replace({ + pathname: "/notices", + params: { ...params, priceTo: value } + }); } - if(params.attribute) { - filterActive = true; - } - + const handleClose = () => setShowActionsheet(false); + const onRefresh = async () => { setRefreshing(true); try { @@ -82,52 +149,118 @@ export default function Notices() { }; if (isLoading && !refreshing) { - return ; + return ; } if (error) { - return Nie udało sie pobrać listy. {error.message}; + return Nie udało się pobrać listy. {error.message}; } + const SCREEN_HEIGHT = Dimensions.get('window').height; + + const selectedCategory = params.category && categories?.find( + (cat) => cat.value === params.category + ) || null; + return ( -<> - - - {filterActive && ( - )} - - - - - - - - - Kategoria - - - - } - refreshControl={ - - } - /> + <> + + + {filterActive && ( + + )} + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + refreshControl={ + + } + /> ); } \ No newline at end of file diff --git a/ArtisanConnect/components/FilterSection.jsx b/ArtisanConnect/components/FilterSection.jsx new file mode 100644 index 0000000..e69de29 diff --git a/ArtisanConnect/components/ui/slider/index.tsx b/ArtisanConnect/components/ui/slider/index.tsx new file mode 100644 index 0000000..cd67c1f --- /dev/null +++ b/ArtisanConnect/components/ui/slider/index.tsx @@ -0,0 +1,264 @@ +'use client'; +import { createSlider } from '@gluestack-ui/slider'; +import { Pressable } from 'react-native'; +import { View } from 'react-native'; +import React from 'react'; +import { tva } from '@gluestack-ui/nativewind-utils/tva'; +import { + withStyleContext, + useStyleContext, +} from '@gluestack-ui/nativewind-utils/withStyleContext'; +import type { VariantProps } from '@gluestack-ui/nativewind-utils'; +import { cssInterop } from 'nativewind'; + +const SCOPE = 'SLIDER'; +const Root = withStyleContext(View, SCOPE); +export const UISlider = createSlider({ + Root: Root, + Thumb: View, + Track: Pressable, + FilledTrack: View, + ThumbInteraction: View, +}); + +cssInterop(UISlider.Track, { className: 'style' }); + +const sliderStyle = tva({ + base: 'justify-center items-center data-[disabled=true]:opacity-40 data-[disabled=true]:web:pointer-events-none', + variants: { + orientation: { + horizontal: 'w-full', + vertical: 'h-full', + }, + size: { + sm: '', + md: '', + lg: '', + }, + isReversed: { + true: '', + false: '', + }, + }, +}); + +const sliderThumbStyle = tva({ + base: 'bg-primary-500 absolute rounded-full data-[focus=true]:bg-primary-600 data-[active=true]:bg-primary-600 data-[hover=true]:bg-primary-600 data-[disabled=true]:bg-primary-500 web:cursor-pointer web:data-[active=true]:outline web:data-[active=true]:outline-4 web:data-[active=true]:outline-primary-400 shadow-hard-1', + + parentVariants: { + size: { + sm: 'h-4 w-4', + md: 'h-5 w-5', + lg: 'h-6 w-6', + }, + }, +}); + +const sliderTrackStyle = tva({ + base: 'bg-background-300 rounded-lg overflow-hidden', + parentVariants: { + orientation: { + horizontal: 'w-full', + vertical: 'h-full', + }, + isReversed: { + true: '', + false: '', + }, + size: { + sm: '', + md: '', + lg: '', + }, + }, + parentCompoundVariants: [ + { + orientation: 'horizontal', + size: 'sm', + class: 'h-1 flex-row', + }, + { + orientation: 'horizontal', + size: 'sm', + isReversed: true, + class: 'h-1 flex-row-reverse', + }, + { + orientation: 'horizontal', + size: 'md', + class: 'h-1 flex-row', + }, + { + orientation: 'horizontal', + size: 'md', + isReversed: true, + class: 'h-[5px] flex-row-reverse', + }, + { + orientation: 'horizontal', + size: 'lg', + class: 'h-1.5 flex-row', + }, + { + orientation: 'horizontal', + size: 'lg', + isReversed: true, + class: 'h-1.5 flex-row-reverse', + }, + { + orientation: 'vertical', + size: 'sm', + class: 'w-1 flex-col-reverse', + }, + { + orientation: 'vertical', + size: 'sm', + isReversed: true, + class: 'w-1 flex-col', + }, + { + orientation: 'vertical', + size: 'md', + class: 'w-[5px] flex-col-reverse', + }, + { + orientation: 'vertical', + size: 'md', + isReversed: true, + class: 'w-[5px] flex-col', + }, + { + orientation: 'vertical', + size: 'lg', + class: 'w-1.5 flex-col-reverse', + }, + { + orientation: 'vertical', + size: 'lg', + isReversed: true, + class: 'w-1.5 flex-col', + }, + ], +}); + +const sliderFilledTrackStyle = tva({ + base: 'bg-primary-500 data-[focus=true]:bg-primary-600 data-[active=true]:bg-primary-600 data-[hover=true]:bg-primary-600', + parentVariants: { + orientation: { + horizontal: 'h-full', + vertical: 'w-full', + }, + }, +}); + +type ISliderProps = React.ComponentProps & + VariantProps; + +const Slider = React.forwardRef< + React.ComponentRef, + ISliderProps +>(function Slider( + { + className, + size = 'md', + orientation = 'horizontal', + isReversed = false, + ...props + }, + ref +) { + return ( + + ); +}); + +type ISliderThumbProps = React.ComponentProps & + VariantProps; + +const SliderThumb = React.forwardRef< + React.ComponentRef, + ISliderThumbProps +>(function SliderThumb({ className, size, ...props }, ref) { + const { size: parentSize } = useStyleContext(SCOPE); + + return ( + + ); +}); + +type ISliderTrackProps = React.ComponentProps & + VariantProps; + +const SliderTrack = React.forwardRef< + React.ComponentRef, + ISliderTrackProps +>(function SliderTrack({ className, ...props }, ref) { + const { + orientation: parentOrientation, + size: parentSize, + isReversed, + } = useStyleContext(SCOPE); + + return ( + + ); +}); + +type ISliderFilledTrackProps = React.ComponentProps< + typeof UISlider.FilledTrack +> & + VariantProps; + +const SliderFilledTrack = React.forwardRef< + React.ComponentRef, + ISliderFilledTrackProps +>(function SliderFilledTrack({ className, ...props }, ref) { + const { orientation: parentOrientation } = useStyleContext(SCOPE); + + return ( + + ); +}); + +export { Slider, SliderThumb, SliderTrack, SliderFilledTrack }; diff --git a/ArtisanConnect/package-lock.json b/ArtisanConnect/package-lock.json index 6f0e69f..8f6d65e 100644 --- a/ArtisanConnect/package-lock.json +++ b/ArtisanConnect/package-lock.json @@ -23,6 +23,7 @@ "@gluestack-ui/overlay": "^0.1.22", "@gluestack-ui/pressable": "^0.1.23", "@gluestack-ui/select": "^0.1.31", + "@gluestack-ui/slider": "^0.1.32", "@gluestack-ui/textarea": "^0.1.25", "@gluestack-ui/themed": "^1.1.73", "@gluestack-ui/toast": "^1.0.9", @@ -2329,6 +2330,8 @@ }, "node_modules/@gluestack-ui/slider": { "version": "0.1.32", + "resolved": "https://registry.npmjs.org/@gluestack-ui/slider/-/slider-0.1.32.tgz", + "integrity": "sha512-g0e7dAGOYYARlL3cdHe3mhN71j85TnqUgK/xOYWjVDE0U+atIXxxTVEXeO0ZPGJ3YUOUUAInIVGaa0xvnjEkYg==", "dependencies": { "@gluestack-ui/form-control": "^0.1.19", "@gluestack-ui/hooks": "0.1.13", diff --git a/ArtisanConnect/package.json b/ArtisanConnect/package.json index 29070cb..c8f9b57 100644 --- a/ArtisanConnect/package.json +++ b/ArtisanConnect/package.json @@ -24,6 +24,7 @@ "@gluestack-ui/overlay": "^0.1.22", "@gluestack-ui/pressable": "^0.1.23", "@gluestack-ui/select": "^0.1.31", + "@gluestack-ui/slider": "^0.1.32", "@gluestack-ui/textarea": "^0.1.25", "@gluestack-ui/themed": "^1.1.73", "@gluestack-ui/toast": "^1.0.9",