diff --git a/src/entities/chart/types/risk-analysis.type.ts b/src/entities/chart/types/risk-analysis.type.ts index f0fd2ce..9fb6d74 100644 --- a/src/entities/chart/types/risk-analysis.type.ts +++ b/src/entities/chart/types/risk-analysis.type.ts @@ -2,19 +2,17 @@ * 위험도 분석 관련 entities */ -// 위험 요인 -export type RiskFactor = { +export type Factor = { name: string; percent: number; }; -// 위험도 요약 export type RiskSummary = { score: number; - factors: RiskFactor[]; + grade: string; + factors: Factor[]; }; -// 임대인 정보 export type Landlord = { landlordId: number; name: string; @@ -25,7 +23,6 @@ export type Landlord = { updatedAt: string; }; -// 임대인 신뢰도 export type LandlordTrust = { trustScore: number; subrogationCount: number; @@ -35,7 +32,6 @@ export type LandlordTrust = { grade: string; }; -// 임대인 소유 매물 export type LandlordPlace = { placeId: number; label: string; diff --git a/src/features/main/apis/diagnosis.api.ts b/src/features/main/apis/diagnosis.api.ts index 6448de0..58bc7e7 100644 --- a/src/features/main/apis/diagnosis.api.ts +++ b/src/features/main/apis/diagnosis.api.ts @@ -1,22 +1,30 @@ +import type { Landlord, LandlordPlace, LandlordTrust, RiskSummary } from '@/entities'; import { fetchInstance } from '@/shared'; import type { HouseType } from '../types'; export const DIAGNOSIS_API_PATH = '/api/diagnosis'; -interface DiagnosisApiResponse { +interface DiagnosisApiRequest { address: string; addressDetail: string; houseType: HouseType; deposit: number; } +interface DiagnosisApiResponse { + riskSummary: RiskSummary; + landlord: Landlord; + landlordTrust: LandlordTrust; + landlordPlaces: LandlordPlace[]; +} + export const diagnosisApi = async ({ address, addressDetail, houseType, deposit, -}: DiagnosisApiResponse) => { +}: DiagnosisApiRequest) => { const response = await fetchInstance.post(DIAGNOSIS_API_PATH, { address, addressDetail, diff --git a/src/features/main/components/features/chart/RiskFactorsBox.tsx b/src/features/main/components/features/chart/RiskFactorsBox.tsx index c27e213..0d770a0 100644 --- a/src/features/main/components/features/chart/RiskFactorsBox.tsx +++ b/src/features/main/components/features/chart/RiskFactorsBox.tsx @@ -1,7 +1,7 @@ -import type { RiskFactor } from '@/entities'; +import type { Factor } from '@/entities'; type Props = { - riskFactors: RiskFactor[]; + riskFactors: Factor[]; }; export const RiskFactorsBox = ({ riskFactors }: Props) => { diff --git a/src/features/main/components/features/form/DiagnosticForm.tsx b/src/features/main/components/features/form/DiagnosticForm.tsx index 8e9919a..6c00bf6 100644 --- a/src/features/main/components/features/form/DiagnosticForm.tsx +++ b/src/features/main/components/features/form/DiagnosticForm.tsx @@ -7,6 +7,7 @@ import { Button, Form } from '@/shared'; import { diagnosisApi } from '../../../apis'; import { type SearchAddressType, searchAddressSchema } from '../../../model'; +import { useHouseData } from '../../../store'; import type { HouseType } from '../../../types'; import { AddressField, @@ -17,6 +18,8 @@ import { } from '../../common'; export const DiagnosticForm = () => { + const { setDiagnosisData } = useHouseData(); + const form = useForm({ resolver: zodResolver(searchAddressSchema), defaultValues: { @@ -35,6 +38,9 @@ export const DiagnosticForm = () => { houseType: data.houseType as HouseType, deposit: data.deposit, }), + onSuccess: (data) => { + setDiagnosisData(data); + }, }); const onSubmit = (data: SearchAddressType) => { diff --git a/src/features/main/components/features/reliability/GradeBox.tsx b/src/features/main/components/features/reliability/GradeBox.tsx index 6a8aea1..41fb702 100644 --- a/src/features/main/components/features/reliability/GradeBox.tsx +++ b/src/features/main/components/features/reliability/GradeBox.tsx @@ -1,21 +1,30 @@ +import type { LandlordTrust } from '@/entities'; import { getTrustGradeColorClass } from '@/shared'; import { LANDLORD_TRUST_DATA } from '../../../mock'; -export const GradeBox = () => { +type Props = { + gradeData: LandlordTrust; +}; + +export const GradeBox = ({ gradeData }: Props) => { + const data = gradeData || LANDLORD_TRUST_DATA; + return (

임대인 신뢰도 등급

위험점수 : - {LANDLORD_TRUST_DATA.trustScore} + {data.trustScore}
- {LANDLORD_TRUST_DATA.grade} + {data.grade}
diff --git a/src/features/main/components/features/reliability/MultiHouseBox.tsx b/src/features/main/components/features/reliability/MultiHouseBox.tsx index 2a34b74..ca6beea 100644 --- a/src/features/main/components/features/reliability/MultiHouseBox.tsx +++ b/src/features/main/components/features/reliability/MultiHouseBox.tsx @@ -1,12 +1,20 @@ +import type { LandlordTrust } from '@/entities'; + import { LANDLORD_TRUST_DATA } from '../../../mock'; -export const MultiHouseBox = () => { +type Props = { + multiHouseData: LandlordTrust; +}; + +export const MultiHouseBox = ({ multiHouseData }: Props) => { + const data = multiHouseData || LANDLORD_TRUST_DATA; + return (

다주택자

- {LANDLORD_TRUST_DATA.ownedUnsoldCount} + {data.ownedUnsoldCount}
diff --git a/src/features/main/components/features/reliability/ReasonBox.tsx b/src/features/main/components/features/reliability/ReasonBox.tsx index c424909..5988b5f 100644 --- a/src/features/main/components/features/reliability/ReasonBox.tsx +++ b/src/features/main/components/features/reliability/ReasonBox.tsx @@ -1,10 +1,12 @@ import { RELIABILITY_REASONS } from '../../../mock'; export const ReasonBox = () => { + const data = RELIABILITY_REASONS; + return (

임대인 신뢰도 등급 사유

- {RELIABILITY_REASONS.map((reason, index) => ( + {data.map((reason, index) => (
{ +type Props = { + subrogationData: number; +}; + +export const SubrogationPaymentBox = ({ subrogationData }: Props) => { + const data = subrogationData || LANDLORD_TRUST_DATA.subrogationCount; + return (

대위변제 이력

- - {LANDLORD_TRUST_DATA.subrogationCount} + + {data}
diff --git a/src/features/main/constants/house-type.ts b/src/features/main/constants/house-type.ts index 8b5091a..16b0851 100644 --- a/src/features/main/constants/house-type.ts +++ b/src/features/main/constants/house-type.ts @@ -5,6 +5,6 @@ export const HOUSE_TYPE_OPTIONS = [ { value: 'ONE_ROOM', label: '원룸' }, { value: 'TWO_ROOM', label: '투룸' }, { value: 'OFFICETEL', label: '오피스텔' }, - { value: 'APARTMENT', label: '아파트' }, + { value: 'APT', label: '아파트' }, { value: 'ETC', label: '기타' }, ] as const; diff --git a/src/features/main/mock/risk-analysis.mock.ts b/src/features/main/mock/risk-analysis.mock.ts index b3ff28f..e0aa6ff 100644 --- a/src/features/main/mock/risk-analysis.mock.ts +++ b/src/features/main/mock/risk-analysis.mock.ts @@ -9,6 +9,7 @@ export const TEMP_RISK_ANALYSIS_DATA: RiskAnalysisResponse[] = [ data: { riskSummary: { score: 50, + grade: 'B', factors: [ { name: '전세가율', percent: 12 }, { name: '가격하락', percent: 10 }, diff --git a/src/features/main/store/index.ts b/src/features/main/store/index.ts index c88c0b6..cea6429 100644 --- a/src/features/main/store/index.ts +++ b/src/features/main/store/index.ts @@ -1 +1,2 @@ export * from './useMapAddress'; +export * from './useHouseData'; diff --git a/src/features/main/store/useHouseData.ts b/src/features/main/store/useHouseData.ts new file mode 100644 index 0000000..4e8e0ca --- /dev/null +++ b/src/features/main/store/useHouseData.ts @@ -0,0 +1,71 @@ +import { create } from 'zustand'; +import { devtools } from 'zustand/middleware'; + +import type { Landlord, LandlordPlace, LandlordTrust, RiskSummary } from '@/entities'; + +export type HouseDiagnosisData = { + riskSummary: RiskSummary; + landlord: Landlord; + landlordTrust: LandlordTrust; + landlordPlaces: LandlordPlace[]; +}; + +// API 응답 형태 +export type ApiResponse = { + data: HouseDiagnosisData; + status: string; + serverDateTime: string; + errorCode: string | null; + errorMessage: string | null; +}; + +type HouseDataState = { + // 상태 + diagnosisData: HouseDiagnosisData | null; + isLoading: boolean; + error: string | null; + + // 액션 + setDiagnosisData: (data: HouseDiagnosisData | ApiResponse) => void; + clearDiagnosisData: () => void; + resetState: () => void; + setLoading: (loading: boolean) => void; + setError: (error: string | null) => void; +}; + +export const useHouseData = create()( + devtools( + (set) => ({ + // 초기 상태 + diagnosisData: null, + isLoading: false, + error: null, + + // 액션들 + setDiagnosisData: (data: HouseDiagnosisData | ApiResponse) => { + // API 응답 형태인지 확인하고 적절히 처리 + const processedData = 'data' in data ? data.data : data; + set({ diagnosisData: processedData }); + }, + + clearDiagnosisData: () => { + set({ diagnosisData: null }); + }, + + resetState: () => { + set({ diagnosisData: null, isLoading: false, error: null }); + }, + + setLoading: (loading: boolean) => { + set({ isLoading: loading }); + }, + + setError: (error: string | null) => { + set({ error }); + }, + }), + { + name: 'house-data-store', + }, + ), +); diff --git a/src/features/main/store/useMapAddress.ts b/src/features/main/store/useMapAddress.ts index 1907946..b86e023 100644 --- a/src/features/main/store/useMapAddress.ts +++ b/src/features/main/store/useMapAddress.ts @@ -1,4 +1,5 @@ import { create } from 'zustand'; +import { devtools } from 'zustand/middleware'; import type { MapAddress } from '@/entities'; @@ -11,40 +12,47 @@ type MapAddressState = { updateAddress: (address: string) => void; }; -export const useMapAddress = create()((set, get) => ({ - mapAddress: null, - - setMapAddress: (address: MapAddress) => { - set({ mapAddress: address }); - }, - - clearMapAddress: () => { - set({ mapAddress: null }); - }, - - updateCoordinates: (latitude: number, longitude: number) => { - const currentAddress = get().mapAddress; - if (currentAddress) { - set({ - mapAddress: { - ...currentAddress, - latitude, - longitude, - }, - }); - } - }, - - updateAddress: (address: string) => { - const currentAddress = get().mapAddress; - - if (currentAddress) { - set({ - mapAddress: { - ...currentAddress, - address, - }, - }); - } - }, -})); +export const useMapAddress = create()( + devtools( + (set, get) => ({ + mapAddress: null, + + setMapAddress: (address: MapAddress) => { + set({ mapAddress: address }); + }, + + clearMapAddress: () => { + set({ mapAddress: null }); + }, + + updateCoordinates: (latitude: number, longitude: number) => { + const currentAddress = get().mapAddress; + if (currentAddress) { + set({ + mapAddress: { + ...currentAddress, + latitude, + longitude, + }, + }); + } + }, + + updateAddress: (address: string) => { + const currentAddress = get().mapAddress; + + if (currentAddress) { + set({ + mapAddress: { + ...currentAddress, + address, + }, + }); + } + }, + }), + { + name: 'map-address-store', // Redux DevTools에서 표시될 이름 + }, + ), +); diff --git a/src/features/main/ui/LandlordReliabilitySection.tsx b/src/features/main/ui/LandlordReliabilitySection.tsx index b049c0e..cb432de 100644 --- a/src/features/main/ui/LandlordReliabilitySection.tsx +++ b/src/features/main/ui/LandlordReliabilitySection.tsx @@ -1,13 +1,27 @@ +import { Spinner } from '@/shared'; + import { GradeBox, MultiHouseBox, ReasonBox, SubrogationPaymentBox } from '../components'; +import { useHouseData } from '../store'; export const LandlordReliabilitySection = () => { + const { diagnosisData, isLoading, error } = useHouseData(); + + if (isLoading) return ; + + if (error) return
{error}
; + + if (!diagnosisData?.landlordTrust) + return ( +
데이터가 없습니다. 임대인 신뢰도 데이터를 조회해주세요.
+ ); + return (
- + - - + +
); diff --git a/src/features/main/ui/RiskAnalysisSummarySection.tsx b/src/features/main/ui/RiskAnalysisSummarySection.tsx index 32f2183..f05cbe1 100644 --- a/src/features/main/ui/RiskAnalysisSummarySection.tsx +++ b/src/features/main/ui/RiskAnalysisSummarySection.tsx @@ -1,21 +1,32 @@ +import { Spinner } from '@/shared'; + import { RiskChartBox, RiskFactorsBox } from '../components'; -import { DEFAULT_RISK_ANALYSIS_DATA } from '../mock'; +import { DEFAULT_RISK_ANALYSIS_DATA } from '../mock/risk-analysis.mock'; +import { useHouseData } from '../store'; export const RiskAnalysisSummarySection = () => { - // 현재 사용할 임시 데이터 - const currentData = DEFAULT_RISK_ANALYSIS_DATA; // 사진과 일치하는 기본 데이터 + const { diagnosisData, isLoading, error } = useHouseData(); + + if (isLoading) return ; + + if (error) return
{error}
; + + // 진단 데이터가 없으면 mock 데이터 사용 + const data = diagnosisData || DEFAULT_RISK_ANALYSIS_DATA.data; + + // 데이터 구조 검증 + if (data?.riskSummary?.score === null || !data?.riskSummary?.factors) { + return
데이터 구조가 올바르지 않습니다.
; + } return (
{/* 메인 위험도 분석 섹션 */}
{/* 왼쪽: 위험도 게이지 */} - + {/* 오른쪽: 핵심 위험 요인 */} - +
);