From 80fa81ae4b8900a0fdf8b4a970b9b391cfea66d0 Mon Sep 17 00:00:00 2001 From: goro Date: Fri, 21 Nov 2025 20:15:03 +0700 Subject: [PATCH] [REFACTOR]: move float filter to its own component --- src/components/Filter/FloatFilter/index.tsx | 104 +++++++++++ src/pages/Discovery/index.tsx | 181 ++++++-------------- 2 files changed, 156 insertions(+), 129 deletions(-) create mode 100644 src/components/Filter/FloatFilter/index.tsx diff --git a/src/components/Filter/FloatFilter/index.tsx b/src/components/Filter/FloatFilter/index.tsx new file mode 100644 index 0000000..1166b02 --- /dev/null +++ b/src/components/Filter/FloatFilter/index.tsx @@ -0,0 +1,104 @@ +import { useClick, useFloating, useInteractions, Side, AlignedPlacement, flip, shift, useDismiss } from "@floating-ui/react"; +import { TargetedEvent, useState, ChangeEvent } from "preact/compat"; +import { JSXInternal } from "node_modules/preact/src/jsx"; +import { Regency } from "src/domains"; + +interface floatFilterProps { + isOpen: boolean, + placement: Side | AlignedPlacement, + outsidePress?: () => void, + onChecked: (e: TargetedEvent, id: number) => void, + onClearFilter?: () => void, + onInputChange: (val: string) => void, + listData?: T[]; + title: string; +} + +export function FloatFilter(props: floatFilterProps) { + const [floatState, setFloatState] = useState({ + searchValue: "", + }) + + const { refs, floatingStyles, context } = useFloating({ + placement: props.placement, + open: props.isOpen, + middleware: [ + flip({ fallbackAxisSideDirection: "end" }), + shift() + ] + }); + + const click = useClick(context); + const dismiss = useDismiss(context, { + outsidePress: () => { + props.outsidePress && props.outsidePress() + return false + } + }); + + const { getReferenceProps, getFloatingProps } = useInteractions([ + click, + dismiss + ]); + + const style: JSXInternal.CSSProperties = { + ...floatingStyles, + top: 210, + left: 'none', + marginLeft: 150, + width: 355, + zIndex: 1, + } + + function onInputChange(e: ChangeEvent): void { + const input = e.target as HTMLInputElement; + setFloatState({ searchValue: input.value.toLowerCase() }) + props.onInputChange(input.value) + } + + if (props.isOpen) return ( +
+
+

{props.title}

+ +
+
+ {props.listData ? +
    + {props.listData.map(x => ( +
    + props.onChecked(e, x.id)} + /> + +
    + ))} +
+ : null} +
+
+ + +
+
+ ) + + return null +} \ No newline at end of file diff --git a/src/pages/Discovery/index.tsx b/src/pages/Discovery/index.tsx index 083b2ed..7e0d3af 100755 --- a/src/pages/Discovery/index.tsx +++ b/src/pages/Discovery/index.tsx @@ -1,115 +1,11 @@ import { useEffect, useState } from "preact/hooks"; -import { CheckboxInput, DefaultSeparator, FilterButton, LocationCard } from "../../components"; -import { useClick, useFloating, useInteractions, Side, AlignedPlacement, flip, shift, useDismiss } from "@floating-ui/react"; -import { JSXInternal } from "node_modules/preact/src/jsx"; +import { CheckboxInput, DefaultSeparator, FilterButton, LocationCard, SpinnerLoading } from "../../components"; import { getListRecentLocationsRatingsService, getRegenciesService, } from "../../services"; import { LocationInfo, Regency } from "../../domains"; -import { ChangeEvent, TargetedEvent } from "preact/compat"; import { LocationType } from "../../types/common"; import { enumKeys } from "../../utils"; import { useNavigate } from "react-router-dom"; -import './style.css'; - - -interface floatFilterArgs { - isOpen: boolean, - placement: Side | AlignedPlacement, - outsidePress?: () => void, - onChecked: (e: TargetedEvent, id: number) => void, - onClearFilter?: () => void, - onInputChange: (val: string) => void, - listData?: T[]; -} - -function FloatFilter(props: floatFilterArgs) { - const [floatState, setFloatState] = useState({ - searchValue: "", - }) - - const { refs, floatingStyles, context } = useFloating({ - placement: props.placement, - open: props.isOpen, - middleware: [ - flip({ fallbackAxisSideDirection: "end" }), - shift() - ] - }); - - const click = useClick(context); - const dismiss = useDismiss(context, { - outsidePress: () => { - props.outsidePress && props.outsidePress() - return false - } - }); - - const { getReferenceProps, getFloatingProps } = useInteractions([ - click, - dismiss - ]); - - const style: JSXInternal.CSSProperties = { - ...floatingStyles, - top: 210, - left: 'none', - marginLeft: 150, - width: 355, - zIndex: 1, - } - - function onInputChange(e: ChangeEvent): void { - const input = e.target as HTMLInputElement; - setFloatState({ searchValue: input.value.toLowerCase() }) - props.onInputChange(input.value) - } - - if (props.isOpen) return ( -
-
- -
-
- {props.listData ? -
    - {props.listData.map(x => ( -
    - props.onChecked(e, x.id)} - /> - -
    - ))} -
- : null} -
-
- - -
-
- ) - - return null -} - +import { FloatFilter } from "../../components/Filter/FloatFilter"; function Discovery() { @@ -129,20 +25,32 @@ function Discovery() { locationType: [] }); const [isFloatFilterOpen, setFloatFilterOpen] = useState(false) + const [isLoading, setIsLoading] = useState(true) const navigate = useNavigate() useEffect(() => { - getLocationType() - getRecentLocations() - getRegion() + loadData() }, []) + async function loadData() { + setIsLoading(true) + try { + await Promise.all([ + getLocationType(), + getRecentLocations(), + getRegion() + ]) + } finally { + setIsLoading(false) + } + } + async function getRegion() { try { const res = await getRegenciesService(); setData((prevState) => ({ ...prevState, regencies: res.data, searchRegencies: res.data })) } catch (err) { - alert(err) + alert("Something went wrong when fetch regions / regencies") } } @@ -248,35 +156,50 @@ function Discovery() { }) } + if (isLoading) { + return ( +
+
+

Discovery

+ +
+
+ +
+
+ ) + } + return (
-

Discovery

+

Discovery

-
+
{/* FILTER */}
-
-
-
Filter
+
+
+
Filter
-
-
Kota / Kabupaten
-
+
+
Kota / Kabupaten
+
{data.regencies.filter(x => x.isSelected).map(x => ( -
-
{x.regency_name}
- +
+
{x.regency_name}
+
))}
-
- +
+ setFloatFilterOpen(false)} onClearFilter={onClearFilter} @@ -285,10 +208,10 @@ function Discovery() { />
-
-
-
Tipe
-
+
+
+
Tipe
+
{data.locationType.map((x, i) => (
-
+
@@ -307,7 +230,7 @@ function Discovery() {
{data.locations.map(x => (