-
@@ -298,31 +333,30 @@ function PropertyDetailContent({
}
function MarkerInfo({
+ rank,
onClose,
+ children,
...p
}:
& schema.RecommendationPropertySummary
- & { onClose: () => void }
+ & {
+ rank: number;
+ onClose: () => void;
+ children: React.ReactNode;
+ }
) {
return (
- {/* 상단 헤더: 타이틀 & 점수 & 닫기버튼 */}
-
-
-
- {p.name}
-
-
-
- {p.address.landLot || p.address.roadName || p.region?.name}
-
-
+ {/* 상단 헤더: 순위 & 점수 & 닫기버튼 */}
+
+
- {/* 상세 내용 */}
-
+ {children}
);
}
@@ -422,9 +455,13 @@ function RecommendationMap({
clickable={true}
>
onActiveChange(null)}
- />
+ >
+
+
+
)}
@@ -448,7 +485,7 @@ function RecommendationMap({
function Trophy({ rank }: { rank: number }) {
const imoji = ["🥇", "🥈", "🥉"][rank - 1] || "";
return (
-
+
+ {Math.round(score)}점
+
+ );
+}
+
function PriceBox({
label,
price,
@@ -521,7 +575,7 @@ function PropertySummaryBox(p: (
hasSalePrice: boolean;
hasJeonsePrice: boolean;
onActiveChange: (id: number) => void;
- onDetailClick: (id: number) => void;
+ onDetailClick: (id: number, rank: number) => void;
}
)) {
return (
@@ -559,7 +613,7 @@ function PropertySummaryBox(p: (
children="상세보기"
onClick={(e) => {
e.stopPropagation();
- p.onDetailClick(p.id);
+ p.onDetailClick(p.id, p.rank);
}}
/>
@@ -582,7 +636,7 @@ function RecommendationItems({
items: PropertySummaryWithRank[];
activeItem?: ActivePropertyItem | null;
onActiveChange: (id: number) => void;
- onDetailClick: (id: number) => void;
+ onDetailClick: (id: number, rank: number) => void;
hasSalePrice: boolean;
hasJeonsePrice: boolean;
className?: string;
@@ -680,12 +734,165 @@ function RecommendationSheet({
+function RecommendationRequestDataDialog({
+ taskId,
+ open,
+ onOpenChange,
+ onRecommendationNameSave,
+ recommendationName,
+ trigger,
+ requestData,
+}: {
+ taskId: string;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ onRecommendationNameSave: (name: string) => void;
+ recommendationName: string;
+ trigger: React.ReactNode;
+ requestData?: schema.RequestData;
+}) {
+ const navigation = useNavigate();
+ const location = useLocation();
+ const [submit, setSUbmit] = useState(false);
+ const selectedInfraTypes = new Set(requestData?.infrastructureTypes.map(x => x.type) ?? []);
+ const selectedSchoolDistricts = new Set(requestData?.schoolDistricts?.map(x => x.type) ?? []);
+ const schoolDistrictTypesStore = useSchoolDistrictTypesStore();
+
+ const [recName, setRecName] = useState(recommendationName);
+
+ useEffect(() => {
+ schoolDistrictTypesStore.fetch();
+ }, [schoolDistrictTypesStore]);
+
+ const onSubmit = useCallback>(e => {
+ e.preventDefault();
+ updateRecommendation(taskId, { name: recName })
+ .then(() => {
+ onRecommendationNameSave(recName);
+ setSUbmit(true);
+ setTimeout(() => {
+ setSUbmit(false);
+ }, 2000);
+ })
+ .then(() => {
+ navigation(location, {
+ replace: true,
+ state: {
+ ...location.state,
+ name: recName,
+ },
+ });
+ });
+ }, [location, navigation, taskId, recName, onRecommendationNameSave]);
+
+ return (
+
+ );
+}
+
+
+
const snapPoints = ["550px", 1];
interface LocationState {
/**
* - 추천 요청 후 페이지 이동 시 입력한 name 값 넘기기
- * - 추천 목록 페이지에서 클릭 시 화묜에 표시된 name 넘기기
+ * - 추천 목록 페이지에서 클릭 시 화면에 표시된 name 넘기기
*/
name?: string;
}
@@ -742,11 +949,13 @@ export function RecommendationPage() {
const [recommendation, setRecommendation] = useState(null);
const [detailOpen, setDetailOpen] = useState(false);
const [detailLoading, setDetailLoading] = useState(false);
- const [detailData, setDetailData] = useState(null);
+ const [details, setDetails] = useState>({});
const [detailError, setDetailError] = useState(null);
- const [selectedProperty, setSelectedProperty] = useState(null);
+ const [selectedProperty, setSelectedProperty] = useState<{ rank: number; item?: schema.RecommendationPropertySummary }>({ rank: 0 });
+
+ const [isRequestDataDialogOpen, setIsRequestDataDialogOpen] = useState(false);
+ const [recName, setRecName] = useState(location.state?.name?.trim() ?? "");
- const recName = recommendation?.requestData.name || location.state?.name || taskId;
const hasSalePrice = !!recommendation?.requestData.salePrice;
const hasJeonsePrice = !!recommendation?.requestData.jeonsePrice;
@@ -758,17 +967,18 @@ export function RecommendationPage() {
}
};
- const handleViewDetail = async (propertyId: number) => {
+ const handleViewDetail = async (propertyId: number, rank: number) => {
if (!recommendation) return;
- const prop = recommendation.properties?.find(p => p.id === propertyId);
- setSelectedProperty(prop ?? null);
+ const item = recommendation.properties?.find(p => p.id === propertyId);
+ setSelectedProperty({ rank, item });
setDetailOpen(true);
+ handlePropertyClick(propertyId, "detail");
+ if (details[propertyId]) return;
+
setDetailLoading(true);
setDetailError(null);
- setDetailData(null);
- handlePropertyClick(propertyId, "detail");
try {
const reqData = recommendation.requestData;
@@ -783,7 +993,10 @@ export function RecommendationPage() {
};
const detail = await getRecommendationProperty(taskId, propertyId, apiInput);
- setDetailData(detail);
+ setDetails(prev => ({
+ ...prev,
+ [propertyId]: detail,
+ }));
} catch (e) {
console.error(e);
setDetailError("상세 정보를 불러오는 데 실패했습니다.");
@@ -822,6 +1035,7 @@ export function RecommendationPage() {
const rec = await getRecommendation(taskId);
if (cancelled) return;
+ setRecName(p => rec.requestData.name?.trim() ?? p);
setRecState(rec.status);
setRecommendation(rec);
@@ -859,12 +1073,22 @@ export function RecommendationPage() {
}}
onDetailClick={handleViewDetail}
>
-
+
+ }
+ requestData={recommendation?.requestData}
+ />
+
{infraInfos.map(infra => (
-
+
))}
)}
{isMobile && <>
-
+
+ }
+ requestData={recommendation?.requestData}
+ />
+
{infraInfos.map((infra) => (
@@ -965,25 +1199,19 @@ export function RecommendationPage() {