Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/entities/listings/hooks/useListingDetailSheetHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const useListingDetailNoticeSheet = <T>({ id, url }: UseListingsHooksWith

return useQuery<T, Error, T | null>({
queryKey: [url],
enabled: !!id,
enabled: !!id && !!url,
staleTime: 1000 * 60 * 5,

queryFn: () =>
Expand Down
9 changes: 6 additions & 3 deletions src/features/listings/ui/listingsCardDetail/button/button.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { useDetailFilterResultButton } from "@/src/features/listings/ui/listingsCardDetail/hooks/hooks";
import { useDetailFilterResultButton } from "@/src/features/listings/ui/listingsCardDetail/hooks/routerHooks";

type ListingCardDetailProps = {
filteredCount: number;
handleCloseSheet: () => void;
};
export const ListingCardDetailOut = ({ filteredCount, handleCloseSheet }: ListingCardDetailProps) => {
export const ListingCardDetailOut = ({
filteredCount,
handleCloseSheet,
}: ListingCardDetailProps) => {
return (
<div>
<button
Expand All @@ -16,4 +19,4 @@ export const ListingCardDetailOut = ({ filteredCount, handleCloseSheet }: Listin
</button>
</div>
);
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { CostFilter } from "./components/CostFilter";
import { RegionFilter } from "./components/regionFilter";
import { AreaFilter } from "./components/areaFilter";
import { ListingCardDetailOut } from "@/src/features/listings/ui/listingsCardDetail/button/button";
import { useDetailFilterResultButton } from "@/src/features/listings/ui/listingsCardDetail/hooks/hooks";
import { useDetailFilterResultButton } from "@/src/features/listings/ui/listingsCardDetail/hooks/routerHooks";

export const DetailFilterSheet = () => {
const open = useDetailFilterSheetStore(s => s.open);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,22 @@ import {
mapPinPointToOptions,
} from "@/src/features/listings/hooks/listingsHooks";
import { ListingCardDetailOut } from "@/src/features/listings/ui/listingsCardDetail/button/button";
import {
useDistanceHooks,
useDistanceVariable,
} from "@/src/features/listings/ui/listingsCardDetail/hooks/distanceHooks";

const SLIDER_MIN = 0;
const SLIDER_MAX = 120;

export const DistanceFilter = () => {
const { data, isFetching } = useListingFilterDetail<PinPointPlace>();
const pinPointData = data?.pinPoints;
const pinPointList = mapPinPointToOptions(pinPointData);
const dropDownTriggerLabel = getDefaultPinPointLabel(pinPointList);
const hasPinPoints = pinPointList.myPinPoint.length > 0;
const { setPinPointId } = useOAuthStore();
const { distance, setDistance } = useListingDetailFilter();


const onChageValue = (selectedKey: string) => {
setPinPointId(selectedKey);
};

const handleDistanceChange = (values: number[]) => {
const [nextValue] = values;
if (typeof nextValue === "number") {
setDistance(nextValue);
}
};

const sliderValue = [distance];
const formatMinutes = (value: number) => value.toString().padStart(1, "0");
const formattedDistance = formatMinutes(distance);
const emptyPinPoint: PinPointPlace = { userName: "", pinPoints: [] };
const { pinPointList, dropDownTriggerLabel, hasPinPoints } = useDistanceVariable(
data ?? emptyPinPoint
);
const { onChangeValue, handleDistanceChange, sliderValue, formattedDistance } =
useDistanceHooks();

return (
<div className="flex h-full flex-col">
Expand All @@ -54,7 +42,7 @@ export const DistanceFilter = () => {
types="myPinPoint"
data={pinPointList}
size="lg"
onChange={onChageValue}
onChange={onChangeValue}
disabled={isFetching || !hasPinPoints}
>
{dropDownTriggerLabel}
Expand All @@ -75,7 +63,6 @@ export const DistanceFilter = () => {
labelSuffix="분"
/>
</section>

</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,134 +1,28 @@
"use client";

import { useEffect, useLayoutEffect, useRef, useState, type ChangeEvent } from "react";
import { Checkbox } from "@/src/shared/lib/headlessUi/checkBox/checkbox";
import { Input } from "@/src/shared/ui/input/deafult";
import { HistogramSlider } from "./HistogramSlider";
import { useParams } from "next/navigation";
import { useListingDetailNoticeSheet } from "@/src/entities/listings/hooks/useListingDetailSheetHooks";
import { CostResponse } from "@/src/entities/listings/model/type";
import { useListingDetailCountStore, useListingDetailFilter } from "@/src/features/listings/model";
import { ListingCardDetailOut } from "@/src/features/listings/ui/listingsCardDetail/button/button";

const DEPOSIT_STEP = 10;
const WON_UNIT = 1;
const GAP = 2;
const BAR_COUNT = 21;
const MAX_INDEX = BAR_COUNT - 1;

export const HISTOGRAM_VALUES = [
10, 13, 15, 16, 17, 15, 14, 13, 14, 15, 16, 17, 18, 15, 12, 10, 14, 13, 12, 11, 10,
];

const formatNumber = (value: number) => {
const normalized = Number.isFinite(value) ? value : 0;
return Math.round(normalized).toLocaleString("ko-KR");
};
const toKRW = (valueInMan: number) => valueInMan * WON_UNIT;
import { useCostFilter } from "@/src/features/listings/ui/listingsCardDetail/hooks/costHooks";

export const CostFilter = () => {
const [activeIndex, setActiveIndex] = useState(DEPOSIT_STEP);
const { id } = useParams() as { id: string };
const { data } = useListingDetailNoticeSheet<CostResponse>({
id: id,
url: "cost",
});
const DEPOSIT_MIN = data?.minPrice ?? 0;
const DEPOSIT_MAX = data?.maxPrice ?? 0;
const HISTOGRAM_MIN = formatNumber(toKRW(DEPOSIT_MIN));
const HISTOGRAM_MAX = formatNumber(toKRW(DEPOSIT_MAX));
const AVG_COST = data?.avgPrice ?? 0;
const [isManualDeposit, setIsManualDeposit] = useState(false);
const { setMaxDeposit, maxDeposit, maxMonthPay, setMaxMonthPay } = useListingDetailFilter();
const [handleDepositInput, setHandleDepositInput] = useState("0");
const [deposit, setDeposit] = useState("0");
const { filteredCount } = useListingDetailCountStore();

// 슬라이더 인덱스를 가격 범위에 맞춰 실제 보증금 값으로 변환
const getDepositByIndex = (index: number) => {
if (DEPOSIT_MAX <= DEPOSIT_MIN) return DEPOSIT_MIN;
const step = (DEPOSIT_MAX - DEPOSIT_MIN) / MAX_INDEX;
return Math.round(DEPOSIT_MIN + step * index);
};

// 보증금 값을 현재 범위에 맞는 슬라이더 인덱스로 역변환
const getIndexByDepositValue = (value: number) => {
if (DEPOSIT_MAX <= DEPOSIT_MIN) return 0;
const ratio = (value - DEPOSIT_MIN) / (DEPOSIT_MAX - DEPOSIT_MIN);
const clamped = Math.min(1, Math.max(0, ratio));
return Math.round(clamped * MAX_INDEX);
};

const sliderRef = useRef<HTMLDivElement>(null);
const [containerWidth, setContainerWidth] = useState(0);
const maxValue = Math.max(...HISTOGRAM_VALUES);
const normalized = HISTOGRAM_VALUES.map(v => (v / maxValue) * 100);
const barCount = normalized.length;

// 히스토그램 컨테이너 폭 변화에 맞춰 막대 폭/위치 재계산
useLayoutEffect(() => {
if (!sliderRef.current) return;
const observer = new ResizeObserver(entries => {
setContainerWidth(entries[0].contentRect.width);
});
observer.observe(sliderRef.current);
return () => observer.disconnect();
}, []);

// 전체 gap/막대 폭 계산 후 슬라이더 핸들의 픽셀/퍼센트 위치 산출
const totalGap = GAP * (barCount - 1);
const barWidth = barCount ? Math.max(0, (containerWidth - totalGap) / barCount) : 0;
const handleLeftPx = barWidth * activeIndex + GAP * activeIndex + barWidth / 2;
const handleLeftPct = containerWidth ? (handleLeftPx / containerWidth) * 100 : 0;
const maxlength = HISTOGRAM_VALUES.length - 1;

// API 데이터가 도착하면 평균값을 기준으로 슬라이더·입력 초기화
useEffect(() => {
if (!data) return;
const baseDeposit = data.avgPrice ?? data.minPrice ?? 0;
const formatted = formatNumber(toKRW(baseDeposit));
setDeposit(formatted);
setActiveIndex(getIndexByDepositValue(baseDeposit));
}, [AVG_COST, DEPOSIT_MIN, DEPOSIT_MAX, data]);

useEffect(() => {
if (!isManualDeposit) {
setMaxDeposit(deposit);
} else {
setMaxDeposit(handleDepositInput === "" ? "0" :handleDepositInput);
}
}, [deposit, handleDepositInput]);

// 슬라이더 인덱스를 실 보증금으로 변환
const handleDepositChange = (value: string) => {
const index = Number(value);
if (Number.isNaN(index)) return;
setActiveIndex(index);
const depositValue = getDepositByIndex(index);
setDeposit(formatNumber(toKRW(depositValue)));
};

// 직접 입력 시 숫자만 추려서 포맷
const handleDepositChangeText = (event: ChangeEvent<HTMLInputElement>) => {
const values = event.target.value;
console.log(values)
if(values === ""){
return setHandleDepositInput("");
}
const numericValue = Number(values.replace(/[^0-9]/g, ""));
setHandleDepositInput(formatNumber(toKRW(numericValue)));
};

const handleManualDepositChange = (event: ChangeEvent<HTMLInputElement>) => {
const rawValue = event.target.value;
const numericValue = Number(rawValue.replace(/[^0-9]/g, ""));
setMaxMonthPay(rawValue === "" ? "" : formatNumber(numericValue));
};

const handleManualToggle = (checked: boolean | "indeterminate") => {
const nextValue = checked === true;
setIsManualDeposit(nextValue);
};
const {
avgCostLabel,
histogramMaxLabel,
histogramMinLabel,
isManualDeposit,
maxDeposit,
maxMonthPay,
activeIndex,
deposit,
normalized,
handleLeftPct,
maxlength,
sliderRef,
handleDepositChange,
handleDepositChangeText,
handleManualDepositChange,
handleManualToggle,
} = useCostFilter();

return (
<div className="flex h-full flex-col bg-white">
Expand All @@ -139,18 +33,15 @@ export const CostFilter = () => {
</p>
<p className="text-xs font-medium leading-[150%] tracking-[-0.01em] text-greyscale-grey-400">
이 공고의 평균 보증금은{" "}
<span className="font-semibold text-primary-blue-400">
{data ? `${formatNumber(toKRW(AVG_COST))}만원` : "정보 없음"}
</span>{" "}
입니다.
<span className="font-semibold text-primary-blue-400">{avgCostLabel}</span> 입니다.
</p>
</div>

<div className="rounded-2xl px-2 pb-6 pt-5">
<div className="relative h-[120px] w-full">
<HistogramSlider
minLabel={HISTOGRAM_MIN + " 만"}
maxLabel={HISTOGRAM_MAX + " 만"}
minLabel={histogramMinLabel}
maxLabel={histogramMaxLabel}
disabled={isManualDeposit}
handleDepositChange={handleDepositChange}
activeIndex={activeIndex}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import { cn } from "@/lib/utils";
import { useListingDetailNoticeSheet } from "@/src/entities/listings/hooks/useListingDetailSheetHooks";
import { DistrictResponse } from "@/src/entities/listings/model/type";
import {
REGION_CHECKBOX,
useListingDetailCountStore,
useListingDetailFilter,
} from "@/src/features/listings/model";
import { REGION_CHECKBOX, useListingDetailFilter } from "@/src/features/listings/model";
import { Checkbox } from "@/src/shared/lib/headlessUi/checkBox/checkbox";
import { TagButton } from "@/src/shared/ui/button/tagButton";
import { Spinner } from "@/src/shared/ui/spinner/default";
import { useParams } from "next/navigation";
import { useState } from "react";
import { ListingCardDetailOut } from "@/src/features/listings/ui/listingsCardDetail/button/button";
import { Tag } from "@/src/features/listings/ui/listingsCardDetail/hooks/regionHooks";

export const RegionFilter = () => {
const { id } = useParams() as { id: string };
const regionType = useListingDetailFilter(state => state.region);
const setRegion = useListingDetailFilter(state => state.toggleRegionType);
const { filteredCount } = useListingDetailCountStore();
const { data } = useListingDetailNoticeSheet<DistrictResponse>({
id: id,
url: "districts",
Expand Down Expand Up @@ -58,35 +50,6 @@ export const RegionFilter = () => {
</div>
))}
</div>

</div>
);
};

const Tag = ({
label,
selected,
onClick,
}: {
label: string;
selected: boolean;
onClick: () => void;
}) => {
console.log(selected);
return (
<>
<TagButton
key={label}
size="xs"
variant="chipSelected"
className={cn(
"border border-greyscale-grey-100 p-3.5 text-xs font-bold text-greyscale-grey-400",
selected ? "bg-button-light text-text-inverse" : "bg-gray-100 text-text-secondary"
)}
onClick={onClick}
>
{label}
</TagButton>
</>
);
};
Loading