customer search

This commit is contained in:
2026-07-07 20:57:26 +03:30
parent 9fdc81bff1
commit c009140f11
2 changed files with 41 additions and 3 deletions
+26 -3
View File
@@ -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,
+15
View File
@@ -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;
}