import { apiClient } from './api-client'; // ─── Enums ─────────────────────────────────────────────── export type TransactionType = 'SALE' | 'RENT'; export type PropertyType = 'APARTMENT' | 'HOUSE' | 'VILLA' | 'LAND' | 'OFFICE' | 'SHOPHOUSE'; export type ListingStatus = | 'DRAFT' | 'PENDING_REVIEW' | 'ACTIVE' | 'RESERVED' | 'SOLD' | 'RENTED' | 'EXPIRED' | 'REJECTED'; export type Direction = | 'NORTH' | 'SOUTH' | 'EAST' | 'WEST' | 'NORTHEAST' | 'NORTHWEST' | 'SOUTHEAST' | 'SOUTHWEST'; // ─── Interfaces ────────────────────────────────────────── export interface PropertyMedia { id: string; url: string; type: 'image' | 'video'; order: number; caption: string | null; } export interface ListingDetail { id: string; status: ListingStatus; transactionType: TransactionType; priceVND: string; pricePerM2: number | null; rentPriceMonthly: string | null; commissionPct: number | null; viewCount: number; saveCount: number; inquiryCount: number; publishedAt: string | null; createdAt: string; property: { id: string; propertyType: PropertyType; title: string; description: string; address: string; ward: string; district: string; city: string; areaM2: number; bedrooms: number | null; bathrooms: number | null; floors: number | null; direction: Direction | null; yearBuilt: number | null; legalStatus: string | null; amenities: string[] | null; projectName: string | null; latitude: number | null; longitude: number | null; media: PropertyMedia[]; thumbnail?: string | null; }; seller: { id: string; fullName: string; phone: string; }; agent: { id: string; userId: string; agency: string | null; } | null; } export interface PaginatedResult { data: T[]; total: number; page: number; limit: number; totalPages: number; } export interface CreateListingPayload { transactionType: TransactionType; priceVND: string; propertyType: PropertyType; title: string; description: string; address: string; ward: string; district: string; city: string; latitude: number; longitude: number; areaM2: number; usableAreaM2?: number; bedrooms?: number; bathrooms?: number; floors?: number; floor?: number; totalFloors?: number; direction?: Direction; yearBuilt?: number; legalStatus?: string; amenities?: string[]; projectName?: string; rentPriceMonthly?: string; commissionPct?: number; } export interface SearchListingsParams { status?: ListingStatus; transactionType?: TransactionType; propertyType?: PropertyType; city?: string; district?: string; minPrice?: string; maxPrice?: string; minArea?: number; maxArea?: number; bedrooms?: number; page?: number; limit?: number; } // ─── API Functions ─────────────────────────────────────── const API_BASE_URL = process.env['NEXT_PUBLIC_API_URL'] || 'http://localhost:3001/api/v1'; export const listingsApi = { create: (data: CreateListingPayload) => apiClient.post<{ listingId: string; propertyId: string; status: string }>( '/listings', data, ), getById: (id: string) => apiClient.get(`/listings/${id}`), search: (params: SearchListingsParams = {}) => { const query = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== '') query.append(key, String(value)); }); const qs = query.toString(); return apiClient.get>(`/listings${qs ? `?${qs}` : ''}`); }, updateStatus: (id: string, status: ListingStatus, moderationNotes?: string) => apiClient.post<{ status: string }>(`/listings/${id}/status`, { status, moderationNotes, }), uploadMedia: async (listingId: string, file: File, caption?: string) => { const formData = new FormData(); formData.append('file', file); if (caption) formData.append('caption', caption); const csrfToken = typeof document !== 'undefined' ? document.cookie.match(/(?:^|;\s*)XSRF-TOKEN=([^;]*)/)?.[1] : undefined; const headers: HeadersInit = {}; if (csrfToken) { headers['X-CSRF-Token'] = decodeURIComponent(csrfToken); } const res = await fetch(`${API_BASE_URL}/listings/${listingId}/media`, { method: 'POST', credentials: 'include', headers, body: formData, }); if (!res.ok) { const error = await res.json().catch(() => ({ message: res.statusText })); throw new Error(error.message || 'Upload failed'); } return res.json() as Promise<{ mediaId: string; url: string }>; }, };