106 lines
2.7 KiB
TypeScript
106 lines
2.7 KiB
TypeScript
export type AreaScore = {
|
|
area_id: string;
|
|
name: string;
|
|
district: string;
|
|
segment: string;
|
|
month: string;
|
|
investment_score: number;
|
|
recommendation: string;
|
|
liquidity_score: number;
|
|
momentum_score: number;
|
|
rent_support_score: number;
|
|
safety_margin_score: number;
|
|
credit_support_score: number;
|
|
supply_risk_score: number;
|
|
valuation_pressure_score: number;
|
|
transaction_count: number;
|
|
transaction_price_psm: number;
|
|
listing_count: number;
|
|
listing_price_psm: number;
|
|
rent_price_psm: number;
|
|
median_days_on_market: number;
|
|
annual_rent_yield_pct: number;
|
|
listing_pressure_ratio: number;
|
|
discount_pct: number;
|
|
price_momentum_pct: number;
|
|
volume_momentum_pct: number;
|
|
};
|
|
|
|
export type AreaScoreSummary = {
|
|
area_id: string;
|
|
name: string;
|
|
district: string;
|
|
investment_score: number;
|
|
recommendation: string;
|
|
};
|
|
|
|
export type MarketOverview = {
|
|
month: string;
|
|
area_count: number;
|
|
average_investment_score: number;
|
|
average_rent_yield_pct: number;
|
|
top_area: AreaScoreSummary | null;
|
|
weakest_area: AreaScoreSummary | null;
|
|
highest_supply_risk_area: AreaScoreSummary | null;
|
|
};
|
|
|
|
export type WatchlistItem = {
|
|
watchlist_item_id: number;
|
|
neighborhood_id: string | null;
|
|
area_id: string | null;
|
|
target_price_psm: number | null;
|
|
status: string;
|
|
notes: string;
|
|
};
|
|
|
|
export type CreateWatchlistItem = {
|
|
area_id?: string;
|
|
neighborhood_id?: string;
|
|
target_price_psm?: number;
|
|
notes?: string;
|
|
};
|
|
|
|
const API_BASE_URL =
|
|
process.env.NEXT_PUBLIC_API_BASE_URL?.replace(/\/$/, "") ?? "http://127.0.0.1:8080";
|
|
|
|
export async function fetchMarketOverview(month: string): Promise<MarketOverview> {
|
|
return fetchJson(`/api/v1/market/overview?month=${encodeURIComponent(month)}`);
|
|
}
|
|
|
|
export async function fetchAreaScores(month: string): Promise<AreaScore[]> {
|
|
return fetchJson(`/api/v1/areas/scores?month=${encodeURIComponent(month)}`);
|
|
}
|
|
|
|
export async function fetchWatchlistItems(): Promise<WatchlistItem[]> {
|
|
return fetchJson("/api/v1/watchlist");
|
|
}
|
|
|
|
export async function createWatchlistItem(
|
|
payload: CreateWatchlistItem,
|
|
): Promise<WatchlistItem> {
|
|
return fetchJson("/api/v1/watchlist", {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function archiveWatchlistItem(id: number): Promise<WatchlistItem> {
|
|
return fetchJson(`/api/v1/watchlist/${id}`, {
|
|
method: "DELETE",
|
|
});
|
|
}
|
|
|
|
async function fetchJson<T>(path: string, init?: RequestInit): Promise<T> {
|
|
const response = await fetch(`${API_BASE_URL}${path}`, {
|
|
...init,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...init?.headers,
|
|
},
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error(`API request failed with ${response.status}`);
|
|
}
|
|
return response.json() as Promise<T>;
|
|
}
|