#24 WebOS Completion Co-authored-by: Sayan Datta <sayan@Sayans-MacBook-Air.local> Reviewed-on: #25
140 lines
4.0 KiB
TypeScript
140 lines
4.0 KiB
TypeScript
import type { InventoryPropertySummary } from '@/lib/velocityPlatformClient';
|
|
import type { Unit } from '@/types';
|
|
|
|
function asRecord(value: unknown): Record<string, unknown> {
|
|
return value && typeof value === 'object' && !Array.isArray(value)
|
|
? value as Record<string, unknown>
|
|
: {};
|
|
}
|
|
|
|
function asNumber(value: unknown): number | null {
|
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
return value;
|
|
}
|
|
if (typeof value === 'string') {
|
|
const cleaned = value.replace(/[^0-9.]/g, '');
|
|
const parsed = Number(cleaned);
|
|
if (Number.isFinite(parsed) && parsed > 0) {
|
|
return parsed;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function pickFirstNumber(values: unknown[]): number | null {
|
|
for (const value of values) {
|
|
const parsed = asNumber(value);
|
|
if (parsed !== null) {
|
|
return parsed;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function mapInventoryStatus(status: string): Unit['status'] {
|
|
switch ((status ?? '').toLowerCase()) {
|
|
case 'active':
|
|
return 'available';
|
|
case 'under_review':
|
|
return 'reserved';
|
|
case 'archived':
|
|
return 'hold';
|
|
default:
|
|
return 'hold';
|
|
}
|
|
}
|
|
|
|
function inferArea(unitMix: unknown[]): number {
|
|
for (const item of unitMix) {
|
|
const record = asRecord(item);
|
|
const area = pickFirstNumber([
|
|
record.avg_area_sqm,
|
|
record.avg_area,
|
|
record.area_sqm,
|
|
record.area,
|
|
record.size_sqm,
|
|
record.size,
|
|
]);
|
|
if (area !== null) {
|
|
return Math.round(area);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function inferPrice(priceBands: unknown[]): number {
|
|
for (const item of priceBands) {
|
|
const record = asRecord(item);
|
|
const price = pickFirstNumber([
|
|
record.from,
|
|
record.min,
|
|
record.price,
|
|
record.starting_price,
|
|
record.amount,
|
|
record.value,
|
|
]);
|
|
if (price !== null) {
|
|
return Math.round(price);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function inferType(propertyType: string, unitMix: unknown[]): Unit['type'] {
|
|
const normalizedPropertyType = (propertyType ?? '').toLowerCase();
|
|
if (normalizedPropertyType.includes('penthouse')) return 'penthouse';
|
|
if (normalizedPropertyType.includes('studio')) return 'studio';
|
|
if (normalizedPropertyType.includes('1')) return '1br';
|
|
if (normalizedPropertyType.includes('2')) return '2br';
|
|
if (normalizedPropertyType.includes('3')) return '3br';
|
|
|
|
for (const item of unitMix) {
|
|
const record = asRecord(item);
|
|
const raw = String(
|
|
record.type ?? record.unit_type ?? record.label ?? record.configuration ?? ''
|
|
).toLowerCase();
|
|
if (raw.includes('penthouse')) return 'penthouse';
|
|
if (raw.includes('studio')) return 'studio';
|
|
if (raw.includes('1')) return '1br';
|
|
if (raw.includes('2')) return '2br';
|
|
if (raw.includes('3')) return '3br';
|
|
}
|
|
|
|
return '2br';
|
|
}
|
|
|
|
function inferFloor(unitMix: unknown[]): number {
|
|
for (const item of unitMix) {
|
|
const record = asRecord(item);
|
|
const floor = pickFirstNumber([record.floor, record.level, record.start_floor]);
|
|
if (floor !== null) {
|
|
return Math.round(floor);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
export function mapInventoryPropertySummaryToUnit(
|
|
property: InventoryPropertySummary,
|
|
index: number,
|
|
): Unit {
|
|
const location = asRecord(property.location);
|
|
const unitMix = Array.isArray(property.unit_mix) ? property.unit_mix : [];
|
|
const priceBands = Array.isArray(property.price_bands) ? property.price_bands : [];
|
|
const district = typeof location.district === 'string' ? location.district : '';
|
|
const city = typeof location.city === 'string' ? location.city : '';
|
|
const view = [district, city].filter(Boolean).join(', ') || property.developer_name || 'Location pending';
|
|
|
|
return {
|
|
id: property.property_id,
|
|
unitNumber: property.project_name || `Property ${index + 1}`,
|
|
type: inferType(property.property_type, unitMix),
|
|
floor: inferFloor(unitMix),
|
|
area: inferArea(unitMix),
|
|
price: inferPrice(priceBands),
|
|
status: mapInventoryStatus(property.status),
|
|
view,
|
|
lastUpdated: new Date(property.ingested_at ?? property.created_at ?? Date.now()),
|
|
};
|
|
}
|