import { ApiPropertyOptional } from '@nestjs/swagger'; import { Transform, Type } from 'class-transformer'; import { IsBoolean, IsOptional, IsString, IsNumber, IsEnum, IsInt, Min, Max, } from 'class-validator'; export enum SortByOption { PRICE_ASC = 'price_asc', PRICE_DESC = 'price_desc', DATE_DESC = 'date_desc', RELEVANCE = 'relevance', } export class SearchPropertiesDto { @ApiPropertyOptional({ description: 'Free-text search query', example: 'chung cu quan 7' }) @IsOptional() @IsString() q?: string; @ApiPropertyOptional({ description: 'Property type filter', example: 'apartment' }) @IsOptional() @IsString() propertyType?: string; @ApiPropertyOptional({ description: 'Transaction type filter (sale or rent)', example: 'sale' }) @IsOptional() @IsString() transactionType?: string; @ApiPropertyOptional({ description: 'Minimum price in VND', example: 1000000000, minimum: 0 }) @IsOptional() @Type(() => Number) @IsNumber() @Min(0) priceMin?: number; @ApiPropertyOptional({ description: 'Maximum price in VND', example: 5000000000, minimum: 0 }) @IsOptional() @Type(() => Number) @IsNumber() @Min(0) priceMax?: number; @ApiPropertyOptional({ description: 'Minimum area in m²', example: 50, minimum: 0 }) @IsOptional() @Type(() => Number) @IsNumber() @Min(0) areaMin?: number; @ApiPropertyOptional({ description: 'Maximum area in m²', example: 200, minimum: 0 }) @IsOptional() @Type(() => Number) @IsNumber() @Min(0) areaMax?: number; @ApiPropertyOptional({ description: 'Number of bedrooms', example: 2, minimum: 0 }) @IsOptional() @Type(() => Number) @IsInt() @Min(0) bedrooms?: number; @ApiPropertyOptional({ description: 'District name', example: 'Quan 7' }) @IsOptional() @IsString() district?: string; @ApiPropertyOptional({ description: 'Ward name', example: 'Tan Phong' }) @IsOptional() @IsString() ward?: string; @ApiPropertyOptional({ description: 'City name', example: 'Ho Chi Minh' }) @IsOptional() @IsString() city?: string; @ApiPropertyOptional({ description: 'Chỉ trả về tin đang được đẩy nổi bật (featured)', example: true, }) @IsOptional() @Transform(({ value }) => { if (value === undefined || value === null || value === '') return undefined; if (typeof value === 'boolean') return value; const normalized = String(value).toLowerCase(); if (normalized === 'true' || normalized === '1') return true; if (normalized === 'false' || normalized === '0') return false; return value; }) @IsBoolean() featured?: boolean; @ApiPropertyOptional({ description: 'Sort order', enum: SortByOption, example: SortByOption.PRICE_ASC }) @IsOptional() @IsEnum(SortByOption) sortBy?: SortByOption; @ApiPropertyOptional({ description: 'Page number (1-based)', example: 1, default: 1, minimum: 1 }) @IsOptional() @Type(() => Number) @IsInt() @Min(1) @Transform(({ value }) => value ?? 1) page?: number; @ApiPropertyOptional({ description: 'Results per page', example: 20, default: 20, minimum: 1, maximum: 100 }) @IsOptional() @Type(() => Number) @IsInt() @Min(1) @Max(100) @Transform(({ value }) => value ?? 20) perPage?: number; }