feat: multi-select contacts filter panel for groups sources and mood
This commit is contained in:
@@ -79,6 +79,19 @@ export function buildSearchWhere<W extends object>(
|
||||
return (parts.length === 1 ? parts[0] : { AND: parts }) as unknown as W;
|
||||
}
|
||||
|
||||
/** پارامتر چندتایی کوئری: «a,b,c» → ['a','b','c'] */
|
||||
export function splitCsv(raw: string | null | undefined, max = 50): string[] {
|
||||
if (!raw) return [];
|
||||
return [
|
||||
...new Set(
|
||||
String(raw)
|
||||
.split(',')
|
||||
.map((v) => v.trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
].slice(0, max);
|
||||
}
|
||||
|
||||
/** کمک برای فیلدهای متنی ساده: `{ field: { contains, mode: 'insensitive' } }` */
|
||||
export function ci(term: string) {
|
||||
return { contains: term, mode: 'insensitive' as const };
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
} from '../common/record-scope';
|
||||
import { normalizeTags } from '../common/tags';
|
||||
import { normalizePersonImportSource } from './person-source';
|
||||
import { buildSearchWhere, ci } from '../common/search';
|
||||
import { buildSearchWhere, ci, splitCsv } from '../common/search';
|
||||
import { GamificationService } from '../gamification/gamification.service';
|
||||
import {
|
||||
CustomerGroupsService,
|
||||
@@ -105,10 +105,10 @@ export class CompaniesService {
|
||||
{ city: ci(t) },
|
||||
{ province: ci(t) },
|
||||
]);
|
||||
const groupId = opts?.groupId?.trim();
|
||||
const groupIds = splitCsv(opts?.groupId);
|
||||
const and: Prisma.CompanyWhereInput[] = [
|
||||
...(search ? [search] : []),
|
||||
...(groupId ? [{ groupLinks: { some: { groupId } } }] : []),
|
||||
...(groupIds.length ? [{ groupLinks: { some: { groupId: { in: groupIds } } } }] : []),
|
||||
];
|
||||
const where: Prisma.CompanyWhereInput = {
|
||||
organizationId: actor.organizationId,
|
||||
|
||||
@@ -50,18 +50,20 @@ export class ListQueryDto {
|
||||
@Max(100)
|
||||
limit?: number = 20;
|
||||
|
||||
/** وضعیت مشتری؛ چندتایی با کاما */
|
||||
@IsOptional()
|
||||
@IsIn([...PERSON_MOODS])
|
||||
mood?: (typeof PERSON_MOODS)[number];
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
mood?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn(['name', 'newest'])
|
||||
sort?: 'name' | 'newest';
|
||||
|
||||
/** فیلتر منبع ورود شخص */
|
||||
/** فیلتر منبع ورود شخص؛ چندتایی با کاما */
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(40)
|
||||
@MaxLength(400)
|
||||
importSource?: string;
|
||||
|
||||
/** استان شرکت مرتبط */
|
||||
@@ -87,10 +89,10 @@ export class ListQueryDto {
|
||||
@MaxLength(12)
|
||||
disc?: string;
|
||||
|
||||
/** فیلتر گروه مشتری */
|
||||
/** فیلتر گروه مشتری؛ چندتایی با کاما (هر کدام از گروهها) */
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(40)
|
||||
@MaxLength(2000)
|
||||
groupId?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import type {
|
||||
PersonMood,
|
||||
UpdatePersonRequest,
|
||||
} from '@mento/shared';
|
||||
import { FILTERED_IDS_MAX } from '@mento/shared';
|
||||
import { FILTERED_IDS_MAX, PERSON_MOODS } from '@mento/shared';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { OutboundSmsLogService } from '../outbound-sms/outbound-sms-log.service';
|
||||
@@ -45,7 +45,7 @@ import { SmsGatewayService } from '../sms-billing/sms-gateway.service';
|
||||
import { selectMonthBirthdays } from './birthday-month';
|
||||
import { renderSmsText } from '../common/sms-text';
|
||||
import { normalizeTags } from '../common/tags';
|
||||
import { buildSearchWhere, ci } from '../common/search';
|
||||
import { buildSearchWhere, ci, splitCsv } from '../common/search';
|
||||
import {
|
||||
normalizePersonImportSource,
|
||||
personImportSourceLabel,
|
||||
@@ -150,7 +150,7 @@ export class PeopleService implements OnModuleInit {
|
||||
actor: Actor,
|
||||
q?: string,
|
||||
opts?: {
|
||||
mood?: PersonMood;
|
||||
mood?: string;
|
||||
importSource?: string;
|
||||
province?: string;
|
||||
city?: string;
|
||||
@@ -159,9 +159,11 @@ export class PeopleService implements OnModuleInit {
|
||||
groupId?: string;
|
||||
},
|
||||
): Prisma.PersonWhereInput {
|
||||
const sourceFilter = opts?.importSource?.trim()
|
||||
? normalizePersonImportSource(opts.importSource)
|
||||
: undefined;
|
||||
const sources = [...new Set(splitCsv(opts?.importSource).map(normalizePersonImportSource))];
|
||||
const moods = splitCsv(opts?.mood).filter((m): m is PersonMood =>
|
||||
(PERSON_MOODS as readonly string[]).includes(m),
|
||||
);
|
||||
const groupIds = splitCsv(opts?.groupId);
|
||||
const search = buildSearchWhere<Prisma.PersonWhereInput>(q, (t) => [
|
||||
{ firstName: ci(t) },
|
||||
{ lastName: ci(t) },
|
||||
@@ -201,13 +203,12 @@ export class PeopleService implements OnModuleInit {
|
||||
}
|
||||
const disc = discFilter(opts?.disc);
|
||||
if (disc) and.push(disc);
|
||||
const groupId = opts?.groupId?.trim();
|
||||
if (groupId) and.push({ groupLinks: { some: { groupId } } });
|
||||
if (groupIds.length) and.push({ groupLinks: { some: { groupId: { in: groupIds } } } });
|
||||
return {
|
||||
organizationId: actor.organizationId,
|
||||
...ownerWhere(actor),
|
||||
...(opts?.mood ? { mood: opts.mood } : {}),
|
||||
...(sourceFilter ? { importSource: sourceFilter } : {}),
|
||||
...(moods.length ? { mood: { in: moods } } : {}),
|
||||
...(sources.length ? { importSource: { in: sources } } : {}),
|
||||
...(and.length ? { AND: and } : {}),
|
||||
};
|
||||
}
|
||||
@@ -218,7 +219,7 @@ export class PeopleService implements OnModuleInit {
|
||||
page = 1,
|
||||
limit = 20,
|
||||
opts?: {
|
||||
mood?: PersonMood;
|
||||
mood?: string;
|
||||
sort?: 'name' | 'newest';
|
||||
importSource?: string;
|
||||
province?: string;
|
||||
@@ -257,7 +258,7 @@ export class PeopleService implements OnModuleInit {
|
||||
actor: Actor,
|
||||
q?: string,
|
||||
opts?: {
|
||||
mood?: PersonMood;
|
||||
mood?: string;
|
||||
importSource?: string;
|
||||
province?: string;
|
||||
city?: string;
|
||||
|
||||
Reference in New Issue
Block a user