diff --git a/src/customers/customers.service.ts b/src/customers/customers.service.ts index 58244b8..33a992d 100644 --- a/src/customers/customers.service.ts +++ b/src/customers/customers.service.ts @@ -76,9 +76,11 @@ export class CustomersService { const salon = await this.resolveSalonForUser(user); const summaries = await this.buildSalonCustomerSummaries(salon.id); const filtered = this.filterSalonCustomers(summaries, query); + const sorted = this.sortSalonCustomersByRecent(filtered); + const customers = query.limit ? sorted.slice(0, query.limit) : sorted; return { - customers: filtered, + customers, stats: this.buildSalonCustomerStats(summaries), }; } @@ -335,15 +337,18 @@ export class CustomersService { customers: SalonCustomerSummaryDto[], query: FindSalonCustomersQueryDto, ): SalonCustomerSummaryDto[] { - const search = query.search?.trim().toLowerCase(); + const search = query.search + ? this.normalizeSearchText(query.search) + : undefined; return customers.filter((customer) => { const fullName = `${customer.firstName} ${customer.lastName}`.toLowerCase(); + const normalizedMobile = this.normalizeSearchText(customer.mobile); const matchesSearch = !search || fullName.includes(search) || - customer.mobile.includes(search); + normalizedMobile.includes(search); const matchesStatus = !query.status || customer.status === query.status; @@ -351,6 +356,24 @@ export class CustomersService { }); } + private sortSalonCustomersByRecent( + customers: SalonCustomerSummaryDto[], + ): SalonCustomerSummaryDto[] { + return [...customers].sort((a, b) => { + const dateA = new Date(a.lastVisitDate ?? a.membershipDate).getTime(); + const dateB = new Date(b.lastVisitDate ?? b.membershipDate).getTime(); + return dateB - dateA; + }); + } + + private normalizeSearchText(value: string): string { + return value + .trim() + .toLowerCase() + .replace(/[۰-۹]/g, (digit) => String('۰۱۲۳۴۵۶۷۸۹'.indexOf(digit))) + .replace(/[٠-٩]/g, (digit) => String('٠١٢٣٤٥٦٧٨٩'.indexOf(digit))); + } + private buildSalonCustomerStats(customers: SalonCustomerSummaryDto[]) { return { total: customers.length, diff --git a/src/customers/dto/salon-customer.dto.ts b/src/customers/dto/salon-customer.dto.ts index 2809d29..cce5f89 100644 --- a/src/customers/dto/salon-customer.dto.ts +++ b/src/customers/dto/salon-customer.dto.ts @@ -1,12 +1,16 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; import { IsDateString, IsIn, + IsInt, IsNotEmpty, IsOptional, IsString, Length, Matches, + Max, + Min, } from 'class-validator'; export class CreateSalonCustomerDto { @@ -50,4 +54,15 @@ export class FindSalonCustomersQueryDto { @IsOptional() @IsIn(['active', 'at-risk', 'lost', 'vip']) status?: 'active' | 'at-risk' | 'lost' | 'vip'; + + @ApiPropertyOptional({ + description: 'Maximum number of customers to return (most recent first)', + example: 10, + }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + @Max(50) + limit?: number; }