customer search
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user